
Libspng – C library for reading and writing PNG files - randy408
https://libspng.org/
======
derefr
I gotta say, it’s great that someone is making this attempt, however it turns
out. PNG has been in a uniquely bad position re: “monoculture”, with libpng
being not just the “reference implementation”, but the _only_ implementation
anyone bothers to use.

But libpng was intended as _only_ a reference implementation—a rigid adherent
to the PNG standard, for the sake of having a “runnable version” of the PNG
standard—and was never particularly optimized for production use-cases, or
code readability/maintainability/low attack surface, or any other criteria you
might like to have. Libpng is optimized for one thing only: allowing other PNG
implementations to build spec-compatibility test suites by testing against
libpng behaviour.

Because of this rigid adherence, libpng doesn’t implement—and will never
implement—any features that _aren’t_ in the base PNG spec, like APNG.

And since everyone _uses_ libpng as their PNG implementation (for _some_
reason), nobody ends up adopting these extra features. (Sure, all the major
browsers except Edge support APNG, but does your image editor? Does your IoT
thermostat whose OS uses PNG image assets? Does your game console? Nah.
Because these all just compile in libpng. And _this_ is what kills support for
features like APNG in the crib.)

That was never the PNG Working Group’s intent, of course. They don’t want to
restrict the development of extra PNG features (yes, even though they
developed MNG and might see features like APNG as “competing” with MNG, they
don’t really care.) They keep libpng the way it is, not for ideological
reasons, but simply because they don’t care about doing anything with libpng
that moves it away from “reference implementation of the base PNG spec”
territory. (And why would they? Their job as the PNG Working Group is to
produce the PNG base spec. It’s supposed to be everyone _else’s_ job, in the
ecosystem, to produce conformant implementations. They even gave you libpng to
make that easier!)

So, like I said, I’m glad someone is finally writing an alternative
implementation (in C), such that the projects that currently use libpng could
reasonably choose to switch to libspng. It’s the same feeling I get from
seeing LibreSSL and BoringSSL: that an ecosystem that was essentially “dead”
and stuck in a set of bad choices due to everyone sticking with one unchanging
reference impl, is now coming alive again.

~~~
Const-me
> the only implementation anyone bothers to use.

I don’t think that’s accurate. I use LibPNG but I also use other things too.

When I write for Linux or cross platform, my only requirement “decode bytes in
RAM to texture in VRAM”, I prefer stb_image for usability.

When I write Windows software, I prefer OS provided codecs. In modern world
that’s WIC for C++ code, previously OleLoadPicture API. This way I don’t have
to patch image codecs in my software for security bugs, MS does that in
windows updates. Also helps with binary size.

~~~
derefr
I guess what I meant was that libpng is (currently) the only implementation
that attempts to fit a libpng-shaped hole. Not in the sense of being API or
ABI compatible, but in the sense of being a free-floating multiplatform
library _solely_ dedicated to PNGs, rather than a higher-level multiformat
library or OS API.

Because of this, libpng gets linked in by a lot of the higher-level
multiformat libraries (like SDL) or graphics toolkits (like WxWidgets) when
their aim is _also_ to be “portable” free-floating multiplatform libraries,
rather than libraries that have special per-OS code-paths for everything.
(Which becomes especially important in things like embedded OSes/unikernels,
or sandboxes like Emscripten, where there _aren’t_ any OS image APIs.) And
these portable libraries and toolkits then get used by _runtimes_ that want
portability, like Erlang or Love2D; or by software that can’t access the OS’s
SDK to link to system libraries, like game-console homebrew.

In these cases, the feature-set of libpng determines the PNG features that
these higher-level libraries support. SDL supports loading animated images—but
not from APNG, because libpng doesn’t provide SDL with that capability. SDL
isn’t going to do the work of adding their own APNG support; they’ve got
enough on their plate just making sure all their components stay glued
together. But if there was a “better” PNG library than libpng, that ticked all
the same portability boxes? They’d switch.

------
Ono-Sendai
Some performance tests decoding PNGs:

Platform: Windows 10, Visual Studio 2019, RelWithDebInfo config, Intel Core
i7-8700K. Compiled with -DSPNG_SSE=3.

    
    
      testing loading image: D:\art\cryptovoxels\ortho overview.png (32-bit, large)
      libSPNG decode took 0.2221 s
      libPNG decode took  0.2373 s
    
      libSPNG decode took 0.2215 s
      libPNG decode took  0.2364 s
    
    
      testing loading image: D:\art\dof_test_output.png (32-bit)
      libSPNG decode took 0.01101 s
      libPNG decode took  0.01225 s
    
      libSPNG decode took 0.01096 s
      libPNG decode took  0.01205 s
    
    
      testing loading image: D:\art\skymodel2_test3\top2 CPU.png (24-bit)
      libSPNG decode took 0.008874 s
      libPNG decode took  0.009399 s
    
    

So Libspng seems slightly faster, but certainly not 35% faster, in these
tests.

~~~
Ono-Sendai
Testing with Release config (full program optimisation):

    
    
      testing loading image: D:\art\cryptovoxels\ortho overview.png
      libSPNG decode took 0.1901 s
      libPNG decode took  0.2069 s
    
      testing loading image: D:\art\skymodel2_test3\top2 CPU.png
      libSPNG decode took 0.007862 s
      libPNG decode took  0.007801 s

~~~
randy408
This is what I use for benchmarking:
[https://github.com/libspng/spngt](https://github.com/libspng/spngt), my
platform is Debian 10, GCC 8.3.0 with -O3 on an i5-4670. I use -SPNG_SSE=2
because that targets SSE2 instead of SSSE3, this matches the Debian build of
libpng, both are linked against zlib-ng. The results I get for
medium_rgb(a)8.png and large_palette.png are the results on the charts.

~~~
Ono-Sendai
What version of LibPNG are you comparing against? I am using LibPNG 1.635.
Note that recent versions of LibPNG have some SSE optimisations contributed
from Intel.

~~~
randy408
I'm using 1.6.36, the most obvious speedups should come from RGBA 8-bit PNG's,
that codepath doesn't rely on compiler optimizations.

------
floor_
I find stb_image single file format and lack of dependencies to be extremely
convenient.

~~~
Ono-Sendai
Libspng seems to just have spng.c and spng.h, which is just as convenient.

~~~
TazeTSchnitzel
But that doesn't include the zlib dependency

------
kodablah
I wonder how these libraries compare to using something like image-rs [0] w/ a
custom-exported C FFI extern? I have used that library's PNG decoder/encoder
in WASM and it seems simple and small and fast. I think it'd be worthy to
include in the next bench round since it's only a bit of glue code away from a
"C library" too.

0 - [https://github.com/image-rs/image](https://github.com/image-rs/image)

------
anaphor
How does this compare to something like
[https://github.com/nothings/stb/blob/master/stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h)
?

Is it just that it has more features and can handle weird edge cases?

~~~
japanoise
There's a comparison in the linked article.

~~~
anaphor
Ah, my bad! I didn't read it closely enough.

------
Ono-Sendai
Doesn't compile 'out of the box' in Visual Studio 2019.

~~~
randy408
Submit a bug report on github, appveyor doesn't report any issues with VS2017.

------
ape4
Just to say the obvious: by using zlib it gets future speed improvements and
bug fixes "for free".

------
wyldfire
FYI, randy: `fmt` is not declared in `Decode example - libspng`.

~~~
randy408
Fixed, thanks.

------
peapicker
Looks like it only does reading at this point - I can find docs or entry
points in the code for writing png.

------
megous
Thanks for including ARM optimizations and using meson. I'll try to use this
with my tablet UI app.

------
Ono-Sendai
Question: do you have to decode to a 4-channel format, e.g. with alpha? What
about decoding to just RGB?

~~~
randy408
For now it's either 8- or 16-bit RGBA, adding more output formats adds
complexity and requires more test cases. What's your use case for a 3-byte
layout?

~~~
Ono-Sendai
RGB uses less memory than RGBA (obviously), so if there is no alpha channel I
don't want to allocate the memory for a channel completely full of 255 bytes.

~~~
randy408
A 3-byte layout can be worse for performance, I think the simplest solution
would be to add a special format that always matches the PNG's format, that
way you always get RGB from RGB images, grayscale from grayscale images, etc.
This wouldn't require a conversion step.

~~~
Ono-Sendai
Yeah, generally if the PNG file is RGBA, then I want to load as RGBA, and if
it's RGB, I want to load as RGB.

------
your-nanny
I don't program C languages, but I can't help but feel that naming your
incompatible library libspng you are introducing a source of potential
confusion.

edit: Thank you cormacrelf for the explanation re: lib being mandatory. Thank
you OP for explaining your rationale.

~~~
cormacrelf
The 'lib' is generally mandatory so that the GCC/clang linker flag can find
the binaries in its search path. '-lpng' means 'find libpng.a or libpng.so'.
Hence many, many C libraries have it as part of the name of the project. In
general the 'lib' prefix in a name means 'this is a C library', or at least
now in the days of Rust, 'you can link to this with a C linker or normal FFI'.

Considering that, 'spng' is different enough from 'png'.

~~~
Eldt
It's a very generic name and adding a single letter doesn't do much to
differentiate at a cursory glance. If you're focused on what you're doing then
maybe...

~~~
FroshKiller
I suggest you not take on dependencies based on your cursory glances, then.

------
bhouston
libpng is so slow to compress. A large 2000x2000 png will actually take about
1 second to compress. This is basically forever for a user experience.

~~~
randy408
libpng compresses the image rows multiple times with different filters to
optimize for size, this filtering process is not optimized. The choice of zlib
also makes a big difference, stock zlib is very slow, zlib-ng is the closest
to a fast zlib alternative.

~~~
bhouston
Amazing info.

> libpng compresses the image rows multiple times with different filters to
> optimize for size, this filtering process is not optimized.

Is there a way to disable this multiple-pass approach to get a slightly larger
image that is generated faster?

> The choice of zlib also makes a big difference, stock zlib is very slow,
> zlib-ng is the closest to a fast zlib alternative.

I'm looking for benchmarks but I haven't found any that make it look good.
This one makes it look nearly equivalent to zlib:
[https://quixdb.github.io/squash-benchmark/?visible-
plugins=z...](https://quixdb.github.io/squash-benchmark/?visible-
plugins=zlib,zlib-ng)

~~~
randy408
> Is there a way to disable this multiple-pass approach to get a slightly
> larger image that is generated faster?

Use png_set_filter() with PNG_NO_FILTERS.

> This one makes it look nearly equivalent to zlib

I don't know how squash is set up, but decompression performance depends on
which library created the DEFLATE stream (see [https://github.com/zlib-
ng/zlib-ng/issues/326#issuecomment-4...](https://github.com/zlib-ng/zlib-
ng/issues/326#issuecomment-471076049)).

~~~
maxst
There is no multiple-pass. libpng only tries to guess the right combination of
filters, without doing actual compression. It's not always a good guess, but
at least it's fast.

