Giter VIP home page Giter VIP logo

png_pong's Introduction

PNG Pong

A pure Rust PNG/APNG encoder & decoder

tests docs crates.io

This is a pure Rust PNG image decoder and encoder taking inspiration from lodepng. This crate allows easy reading and writing of PNG files without any system dependencies.

Why another PNG crate?

These are the 4 Rust PNG encoder/decoder crates I know of:

  • png - The one everyone uses (used to be able to load less pngs than png_pong and slower, but has caught up).
  • lodepng - Loads all the PNGs, code is ported from C, therefore code is hard read & maintain, also uses slow implementation of deflate/inflate algorithm.
  • imagefmt - Abandoned, and limited in what files it can open, but with a lot less lines of code.
  • imagine - PNG decoding only.

Originally I made the aci_png based on imagefmt, and intended to add more features. At the time, I didn't want to write a PNG encoder/decoder from scratch so I decided to take lodepng which has more features (and more low level features) and clean up the code, upgrade to 2018 edition of Rust, depend on the miniz_oxide crate (because it can decompress faster than lodepng's INFLATE implementation) and get rid of the libc dependency so it actually becomes pure Rust (lodepng claimed to be, but called C's malloc and free). Then, I rewrote the entire library, based on gift and pix.

Goals

  • Forbid unsafe.
  • APNG support as iterator.
  • Fast.
  • Compatible with pix / gift-style API.
  • Load all PNG files crushed with pngcrush.
  • Save crushed PNG files.
  • Clean, well-documented, concise code.
  • Implement all completed, non-deprecated chunks in the PNG 1.2 Specification, including the PNG 1.2 Extensions and the APNG Specification

TODO

  • Implement APNG reading.
  • Implement Chunk reading (with all the different chunk structs).
  • StepDecoder should wrap StepDecoder, RasterEncoder should wrap ChunkEncoder
  • More test cases to test against.

Benchmarks And Comparisons

TODO: Update, ran on older png_pong and Rust 1.52.1

Using Rust 1.52.1, criterion and 4 different PNG sizes with PNGs from "./tests/png/" (units are: us / microseconds). I stopped anything that was predicted to take longer than a half hour with criterion with the message "TIMEOUT".

  • sRGB 1x1: Uses tests/png/profile.png
  • sRGBA 1x1: Uses tests/png/test.png
  • sRGB 64x64: Uses test/png/4.png
  • sRGBA 64x64: Uses tests/png/res.png
  • sRGB 256x256: tests/png/PngSuite.png
  • sRGBA 256x256: Uses tests/png/icon.png
  • sRGB 4096x4096: tests/png/plopgrizzly.png
  • sRGBA 4096x4096: Uses tests/png/noise.png

Encoder

Library sRGB 1x1 sRGBA 1x1 sRGB 64x64 sRGBA 64x64 sRGB 256x256 sRGBA 256x256 sRGB 4096x4096 sRGBA 4096x4096
png_pong 41.956 8.2661 1_025.7 700.80 2_646.1 5_061.5 587_320 3_587_100
png 29.538 9.4122 213.49 203.02 944.99 1_534.3 201_680 1_535_300
lodepng 59.989 10.700 1_399.3 982.63 3_391.3 6_664.7 831_190 3_394_900
imagefmt 8.7942 8.7399 211.01 177.82 901.22 1_569.4 218_550 1_285_700
imagine --- --- --- --- --- --- --- ---
aci_png FAILS 30.987 FAILS 289.24 FAILS 2_298.1 FAILS 2_135_400
libpng-sys 6.8443 2.9461 1_613.5 769.70 2_261.1 4_745.2 520_770 2_926_900

Decoder

Library sRGB 1x1 sRGBA 1x1 sRGB 64x64 sRGBA 64x64 sRGB 256x256 sRGBA 256x256 sRGB 4096x4096 sRGBA 4096x4096
png_pong 7.7520 3.9459 77.981 99.384 752.95 901.98 178_880 570_200
png 8.1195 6.6107 54.834 71.873 643.09 686.29 128_000 355_080
lodepng 5.8958 5.4527 77.050 97.762 882.83 982.76 230_570 563_210
imagefmt 4.2864 4.8706 74.715 82.026 566.86 758.27 69_465 545_060
imagine 2.8809 0.44822 3_202.3 2_266.4 2_056.1 10_753 442_750 27_944_000
aci_png 5.0243 4.3516 201.29 174.30 1_500.6 1_689.8 398_340 1_323_600
libpng-sys 3.6011 0.48747 1.8175 0.67344 25.809 4.4175 19_400 18_262

Table of Contents

API

API documentation can be found on docs.rs.

Features

There are no optional features.

Upgrade

You can use the changelog to facilitate upgrading this crate as a dependency.

MSRV

The current MSRV is Rust 1.70.

MSRV is updated according to the Ardaku MSRV guidelines.

License

Copyright © 2019-2024 The PNG Pong Crate Contributor(s)

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Before contributing, check out the contribution guidelines, and, as always, make sure to always follow the code of conduct.

png_pong's People

Contributors

aldaronlau avatar cjgriscom avatar dependabot-preview[bot] avatar dependabot[bot] avatar frankenapps avatar wezm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

png_pong's Issues

Support gAMA, cHRM chunks

Is your feature request related to a problem? Please describe.

Attempting to read an image with a gAMA and cHRM chunks (which are in the PNG1.2 spec) results in UnknownChunkType error.

Describe the solution you'd like

I'd like to be able to losslessly stream a PNG from a Decoder to Encoder, to do so it would be ideal to propagate all chunks from the source to the destination. Perhaps there could be an extra variant Unknown added to Chunk that carries the chunk type and data, so that it can be passed through to the encoder as-is.

Describe alternatives you've considered

I can ignore the error for now and skip this chunk.

Additional context

The image in question:

Screenshot_2021-02-12 Screenshot-192

Thread 'main' panicked at 'called `Option::unwrap()` on a `None` value'

Describe the bug
The attached image seems to crash while attempting to decode with the following error (referencing this line)

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value'

To Reproduce

// "raw" is an &[u8] of the raw file.
let png_pong::Step { raster, .. } = png_pong::Decoder::new(raw)
	.unwrap()
	.into_steps()
	.last();

Screenshots
mountains-on-mars

Desktop (please complete the following information):

  • OS: Debian Buster
  • Rust: stable-x86_64-unknown-linux-gnu (default) / rustc 1.52.1 (9bc8c42bb 2021-05-09)

You might like the `png_filters` crate

I put out a new crate called png_filters which is just the byte reconstruction ops but done in SIMD when it gives a performance boost.

Just thought you might find it useful to know about!

Support 1, 2, and 4 bit palette modes

Is your feature request related to a problem? Please describe.
Attempting to decode a PNG with a 4bpp palette results in a ColorMode(Palette, 4) error.

Describe the solution you'd like
I'd like it to "just work", perhaps providing the {1, 2, 4} bits in the 8 bit channel as before. (different types could also work, but would be more frustrating to deal with).

Describe alternatives you've considered
Converting the image to 8bpp ahead of time. (convert 4bpp.png PNG8:8bpp.png works for this)
Using the chunked api instead.
Using a different library.

Additional context
test_image.png

iTxt encode function does not encode the size of the "val" creating incorrectly sized chunk

Describe the bug
The size of the iTxt encoded box will be incorrect since the val field is not included.

To Reproduce
Here is an example of code that does not work correctly:

// make XMP chunk
let mut xmp_data = Vec::new();
let mut xmp_encoder = png_pong::Encoder::new(&mut xmp_data).into_chunk_enc();

            let mut xmp_chunk = png_pong::chunk::Chunk::InternationalText(InternationalText {
                key: XMP_KEY.to_string(),
                langtag: "".to_string(),
                transkey: "".to_string(),
                val: updated_xmp,
                compressed: false,
            });
            xmp_encoder
                .encode(&mut xmp_chunk)
                .map_err(|_| Error::EmbeddingError)?;

The size of xmp_data will be short by the length of updated_xmp.

The cause of the issue in the InternationText::write function. It does not include the size of "val" in the prepare statement:
// Encode
enc.prepare(
self.key.len() + self.langtag.len() + self.transkey.len() + 5,
consts::ITEXT,

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

Lift 79 bytes restriction for tEXt chunk payload

encode/text.rs is testing that the embedded text string in tEXt chunks is under 79 bytes for encoding.

if self.key.as_bytes().is_empty() || self.val.as_bytes().len() > 79 {
    return Err(EncoderError::TextSize(self.val.as_bytes().len()));
}

Despite larger strings being discouraged, the PNG spec doesn't set any upper bound on the size of the text string payload when encoding; the hard restriction is only on the length of the keyword.

In my particular use case, (character cards for language model chats), I'm dealing with long runs of base64-encoded json data embedded into png images, and I'm unable to use this crate for this. It'd be best if this restriction was lifted or could be opted out of. The reasoning in the spec for discouraging longer strings is mainly just for the readability of metadata displayed to users.

A 4th PNG decoding crate: `imagine`

Hi you don't know me but I was staring at the super long docs.rs releases queue right now and then i saw png_pong in the list and I have a PNG decoder crate so I thought "hey neat" and then in your crate it says that you know of 3 others, but I'm not on your list.

So, you can add https://github.com/Lokathor/imagine to your list if you like.

The version out on crates.io works, but forces allocation. The next draft has been revised many times and it's mostly working (will be finished "soon" tm), and it won't force allocations. Though it will have helper functions that can do the allocation for you.

MSRV addition

Is your feature request related to a problem? Please describe.
I noticed that as part of the latest release (thank you by the way) the MSRV was updated to 1.77. We use this as a dependency of our SDK, and try not to force people into the most bleeding edge version of Rust if possible, since people may not have upgraded. After trying to upgrade to png_pong v0.9.0 to fix a bug, our tests failed since we test on Rust 1.75 to ensure compatibility with older clients.

Describe the solution you'd like
I was wondering if the latest version needs 1.77 to function, or if this is an arbitrary requirement that is open to change/input - I also see that the MSRV policy says the following, so I didn't know if this was temporary:

The current MSRV policy only supports the most recent Rust. This will be changed in the future.

If you are open to changing it and there are no technical requirements to requiring 1.77, would it be possible to relax the requirement to something such as 1.73 (or even 1.75) for wider compatibility?

Describe alternatives you've considered
Upgrading our library to use 1.77, which would require code changes plus possibly render our versions incompatible with a subset of the population.

Additional context
N/A

Benchmark for libpng-sys seems to be wrong.

Hi, I was looking for a fast way to encode .pngs in Rust, so I stumbled over your performance comparison.
Looking at the table it seems like libpng-sys just totally rocks for sRGBA images. For example sRGBA 4096x4096 took 0.039266 ms, which if true would be totally insane.

So I tried it. Turns out it is as fast as measured, but only because it does exactly nothing. Here is my test script, heavily inspired by your benchmark:

#[macro_use] extern crate const_cstr;

fn main() {
    let data = std::fs::read("noise.png").expect("Failed to open PNG");
    let data = std::io::Cursor::new(data);
    let decoder = png_pong::Decoder::new(data).expect("Not PNG").into_steps();
    let step = decoder
        .last()
        .expect("No frames in PNG")
        .expect("PNG parsing error");

    let raster = match step.raster {
        png_pong::PngRaster::Rgba8(ok) => ok,
        _ => unreachable!(),
    };

    let before = std::time::Instant::now();

    // 1. Declare png_image struct, 2. Set members to describe image
    let mut png_image = libpng_sys::ffi::png_image {
        opaque: std::ptr::null_mut(),
        version: 0,
        width: raster.width(),
        height: raster.height(),
        format: libpng_sys::ffi::PNG_FORMAT_RGBA as u32,
        flags: 0,
        colormap_entries: 0,
        warning_or_error: 0,
        message: [0; 64],
    };
    // 3. Call png_image_write...
    /* let memory: *mut std::ffi::c_void = std::ptr::null_mut();
    let mut memory_bytes = 0;
    unsafe{
        let _r = libpng_sys::ffi::png_image_write_to_memory(
            &mut png_image,
            memory,
            &mut memory_bytes,
            0,
            raster.as_u8_slice().as_ptr().cast(),
            raster.width() as i32,
            std::ptr::null(),
        );
    } */

    unsafe {
        let path = const_cstr!("output.png").as_cstr().as_ptr();
        libpng_sys::ffi::png_image_write_to_file(
            &mut png_image,
            path, 
            0, 
            raster.as_u8_slice().as_ptr().cast(),
            raster.width() as i32,
            std::ptr::null(),
        );
    }

    println!("Encoding took {:#?}.", before.elapsed());
}

I had to use const-cstr in my Cargo.toml:

[package]
name = "libpng_exporter"
version = "0.1.0"
authors = ["FrankenApps <[email protected]>"]
edition = "2018"

[dependencies]
libpng-sys = "1.1.8"
png_pong = "0.8.0"
const-cstr = "0.3.0"

As you can see by running the code, no file was ever created, which I suspect is also true for encoding in memory.
I have not tested, if sRGB 4096x4096 works, but at 3.7263 ms that would still be insanely fast for .png encoding (I tried quite a bunch of stuff already ;-) ).

So it seems to me, that if have not missed anything libpng-sys should better be removed from the comparison, because it does not seem to work, or needs a fix to run properly...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.