If you followed my works, you know that I like compact, single-file implementations of decoders for various media formats, and where such a thing doesn’t exist, I tend to write or at least port one myself. Now I’d like to add the third format to that list: Baseline JPEG images.
There are already two decoders on the web that go by the name »Tiny JPEG Decoder«: One of them actually isn’t tiny at all, it’s nothing but a huge load of C++ bloat. Luc Saillard’s decoder at least deserves its name somehow – I use it for my demo engine currently. It’s far from perfect, though – the color conversion code is awful, for example. It may be reasonably fast, but it’s bloated (dedicated conversion routines for every common format) and it lacks a proper chroma upsampling filter, resulting in ugly artifacts.
Since I was writing a JPEG decoder for work anyway, I decided to write another one at home, too. My goals were compact code, reasonable quality (read: a proper upscaling filter is a must-have) and decent, but not necessarily good speed. I think I have achieved that. Here are the bullet points:
- decodes baseline JPEG only, no progressive or lossless JPEG
- supports 8-bit grayscale and YCbCr images, no 16 bit, CMYK or other color spaces
- supports any power-of-two chroma subsampling ratio
- supports restart markers
- the four points above mean that it should be able to decode all digital camera JPEG files and many other JPEG files
- below 900 lines of code (and that already includes over 200 lines of comments and empty lines!)
- converts YCbCr to RGB
- uses a bicubic chrominance upsampling filter (this is actually better than the mere bilinear filter of libjpeg!)
- a little slower than libjpeg
- memory requirements: ~512 KiB (static) + 1x the decoded image size for grayscale images or 2x the decoded image size for color images
- very simple API
- input: memory dumps of JPEG files
- output: memory dumps of raw, uncompressed 8-bit grayscale or 24-bit RGB pixels
- output format is compatible to the PGM/PPM file formats as well as OpenGL
- not fault-tolerant – any bitstream error will stop the decoder immediately and return an error to the application
- 100% pure C code
- no warnings with GCC and Clang
- no UBSan warnings when compiled with
- 32-bit integer arithmetic only
- supposed to be endianness independent
- 64-bit clean
- not thread-safe
- includes some provisions to build ultra-small Win32 executables
- open source
- single C file
- batteries example program included
If you want to check it out, here is it:
The code compiles to less than 6 kB of x86 code; with a suitable
main() function that doesn’t use any C runtime library calls, it’s easily possible to create a 8 KiB Win32 executable that does what the built-in example program does: Decode a JPEG file to PGM or PPM. (Well, almost. The Win32-only version lacks error messages :) This can be reduced further by using Crinkler, and voilà: Here’s a working JPEG decompression program for Windows in just 4072 bytes:
Ports to other programming languages
Other people have been busy porting NanoJPEG to other languages. Here is a list of all ports that have been contributed so far:
- C++ port by Scott Graham, consisting of just a single header file (only
- Object Pascal (Delphi) port by Ville Krumlinde
- Python port by Andras Suller
- Oberon-2 port by CeeKay
- C# port by Roel van Uden
- C# port by Johannes Bildstein (uses unsafe code and is therefore faster than the other C# port)
- ActionScript port by Grayson Lang
Finally, there’s my own port to (thread-safe) C and C++, called »µJPEG«, that adds a second bicubic chroma scaler for co-sited chroma samples (detected at runtime by examining the file’s Exif information), makes the scaler selectable at runtime and adds a »no decoding« mode where only the headers are examined to detect the image size.
2010-05-12: After a little delay of just (*cough*) two months, I updated nanojpeg.c to version 1.1. It fixes two compiler warnings with newer GCCs and, most importantly, a bug that caused NanoJPEG to reject valid files where the number of macroblocks is divisible by the restart interval – oops :)
Note that the new release is just an update of the C file, the example .exe file is still based on version 1.0.
2011-06-25: NanoJPEG now has an SVN repository: http://svn.emphy.de/nanojpeg/.
2012-02-18: Folkert van Heusden found and fixed a bug which caused NanoJPEG to generate syntax errors for valid streams that used 0xFFFF-style padding. The fix is incorporated in the updated version 1.2.