[
  {
    "path": ".gitignore",
    "content": "images/\nstb_image.h\nstb_image_write.h\nqoibench\nqoiconv\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Dominic Szablewski\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "CC ?= gcc\nCFLAGS_BENCH ?= -std=gnu99 -O3\nLFLAGS_BENCH ?= -lpng $(LDFLAGS)\nCFLAGS_CONV ?= -std=c99 -O3\nLFLAGS_CONV ?= $(LDFLAGS)\n\nTARGET_BENCH ?= qoibench\nTARGET_CONV ?= qoiconv\n\nall: $(TARGET_BENCH) $(TARGET_CONV)\n\nbench: $(TARGET_BENCH)\n$(TARGET_BENCH):$(TARGET_BENCH).c qoi.h\n\t$(CC) $(CFLAGS_BENCH) $(CFLAGS) $(TARGET_BENCH).c -o $(TARGET_BENCH) $(LFLAGS_BENCH)\n\nconv: $(TARGET_CONV)\n$(TARGET_CONV):$(TARGET_CONV).c qoi.h\n\t$(CC) $(CFLAGS_CONV) $(CFLAGS) $(TARGET_CONV).c -o $(TARGET_CONV) $(LFLAGS_CONV)\n\n.PHONY: clean\nclean:\n\t$(RM) $(TARGET_BENCH) $(TARGET_CONV)\n"
  },
  {
    "path": "README.md",
    "content": "![QOI Logo](https://qoiformat.org/qoi-logo.svg)\n\n# QOI - The “Quite OK Image Format” for fast, lossless image compression\n\nSingle-file MIT licensed library for C/C++\n\nSee [qoi.h](https://github.com/phoboslab/qoi/blob/master/qoi.h) for\nthe documentation and format specification.\n\nMore info at https://qoiformat.org\n\n\n## Why?\n\nQOI offers sweet-spot of compression ratio and throughput for lossless \nimage encoding. QOI's compression is roughly comparable to PNG (usually worse than \n[libPNG](https://github.com/pnggroup/libpng), but better than [stb_image_write.h](https://github.com/nothings/stb/blob/master/stb_image_write.h)), while throughput is a lot higher.\n\nBenchmark results on a few thousand images can be found here: https://qoiformat.org/benchmark/\n\nThe QOI format is also extremely simple, which helps a lot when porting to other languages.\n\n\n## Example Usage\n\n- [qoiconv.c](https://github.com/phoboslab/qoi/blob/master/qoiconv.c)\nconverts between png <> qoi\n - [qoibench.c](https://github.com/phoboslab/qoi/blob/master/qoibench.c)\na simple wrapper to benchmark stbi, libpng and qoi\n\n\n## MIME Type, File Extension\n\nThe recommended MIME type for QOI images is `image/qoi`. While QOI is not yet\nofficially registered with IANA, I believe QOI has found enough adoption to\nprevent any future image format from choosing the same name, thus making a \nMIME type collision highly unlikely ([see #167](https://github.com/phoboslab/qoi/issues/167)).\n\nThe recommended file extension for QOI images is `.qoi`\n\n\n## Limitations\n\nThe QOI file format allows for huge images with up to 18 exa-pixels. A streaming \nen-/decoder can handle these with minimal RAM requirements, assuming there is \nenough storage space.\n\nThis particular implementation of QOI however is limited to images with a \nmaximum size of 400 million pixels. It will safely refuse to en-/decode anything\nlarger than that. This is not a streaming en-/decoder. It loads the whole image\nfile into RAM before doing any work and is not extensively optimized for \nperformance (but it's still very fast).\n\nIf this is a limitation for your use case, please look into any of the other \nimplementations listed below.\n\n\n## Improvements, New Versions and Contributing\n\nThe QOI format has been finalized. It was a conscious decision to **not** have a\nversion number in the file header. If you have a working QOI implementation today, \nyou can rest assured that it will be compatible with all QOI files tomorrow.\n\nThere are a lot of interesting ideas for a successor of QOI, but none of these will \nbe implemented here. That doesn't mean you shouldn't experiment with QOI, but please\nbe aware that pull requests that change the format will not be accepted.\n\nLikewise, pull requests for performance improvements will probably not be accepted\neither, as this \"reference implementation\" tries to be as easy to read as possible.\n\n\n## Tools\n\n- [floooh/qoiview](https://github.com/floooh/qoiview) - native QOI viewer\n- [pfusik/qoi-fu](https://github.com/pfusik/qoi-fu/releases) - QOI Plugin installer for Windows Explorer, Finder, GNOME, GIMP 2, Paint.NET and XnView\n- [iOrange/QoiFileTypeNet](https://github.com/iOrange/QoiFileTypeNet/releases) - QOI Plugin for Paint.NET\n- [iOrange/QOIThumbnailProvider](https://github.com/iOrange/QOIThumbnailProvider) - Add thumbnails for QOI images in Windows Explorer\n- [Tom94/tev](https://github.com/Tom94/tev) - another native QOI viewer (allows pixel peeping and comparison with other image formats)\n- [wkjarosz/hdrview](https://github.com/wkjarosz/hdrview) - research-oriented HDR/LDR image viewer with QOI read/write support; runs natively or in-browser\n- [qoiconverterx](https://apps.apple.com/br/app/qoiconverterx/id1602159820) QOI <=> PNG converter available on the Mac App Store\n- [kaetemi/qoi-ma](https://github.com/kaetemi/qoi-max) - QOI Bitmap I/O Plugin for 3ds Max\n- [rtexviewer](https://raylibtech.itch.io/rtexviewer) - texture viewer, supports QOI\n- [rtexpacker](https://raylibtech.itch.io/rtexpacker) - texture packer, supports QOI\n- [DmitriySalnikov/godot_qoi](https://github.com/DmitriySalnikov/godot_qoi) - QOI GDNative Addon for Godot Engine\n- [dan9er/farbfeld-convert-qoi](https://gitlab.com/dan9er/farbfeld-convert-qoi) - QOI <=> farbfeld converter\n- [LTMX/Unity.QOI](https://github.com/LTMX/Unity.QOI) - QOI Importer and Exporter for the Unity3D Game Engine\n- [Ben1138/unity-qoi](https://github.com/Ben1138/unity-qoi) - QOI Importer(only) support for the Unity3D Game Engine\n- [xiaozhuai/jetbrains-qo](https://github.com/xiaozhuai/jetbrains-qoi) - [QOI Support](https://plugins.jetbrains.com/plugin/19352-qoi-support) for Jetbrains' IDE.\n- [serge-ivamov/QOIql](https://github.com/serge-ivamov/QOIql) - MacOS QuickLook plugin for QOI\n- [tobozo/kde-thumbnailer-qoi](https://github.com/tobozo/kde-thumbnailer-qoi) - QOI Thumbnailer for KDE\n- [walksanatora/qoi-thumbnailer-nemo](https://github.com/walksanatora/qoi-thumbnailer-nemo) - QOI Thumbnailer for Nemo\n- [hzeller/timg](https://github.com/hzeller/timg) - a terminal image viewer with QOI support\n- [LuisAlfredo92/Super-QOI-converter](https://github.com/LuisAlfredo92/Super-QOI-converter \"LuisAlfredo92/Super-QOI-converter\") - A program to convert JPG, JPEG, BMP, and PNG to QOI\n\t- [Console version](https://github.com/LuisAlfredo92/Super-QOI-converter-Console- \"Console version\"): Available for Linux, OSX and Windows\n\t- [GUI version](https://github.com/LuisAlfredo92/Super-QOI-converter-GUI- \"GUI version\"): Available only for windows\n- [tacent view](https://github.com/bluescan/tacentview) - Image and texture viewer, supports QOI\n- [colemanrgb/qoi2spr](https://github.com/colemanrgb/qoi2spr) - A variety of applications for decoding and encoding of QOI images on [RISC OS](https://www.riscosopen.org/)\n- [Muppetsg2/UnrealQOI](https://github.com/Muppetsg2/UnrealQOI) - QOI Format Import/Export plugin for Unreal Engine 5\n- [microsoft/PowerToys](https://github.com/microsoft/PowerToys) - Adds QOI file preview and thumbnails support to Windows Explorer\n\n## Implementations & Bindings of QOI\n\n- [pfusik/qoi-fu](https://github.com/pfusik/qoi-fu) - Fusion, transpiling to C, C++, C#, D, Java, JavaScript, Python, Swift and TypeScript\n- [kodonnell/qoi](https://github.com/kodonnell/qoi) - Python\n- [JaffaKetchup/dqoi](https://github.com/JaffaKetchup/dqoi) - Dart, with Flutter support\n- [Cr4xy/lua-qoi](https://github.com/Cr4xy/lua-qoi) - Lua\n- [superzazu/SDL_QOI](https://github.com/superzazu/SDL_QOI) - C, SDL2 bindings\n- [saharNooby/qoi-java](https://github.com/saharNooby/qoi-java) - Java\n- [MasterQ32/zig-qoi](https://github.com/MasterQ32/zig-qoi) - Zig\n- [rbino/qoix](https://github.com/rbino/qoix) - Elixir\n- [NUlliiON/QoiSharp](https://github.com/NUlliiON/QoiSharp) - C#\n- [aldanor/qoi-rust](https://github.com/aldanor/qoi-rust) - Rust\n- [zakarumych/rapid-qoi](https://github.com/zakarumych/rapid-qoi) - Rust\n- [takeyourhatoff/qoi](https://github.com/takeyourhatoff/qoi) - Go\n- [DosWorld/pasqoi](https://github.com/DosWorld/pasqoi) - Pascal\n- [elihwyma/Swift-QOI](https://github.com/elihwyma/Swift-QOI) - Swift\n- [xfmoulet/qoi](https://github.com/xfmoulet/qoi) - Go\n- [erratique.ch/qoic](https://erratique.ch/software/qoic) - OCaml\n- [arian/go-qoi](https://github.com/arian/go-qoi) - Go\n- [kchapelier/qoijs](https://github.com/kchapelier/qoijs) - JavaScript\n- [KristofferC/QOI.jl](https://github.com/KristofferC/QOI.jl) - Julia\n- [shadowMitia/libqoi](https://github.com/shadowMitia/libqoi) - C++\n- [MKCG/php-qoi](https://github.com/MKCG/php-qoi) - PHP\n- [LightHouseSoftware/qoiformats](https://github.com/LightHouseSoftware/qoiformats) - D\n- [mhoward540/qoi-nim](https://github.com/mhoward540/qoi-nim) - Nim\n- [wx257osn2/qoixx](https://github.com/wx257osn2/qoixx) - C++\n- [Tiefseetauchner/lr-paint](https://github.com/Tiefseetauchner/lr-paint) - Processing\n- [amstan/qoi-fpga](https://github.com/amstan/qoi-fpga) - FPGA: verilog\n- [musabkilic/qoi-decoder](https://github.com/musabkilic/qoi-decoder) - Python\n- [mathpn/py-qoi](https://github.com/mathpn/py-qoi) - Python\n- [Joshix/qoi-rs-python](https://codeberg.org/Joshix/qoi-rs-python/) - Python\n- [JohannesFriedrich/qoi4R](https://github.com/JohannesFriedrich/qoi4R) - R\n- [shraiwi/mini-qoi](https://github.com/shraiwi/mini-qoi) - C, streaming decoder\n- [10maurycy10/libqoi/](https://github.com/10maurycy10/libqoi/) - Rust\n- [0xd34df00d/hsqoi](https://github.com/0xd34df00d/hsqoi) - Haskell\n- [418Coffee/qoi-v](https://github.com/418Coffee/qoi-v) - V\n- [Imagine-Programming/QoiImagePlugin](https://github.com/Imagine-Programming/QoiImagePlugin) - PureBasic\n- [Fabien-Chouteau/qoi-spark](https://github.com/Fabien-Chouteau/qoi-spark) - Ada/SPARK formally proven\n- [mzgreen/qoi-kotlin](https://github.com/mzgreen/qoi-kotlin) - Kotlin Multiplatform\n- [Aftersol/Simplified-QOI-Codec](https://github.com/Aftersol/Simplified-QOI-Codec) - C99, encoder and decoder, freestanding\n- [AuburnSounds/gamut](https://github.com/AuburnSounds/gamut) - D\n- [AngusJohnson/TQoiImage](https://github.com/AngusJohnson/TQoiImage) - Delphi\n- [MarkJeronimus/qoi-java-spi](https://github.com/MarkJeronimus/qoi-java-spi) - Java SPI\n- [aumouvantsillage/qoi-racket](https://github.com/aumouvantsillage/qoi-racket) - Racket\n- [rubikscraft/qoi-stream](https://github.com/rubikscraft/qoi-stream) - C99, one byte at a time streaming encoder and decoder\n- [rubikscraft/qoi-img](https://github.com/rubikscraft/qoi-img) - NodeJS typescript, bindings to both [QOIxx](https://github.com/wx257osn2/qoixx) and [qoi-stream](https://github.com/rubikscraft/qoi-stream)\n- [grego/hare-qoi](https://git.sr.ht/~grego/hare-qoi) - Hare\n- [MrNocole/ZTQOI](https://github.com/MrNocole/ZTQOI) - Objective-C\n- [bpanthi977/qoi](https://github.com/bpanthi977/qoi) - Common Lisp\n- [Floessie/pam2qoi](https://github.com/Floessie/pam2qoi) - C++\n- [SpeckyYT/spwn-qoi](https://github.com/SpeckyYT/spwn-qoi) - SPWN\n- [n00bmind/qoi](https://github.com/n00bmind/qoi) - Jai\n- [SixLabors/ImageSharp](https://github.com/SixLabors/ImageSharp) - C# image proccesing library\n- [zertovitch/gid](https://github.com/zertovitch/gid) - Ada\n- [nazrin/lil](https://codeberg.org/nazrin/lil) - Lua image library\n- [Hema2-official/qoi-c3](https://github.com/Hema2-official/qoi-c3) - C3\n- [kwon-young/qoi-prolog](https://github.com/kwon-young/qoi-prolog) - Prolog\n- [google/wuffs](https://github.com/google/wuffs) - Wuffs\n- [dokutan/qoi-bf](https://github.com/dokutan/qoi-bf) - Brainfuck\n- [alex-s168/uiua-qoi](https://github.com/alex-s168/uiua-qoi) - Uiua\n- [hchargois/qoi](https://github.com/hchargois/qoi) - Go\n- [coralpink/qoi.cr](https://codeberg.org/coralpink/qoi.cr) - Crystal\n- [Pivok7/zqoi](https://github.com/Pivok7/zqoi) - Zig\n- [Muppetsg2/koi](https://github.com/Muppetsg2/koi) - stb-like single-file, public domain (or MIT-licensed) image processing libraries for C/C++\n\n## QOI Support in Other Software\n\n- [Amiga OS QOI datatype](https://github.com/dgaw/qoi-datatype) - adds support for decoding QOI images to the Amiga operating system.\n- [SerenityOS](https://github.com/SerenityOS/serenity) - supports decoding QOI system wide through a custom [cpp implementation in LibGfx](https://github.com/SerenityOS/serenity/blob/master/Userland/Libraries/LibGfx/ImageFormats/QOILoader.h)\n- [Raylib](https://github.com/raysan5/raylib) - supports decoding and encoding QOI textures through its [rtextures module](https://github.com/raysan5/raylib/blob/master/src/rtextures.c)\n- [Rebol3](https://github.com/Oldes/Rebol3/issues/39) - supports decoding and encoding QOI using a native codec\n- [c-ray](https://github.com/vkoskiv/c-ray) - supports QOI natively\n- [SAIL](https://sail.software) - image decoding library, supports decoding and encoding QOI images\n- [Orx](https://github.com/orx/orx) - 2D game engine, supports QOI natively\n- [IrfanView](https://www.irfanview.com) - supports decoding and encoding QOI through its Formats plugin\n- [ImageMagick](https://github.com/ImageMagick/ImageMagick) - supports decoding and encoding QOI, since 7.1.0-20\n- [barebox](https://barebox.org) - bootloader, supports decoding QOI images for splash logo, since v2022.03.0\n- [KorGE](https://korge.org) - & KorIM Kotlin 2D game engine and imaging library, supports decoding and encoding QOI natively since 2.7.0\n- [DOjS](https://github.com/SuperIlu/DOjS) - DOS JavaScript Canvas implementation supports loading QOI files\n- [XnView MP](https://www.xnview.com/en/xnviewmp/) - supports decoding QOI since 1.00\n- [ffmpeg](https://ffmpeg.org/) - supports decoding and encoding QOI since 5.1\n- [JPEGView](https://github.com/sylikc/jpegview) - lightweight Windows image viewer, supports decoding and encoding of QOI natively, since 1.1.44\n- [darktable](https://github.com/darktable-org/darktable) - photography workflow application and raw developer, supports decoding since 4.4.0\n- [KDE](https://kde.org) - supports decoding and encoding QOI images. Implemented in [KImageFormats](https://invent.kde.org/frameworks/kimageformats)\n- [EFL](https://www.enlightenment.org) - supports decoding and encoding QOI images since 1.27.\n- [Swingland](https://git.sr.ht/~phlash/swingland) - supports QOI decoding/loading via the `ImageIO` API of this Java Swing reimplemenation for Wayland\n- [Imagine](https://www.nyam.pe.kr/dev/imagine/) - supports decoding and encoding QOI images since 1.3.9\n- [Uiua](https://uiua.org) - supports decoding and encoding QOI images since 0.8.0\n- [Google Earth Pro](https://www.google.com/intl/en_uk/earth/about/versions/#download-pro) - supports Movie Maker export as sequence of QOI images since 7.3.6\n- [GIMP](https://www.gimp.org) - supports decoding and encoding QOI images since 3.0\n\n## Packages\n\n- [AUR](https://aur.archlinux.org/pkgbase/qoi-git/) - system-wide qoi.h, qoiconv and qoibench install as split packages.\n- [Debian](https://packages.debian.org/bookworm/source/qoi) - packages for binaries and qoi.h\n- [Ubuntu](https://launchpad.net/ubuntu/+source/qoi) - packages for binaries and qoi.h\n- [Fedora](https://packages.fedoraproject.org/pkgs/qoi) - packages for binaries and qoi.h\n\nPackages for other systems [tracked at Repology](https://repology.org/project/qoi/versions).\n"
  },
  {
    "path": "qoi.h",
    "content": "/*\n\nCopyright (c) 2021, Dominic Szablewski - https://phoboslab.org\nSPDX-License-Identifier: MIT\n\n\nQOI - The \"Quite OK Image\" format for fast, lossless image compression\n\n-- About\n\nQOI encodes and decodes images in a lossless format. Compared to stb_image and\nstb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and\n20% better compression.\n\n\n-- Synopsis\n\n// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this\n// library to create the implementation.\n\n#define QOI_IMPLEMENTATION\n#include \"qoi.h\"\n\n// Encode and store an RGBA buffer to the file system. The qoi_desc describes\n// the input pixel data.\nqoi_write(\"image_new.qoi\", rgba_pixels, &(qoi_desc){\n\t.width = 1920,\n\t.height = 1080,\n\t.channels = 4,\n\t.colorspace = QOI_SRGB\n});\n\n// Load and decode a QOI image from the file system into a 32bbp RGBA buffer.\n// The qoi_desc struct will be filled with the width, height, number of channels\n// and colorspace read from the file header.\nqoi_desc desc;\nvoid *rgba_pixels = qoi_read(\"image.qoi\", &desc, 4);\n\n\n\n-- Documentation\n\nThis library provides the following functions;\n- qoi_read    -- read and decode a QOI file\n- qoi_decode  -- decode the raw bytes of a QOI image from memory\n- qoi_write   -- encode and write a QOI file\n- qoi_encode  -- encode an rgba buffer into a QOI image in memory\n\nSee the function declaration below for the signature and more information.\n\nIf you don't want/need the qoi_read and qoi_write functions, you can define\nQOI_NO_STDIO before including this library.\n\nThis library uses malloc() and free(). To supply your own malloc implementation\nyou can define QOI_MALLOC and QOI_FREE before including this library.\n\nThis library uses memset() to zero-initialize the index. To supply your own\nimplementation you can define QOI_ZEROARR before including this library.\n\n\n-- Data Format\n\nA QOI file has a 14 byte header, followed by any number of data \"chunks\" and an\n8-byte end marker.\n\nstruct qoi_header_t {\n\tchar     magic[4];   // magic bytes \"qoif\"\n\tuint32_t width;      // image width in pixels (BE)\n\tuint32_t height;     // image height in pixels (BE)\n\tuint8_t  channels;   // 3 = RGB, 4 = RGBA\n\tuint8_t  colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear\n};\n\nImages are encoded row by row, left to right, top to bottom. The decoder and\nencoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An\nimage is complete when all pixels specified by width * height have been covered.\n\nPixels are encoded as\n - a run of the previous pixel\n - an index into an array of previously seen pixels\n - a difference to the previous pixel value in r,g,b\n - full r,g,b or r,g,b,a values\n\nThe color channels are assumed to not be premultiplied with the alpha channel\n(\"un-premultiplied alpha\").\n\nA running array[64] (zero-initialized) of previously seen pixel values is\nmaintained by the encoder and decoder. Each pixel that is seen by the encoder\nand decoder is put into this array at the position formed by a hash function of\nthe color value. In the encoder, if the pixel value at the index matches the\ncurrent pixel, this index position is written to the stream as QOI_OP_INDEX.\nThe hash function for the index is:\n\n\tindex_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64\n\nEach chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The\nbit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All\nvalues encoded in these data bits have the most significant bit on the left.\n\nThe 8-bit tags have precedence over the 2-bit tags. A decoder must check for the\npresence of an 8-bit tag first.\n\nThe byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte.\n\n\nThe possible chunks are:\n\n\n.- QOI_OP_INDEX ----------.\n|         Byte[0]         |\n|  7  6  5  4  3  2  1  0 |\n|-------+-----------------|\n|  0  0 |     index       |\n`-------------------------`\n2-bit tag b00\n6-bit index into the color index array: 0..63\n\nA valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the\nsame index. QOI_OP_RUN should be used instead.\n\n\n.- QOI_OP_DIFF -----------.\n|         Byte[0]         |\n|  7  6  5  4  3  2  1  0 |\n|-------+-----+-----+-----|\n|  0  1 |  dr |  dg |  db |\n`-------------------------`\n2-bit tag b01\n2-bit   red channel difference from the previous pixel between -2..1\n2-bit green channel difference from the previous pixel between -2..1\n2-bit  blue channel difference from the previous pixel between -2..1\n\nThe difference to the current channel values are using a wraparound operation,\nso \"1 - 2\" will result in 255, while \"255 + 1\" will result in 0.\n\nValues are stored as unsigned integers with a bias of 2. E.g. -2 is stored as\n0 (b00). 1 is stored as 3 (b11).\n\nThe alpha value remains unchanged from the previous pixel.\n\n\n.- QOI_OP_LUMA -------------------------------------.\n|         Byte[0]         |         Byte[1]         |\n|  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |\n|-------+-----------------+-------------+-----------|\n|  1  0 |  green diff     |   dr - dg   |  db - dg  |\n`---------------------------------------------------`\n2-bit tag b10\n6-bit green channel difference from the previous pixel -32..31\n4-bit   red channel difference minus green channel difference -8..7\n4-bit  blue channel difference minus green channel difference -8..7\n\nThe green channel is used to indicate the general direction of change and is\nencoded in 6 bits. The red and blue channels (dr and db) base their diffs off\nof the green channel difference and are encoded in 4 bits. I.e.:\n\tdr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)\n\tdb_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)\n\nThe difference to the current channel values are using a wraparound operation,\nso \"10 - 13\" will result in 253, while \"250 + 7\" will result in 1.\n\nValues are stored as unsigned integers with a bias of 32 for the green channel\nand a bias of 8 for the red and blue channel.\n\nThe alpha value remains unchanged from the previous pixel.\n\n\n.- QOI_OP_RUN ------------.\n|         Byte[0]         |\n|  7  6  5  4  3  2  1  0 |\n|-------+-----------------|\n|  1  1 |       run       |\n`-------------------------`\n2-bit tag b11\n6-bit run-length repeating the previous pixel: 1..62\n\nThe run-length is stored with a bias of -1. Note that the run-lengths 63 and 64\n(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and\nQOI_OP_RGBA tags.\n\n\n.- QOI_OP_RGB ------------------------------------------.\n|         Byte[0]         | Byte[1] | Byte[2] | Byte[3] |\n|  7  6  5  4  3  2  1  0 | 7 .. 0  | 7 .. 0  | 7 .. 0  |\n|-------------------------+---------+---------+---------|\n|  1  1  1  1  1  1  1  0 |   red   |  green  |  blue   |\n`-------------------------------------------------------`\n8-bit tag b11111110\n8-bit   red channel value\n8-bit green channel value\n8-bit  blue channel value\n\nThe alpha value remains unchanged from the previous pixel.\n\n\n.- QOI_OP_RGBA ---------------------------------------------------.\n|         Byte[0]         | Byte[1] | Byte[2] | Byte[3] | Byte[4] |\n|  7  6  5  4  3  2  1  0 | 7 .. 0  | 7 .. 0  | 7 .. 0  | 7 .. 0  |\n|-------------------------+---------+---------+---------+---------|\n|  1  1  1  1  1  1  1  1 |   red   |  green  |  blue   |  alpha  |\n`-----------------------------------------------------------------`\n8-bit tag b11111111\n8-bit   red channel value\n8-bit green channel value\n8-bit  blue channel value\n8-bit alpha channel value\n\n*/\n\n\n/* -----------------------------------------------------------------------------\nHeader - Public functions */\n\n#ifndef QOI_H\n#define QOI_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.\nIt describes either the input format (for qoi_write and qoi_encode), or is\nfilled with the description read from the file header (for qoi_read and\nqoi_decode).\n\nThe colorspace in this qoi_desc is an enum where\n\t0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel\n\t1 = all channels are linear\nYou may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely\ninformative. It will be saved to the file header, but does not affect\nhow chunks are en-/decoded. */\n\n#define QOI_SRGB   0\n#define QOI_LINEAR 1\n\ntypedef struct {\n\tunsigned int width;\n\tunsigned int height;\n\tunsigned char channels;\n\tunsigned char colorspace;\n} qoi_desc;\n\n#ifndef QOI_NO_STDIO\n\n/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file\nsystem. The qoi_desc struct must be filled with the image width, height,\nnumber of channels (3 = RGB, 4 = RGBA) and the colorspace.\n\nThe function returns 0 on failure (invalid parameters, or fopen or malloc\nfailed) or the number of bytes written on success. */\n\nint qoi_write(const char *filename, const void *data, const qoi_desc *desc);\n\n\n/* Read and decode a QOI image from the file system. If channels is 0, the\nnumber of channels from the file header is used. If channels is 3 or 4 the\noutput format will be forced into this number of channels.\n\nThe function either returns NULL on failure (invalid data, or malloc or fopen\nfailed) or a pointer to the decoded pixels. On success, the qoi_desc struct\nwill be filled with the description from the file header.\n\nThe returned pixel data should be free()d after use. */\n\nvoid *qoi_read(const char *filename, qoi_desc *desc, int channels);\n\n#endif /* QOI_NO_STDIO */\n\n\n/* Encode raw RGB or RGBA pixels into a QOI image in memory.\n\nThe function either returns NULL on failure (invalid parameters or malloc\nfailed) or a pointer to the encoded data on success. On success the out_len\nis set to the size in bytes of the encoded data.\n\nThe returned qoi data should be free()d after use. */\n\nvoid *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);\n\n\n/* Decode a QOI image from memory.\n\nThe function either returns NULL on failure (invalid parameters or malloc\nfailed) or a pointer to the decoded pixels. On success, the qoi_desc struct\nis filled with the description from the file header.\n\nThe returned pixel data should be free()d after use. */\n\nvoid *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);\n\n\n#ifdef __cplusplus\n}\n#endif\n#endif /* QOI_H */\n\n\n/* -----------------------------------------------------------------------------\nImplementation */\n\n#ifdef QOI_IMPLEMENTATION\n#include <stdlib.h>\n#include <string.h>\n\n#ifndef QOI_MALLOC\n\t#define QOI_MALLOC(sz) malloc(sz)\n\t#define QOI_FREE(p)    free(p)\n#endif\n#ifndef QOI_ZEROARR\n\t#define QOI_ZEROARR(a) memset((a),0,sizeof(a))\n#endif\n\n#define QOI_OP_INDEX  0x00 /* 00xxxxxx */\n#define QOI_OP_DIFF   0x40 /* 01xxxxxx */\n#define QOI_OP_LUMA   0x80 /* 10xxxxxx */\n#define QOI_OP_RUN    0xc0 /* 11xxxxxx */\n#define QOI_OP_RGB    0xfe /* 11111110 */\n#define QOI_OP_RGBA   0xff /* 11111111 */\n\n#define QOI_MASK_2    0xc0 /* 11000000 */\n\n#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)\n#define QOI_MAGIC \\\n\t(((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \\\n\t ((unsigned int)'i') <<  8 | ((unsigned int)'f'))\n#define QOI_HEADER_SIZE 14\n\n/* 2GB is the max file size that this implementation can safely handle. We guard\nagainst anything larger than that, assuming the worst case with 5 bytes per\npixel, rounded down to a nice clean value. 400 million pixels ought to be\nenough for anybody. */\n#define QOI_PIXELS_MAX ((unsigned int)400000000)\n\ntypedef union {\n\tstruct { unsigned char r, g, b, a; } rgba;\n\tunsigned int v;\n} qoi_rgba_t;\n\nstatic const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};\n\nstatic void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {\n\tbytes[(*p)++] = (0xff000000 & v) >> 24;\n\tbytes[(*p)++] = (0x00ff0000 & v) >> 16;\n\tbytes[(*p)++] = (0x0000ff00 & v) >> 8;\n\tbytes[(*p)++] = (0x000000ff & v);\n}\n\nstatic unsigned int qoi_read_32(const unsigned char *bytes, int *p) {\n\tunsigned int a = bytes[(*p)++];\n\tunsigned int b = bytes[(*p)++];\n\tunsigned int c = bytes[(*p)++];\n\tunsigned int d = bytes[(*p)++];\n\treturn a << 24 | b << 16 | c << 8 | d;\n}\n\nvoid *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {\n\tint i, max_size, p, run;\n\tint px_len, px_end, px_pos, channels;\n\tunsigned char *bytes;\n\tconst unsigned char *pixels;\n\tqoi_rgba_t index[64];\n\tqoi_rgba_t px, px_prev;\n\n\tif (\n\t\tdata == NULL || out_len == NULL || desc == NULL ||\n\t\tdesc->width == 0 || desc->height == 0 ||\n\t\tdesc->channels < 3 || desc->channels > 4 ||\n\t\tdesc->colorspace > 1 ||\n\t\tdesc->height >= QOI_PIXELS_MAX / desc->width\n\t) {\n\t\treturn NULL;\n\t}\n\n\tmax_size =\n\t\tdesc->width * desc->height * (desc->channels + 1) +\n\t\tQOI_HEADER_SIZE + sizeof(qoi_padding);\n\n\tp = 0;\n\tbytes = (unsigned char *) QOI_MALLOC(max_size);\n\tif (!bytes) {\n\t\treturn NULL;\n\t}\n\n\tqoi_write_32(bytes, &p, QOI_MAGIC);\n\tqoi_write_32(bytes, &p, desc->width);\n\tqoi_write_32(bytes, &p, desc->height);\n\tbytes[p++] = desc->channels;\n\tbytes[p++] = desc->colorspace;\n\n\n\tpixels = (const unsigned char *)data;\n\n\tQOI_ZEROARR(index);\n\n\trun = 0;\n\tpx_prev.rgba.r = 0;\n\tpx_prev.rgba.g = 0;\n\tpx_prev.rgba.b = 0;\n\tpx_prev.rgba.a = 255;\n\tpx = px_prev;\n\n\tpx_len = desc->width * desc->height * desc->channels;\n\tpx_end = px_len - desc->channels;\n\tchannels = desc->channels;\n\n\tfor (px_pos = 0; px_pos < px_len; px_pos += channels) {\n\t\tpx.rgba.r = pixels[px_pos + 0];\n\t\tpx.rgba.g = pixels[px_pos + 1];\n\t\tpx.rgba.b = pixels[px_pos + 2];\n\n\t\tif (channels == 4) {\n\t\t\tpx.rgba.a = pixels[px_pos + 3];\n\t\t}\n\n\t\tif (px.v == px_prev.v) {\n\t\t\trun++;\n\t\t\tif (run == 62 || px_pos == px_end) {\n\t\t\t\tbytes[p++] = QOI_OP_RUN | (run - 1);\n\t\t\t\trun = 0;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tint index_pos;\n\n\t\t\tif (run > 0) {\n\t\t\t\tbytes[p++] = QOI_OP_RUN | (run - 1);\n\t\t\t\trun = 0;\n\t\t\t}\n\n\t\t\tindex_pos = QOI_COLOR_HASH(px) & (64 - 1);\n\n\t\t\tif (index[index_pos].v == px.v) {\n\t\t\t\tbytes[p++] = QOI_OP_INDEX | index_pos;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tindex[index_pos] = px;\n\n\t\t\t\tif (px.rgba.a == px_prev.rgba.a) {\n\t\t\t\t\tsigned char vr = px.rgba.r - px_prev.rgba.r;\n\t\t\t\t\tsigned char vg = px.rgba.g - px_prev.rgba.g;\n\t\t\t\t\tsigned char vb = px.rgba.b - px_prev.rgba.b;\n\n\t\t\t\t\tsigned char vg_r = vr - vg;\n\t\t\t\t\tsigned char vg_b = vb - vg;\n\n\t\t\t\t\tif (\n\t\t\t\t\t\tvr > -3 && vr < 2 &&\n\t\t\t\t\t\tvg > -3 && vg < 2 &&\n\t\t\t\t\t\tvb > -3 && vb < 2\n\t\t\t\t\t) {\n\t\t\t\t\t\tbytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);\n\t\t\t\t\t}\n\t\t\t\t\telse if (\n\t\t\t\t\t\tvg_r >  -9 && vg_r <  8 &&\n\t\t\t\t\t\tvg   > -33 && vg   < 32 &&\n\t\t\t\t\t\tvg_b >  -9 && vg_b <  8\n\t\t\t\t\t) {\n\t\t\t\t\t\tbytes[p++] = QOI_OP_LUMA     | (vg   + 32);\n\t\t\t\t\t\tbytes[p++] = (vg_r + 8) << 4 | (vg_b +  8);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tbytes[p++] = QOI_OP_RGB;\n\t\t\t\t\t\tbytes[p++] = px.rgba.r;\n\t\t\t\t\t\tbytes[p++] = px.rgba.g;\n\t\t\t\t\t\tbytes[p++] = px.rgba.b;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tbytes[p++] = QOI_OP_RGBA;\n\t\t\t\t\tbytes[p++] = px.rgba.r;\n\t\t\t\t\tbytes[p++] = px.rgba.g;\n\t\t\t\t\tbytes[p++] = px.rgba.b;\n\t\t\t\t\tbytes[p++] = px.rgba.a;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpx_prev = px;\n\t}\n\n\tfor (i = 0; i < (int)sizeof(qoi_padding); i++) {\n\t\tbytes[p++] = qoi_padding[i];\n\t}\n\n\t*out_len = p;\n\treturn bytes;\n}\n\nvoid *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {\n\tconst unsigned char *bytes;\n\tunsigned int header_magic;\n\tunsigned char *pixels;\n\tqoi_rgba_t index[64];\n\tqoi_rgba_t px;\n\tint px_len, chunks_len, px_pos;\n\tint p = 0, run = 0;\n\n\tif (\n\t\tdata == NULL || desc == NULL ||\n\t\t(channels != 0 && channels != 3 && channels != 4) ||\n\t\tsize < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)\n\t) {\n\t\treturn NULL;\n\t}\n\n\tbytes = (const unsigned char *)data;\n\n\theader_magic = qoi_read_32(bytes, &p);\n\tdesc->width = qoi_read_32(bytes, &p);\n\tdesc->height = qoi_read_32(bytes, &p);\n\tdesc->channels = bytes[p++];\n\tdesc->colorspace = bytes[p++];\n\n\tif (\n\t\tdesc->width == 0 || desc->height == 0 ||\n\t\tdesc->channels < 3 || desc->channels > 4 ||\n\t\tdesc->colorspace > 1 ||\n\t\theader_magic != QOI_MAGIC ||\n\t\tdesc->height >= QOI_PIXELS_MAX / desc->width\n\t) {\n\t\treturn NULL;\n\t}\n\n\tif (channels == 0) {\n\t\tchannels = desc->channels;\n\t}\n\n\tpx_len = desc->width * desc->height * channels;\n\tpixels = (unsigned char *) QOI_MALLOC(px_len);\n\tif (!pixels) {\n\t\treturn NULL;\n\t}\n\n\tQOI_ZEROARR(index);\n\tpx.rgba.r = 0;\n\tpx.rgba.g = 0;\n\tpx.rgba.b = 0;\n\tpx.rgba.a = 255;\n\n\tchunks_len = size - (int)sizeof(qoi_padding);\n\tfor (px_pos = 0; px_pos < px_len; px_pos += channels) {\n\t\tif (run > 0) {\n\t\t\trun--;\n\t\t}\n\t\telse if (p < chunks_len) {\n\t\t\tint b1 = bytes[p++];\n\n\t\t\tif (b1 == QOI_OP_RGB) {\n\t\t\t\tpx.rgba.r = bytes[p++];\n\t\t\t\tpx.rgba.g = bytes[p++];\n\t\t\t\tpx.rgba.b = bytes[p++];\n\t\t\t}\n\t\t\telse if (b1 == QOI_OP_RGBA) {\n\t\t\t\tpx.rgba.r = bytes[p++];\n\t\t\t\tpx.rgba.g = bytes[p++];\n\t\t\t\tpx.rgba.b = bytes[p++];\n\t\t\t\tpx.rgba.a = bytes[p++];\n\t\t\t}\n\t\t\telse if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {\n\t\t\t\tpx = index[b1];\n\t\t\t}\n\t\t\telse if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {\n\t\t\t\tpx.rgba.r += ((b1 >> 4) & 0x03) - 2;\n\t\t\t\tpx.rgba.g += ((b1 >> 2) & 0x03) - 2;\n\t\t\t\tpx.rgba.b += ( b1       & 0x03) - 2;\n\t\t\t}\n\t\t\telse if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {\n\t\t\t\tint b2 = bytes[p++];\n\t\t\t\tint vg = (b1 & 0x3f) - 32;\n\t\t\t\tpx.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);\n\t\t\t\tpx.rgba.g += vg;\n\t\t\t\tpx.rgba.b += vg - 8 +  (b2       & 0x0f);\n\t\t\t}\n\t\t\telse if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {\n\t\t\t\trun = (b1 & 0x3f);\n\t\t\t}\n\n\t\t\tindex[QOI_COLOR_HASH(px) & (64 - 1)] = px;\n\t\t}\n\n\t\tpixels[px_pos + 0] = px.rgba.r;\n\t\tpixels[px_pos + 1] = px.rgba.g;\n\t\tpixels[px_pos + 2] = px.rgba.b;\n\t\t\n\t\tif (channels == 4) {\n\t\t\tpixels[px_pos + 3] = px.rgba.a;\n\t\t}\n\t}\n\n\treturn pixels;\n}\n\n#ifndef QOI_NO_STDIO\n#include <stdio.h>\n\nint qoi_write(const char *filename, const void *data, const qoi_desc *desc) {\n\tFILE *f = fopen(filename, \"wb\");\n\tint size, err;\n\tvoid *encoded;\n\n\tif (!f) {\n\t\treturn 0;\n\t}\n\n\tencoded = qoi_encode(data, desc, &size);\n\tif (!encoded) {\n\t\tfclose(f);\n\t\treturn 0;\n\t}\n\n\tfwrite(encoded, 1, size, f);\n\tfflush(f);\n\terr = ferror(f);\n\tfclose(f);\n\n\tQOI_FREE(encoded);\n\treturn err ? 0 : size;\n}\n\nvoid *qoi_read(const char *filename, qoi_desc *desc, int channels) {\n\tFILE *f = fopen(filename, \"rb\");\n\tint size, bytes_read;\n\tvoid *pixels, *data;\n\n\tif (!f) {\n\t\treturn NULL;\n\t}\n\n\tfseek(f, 0, SEEK_END);\n\tsize = ftell(f);\n\tif (size <= 0 || fseek(f, 0, SEEK_SET) != 0) {\n\t\tfclose(f);\n\t\treturn NULL;\n\t}\n\n\tdata = QOI_MALLOC(size);\n\tif (!data) {\n\t\tfclose(f);\n\t\treturn NULL;\n\t}\n\n\tbytes_read = fread(data, 1, size, f);\n\tfclose(f);\n\tpixels = (bytes_read != size) ? NULL : qoi_decode(data, bytes_read, desc, channels);\n\tQOI_FREE(data);\n\treturn pixels;\n}\n\n#endif /* QOI_NO_STDIO */\n#endif /* QOI_IMPLEMENTATION */\n"
  },
  {
    "path": "qoibench.c",
    "content": "/*\n\nCopyright (c) 2021, Dominic Szablewski - https://phoboslab.org\nSPDX-License-Identifier: MIT\n\n\nSimple benchmark suite for png, stbi and qoi\n\nRequires libpng, \"stb_image.h\" and \"stb_image_write.h\"\nCompile with: \n\tgcc qoibench.c -std=gnu99 -lpng -O3 -o qoibench \n\n*/\n\n#include <stdio.h>\n#include <dirent.h>\n#include <png.h>\n\n#define STB_IMAGE_IMPLEMENTATION\n#define STBI_ONLY_PNG\n#define STBI_NO_LINEAR\n#include \"stb_image.h\"\n\n#define STB_IMAGE_WRITE_IMPLEMENTATION\n#include \"stb_image_write.h\"\n\n#define QOI_IMPLEMENTATION\n#include \"qoi.h\"\n\n\n\n\n// -----------------------------------------------------------------------------\n// Cross platform high resolution timer\n// From https://gist.github.com/ForeverZer0/0a4f80fc02b96e19380ebb7a3debbee5\n\n#include <stdint.h>\n#if defined(__linux)\n\t#define HAVE_POSIX_TIMER\n\t#include <time.h>\n\t#ifdef CLOCK_MONOTONIC\n\t\t#define CLOCKID CLOCK_MONOTONIC\n\t#else\n\t\t#define CLOCKID CLOCK_REALTIME\n\t#endif\n#elif defined(__APPLE__)\n\t#define HAVE_MACH_TIMER\n\t#include <mach/mach_time.h>\n#elif defined(_WIN32)\n\t#define WIN32_LEAN_AND_MEAN\n\t#include <windows.h>\n#endif\n\nstatic uint64_t ns() {\n\tstatic uint64_t is_init = 0;\n#if defined(__APPLE__)\n\t\tstatic mach_timebase_info_data_t info;\n\t\tif (0 == is_init) {\n\t\t\tmach_timebase_info(&info);\n\t\t\tis_init = 1;\n\t\t}\n\t\tuint64_t now;\n\t\tnow = mach_absolute_time();\n\t\tnow *= info.numer;\n\t\tnow /= info.denom;\n\t\treturn now;\n#elif defined(__linux)\n\t\tstatic struct timespec linux_rate;\n\t\tif (0 == is_init) {\n\t\t\tclock_getres(CLOCKID, &linux_rate);\n\t\t\tis_init = 1;\n\t\t}\n\t\tuint64_t now;\n\t\tstruct timespec spec;\n\t\tclock_gettime(CLOCKID, &spec);\n\t\tnow = spec.tv_sec * 1.0e9 + spec.tv_nsec;\n\t\treturn now;\n#elif defined(_WIN32)\n\t\tstatic LARGE_INTEGER win_frequency;\n\t\tif (0 == is_init) {\n\t\t\tQueryPerformanceFrequency(&win_frequency);\n\t\t\tis_init = 1;\n\t\t}\n\t\tLARGE_INTEGER now;\n\t\tQueryPerformanceCounter(&now);\n\t\treturn (uint64_t) ((1e9 * now.QuadPart)\t/ win_frequency.QuadPart);\n#endif\n}\n\n#define STRINGIFY(x) #x\n#define TOSTRING(x) STRINGIFY(x)\n#define ERROR(...) printf(\"abort at line \" TOSTRING(__LINE__) \": \" __VA_ARGS__); printf(\"\\n\"); exit(1)\n\n\n// -----------------------------------------------------------------------------\n// libpng encode/decode wrappers\n// Seriously, who thought this was a good abstraction for an API to read/write\n// images?\n\ntypedef struct {\n\tint size;\n\tint capacity;\n\tunsigned char *data;\n} libpng_write_t;\n\nvoid libpng_encode_callback(png_structp png_ptr, png_bytep data, png_size_t length) {\n\tlibpng_write_t *write_data = (libpng_write_t*)png_get_io_ptr(png_ptr);\n\tif (write_data->size + length >= write_data->capacity) {\n\t\tERROR(\"PNG write\");\n\t}\n\tmemcpy(write_data->data + write_data->size, data, length);\n\twrite_data->size += length;\n}\n\nvoid *libpng_encode(void *pixels, int w, int h, int channels, int *out_len) {\n\tpng_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);\n\tif (!png) {\n\t\tERROR(\"png_create_write_struct\");\n\t}\n\n\tpng_infop info = png_create_info_struct(png);\n\tif (!info) {\n\t\tERROR(\"png_create_info_struct\");\n\t}\n\n\tif (setjmp(png_jmpbuf(png))) {\n\t\tERROR(\"png_jmpbuf\");\n\t}\n\n\t// Output is 8bit depth, RGBA format.\n\tpng_set_IHDR(\n\t\tpng,\n\t\tinfo,\n\t\tw, h,\n\t\t8,\n\t\tchannels == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA,\n\t\tPNG_INTERLACE_NONE,\n\t\tPNG_COMPRESSION_TYPE_DEFAULT,\n\t\tPNG_FILTER_TYPE_DEFAULT\n\t);\n\n\tpng_bytep row_pointers[h];\n\tfor(int y = 0; y < h; y++){\n\t\trow_pointers[y] = ((unsigned char *)pixels + y * w * channels);\n\t}\n\n\tlibpng_write_t write_data = {\n\t\t.size = 0,\n\t\t.capacity = w * h * channels,\n\t\t.data = malloc(w * h * channels)\n\t};\n\n\tpng_set_rows(png, info, row_pointers);\n\tpng_set_write_fn(png, &write_data, libpng_encode_callback, NULL);\n\tpng_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);\n\n\tpng_destroy_write_struct(&png, &info);\n\n\t*out_len = write_data.size;\n\treturn write_data.data;\n}\n\n\ntypedef struct {\n\tint pos;\n\tint size;\n\tunsigned char *data;\n} libpng_read_t;\n\nvoid png_decode_callback(png_structp png, png_bytep data, png_size_t length) {\n\tlibpng_read_t *read_data = (libpng_read_t*)png_get_io_ptr(png);\n\tif (read_data->pos + length > read_data->size) {\n\t\tERROR(\"PNG read %ld bytes at pos %d (size: %d)\", length, read_data->pos, read_data->size);\n\t}\n\tmemcpy(data, read_data->data + read_data->pos, length);\n\tread_data->pos += length;\n}\n\nvoid png_warning_callback(png_structp png_ptr, png_const_charp warning_msg) {\n\t// Ignore warnings about sRGB profiles and such.\n}\n\nvoid *libpng_decode(void *data, int size, int *out_w, int *out_h) {\t\n\tpng_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, png_warning_callback);\n\tif (!png) {\n\t\tERROR(\"png_create_read_struct\");\n\t}\n\n\tpng_infop info = png_create_info_struct(png);\n\tif (!info) {\n\t\tERROR(\"png_create_info_struct\");\n\t}\n\n\tlibpng_read_t read_data = {\n\t\t.pos = 0,\n\t\t.size = size,\n\t\t.data = data\n\t};\n\t\n\tpng_set_read_fn(png, &read_data, png_decode_callback);\n\tpng_set_sig_bytes(png, 0);\n\tpng_read_info(png, info);\n\t\n\tpng_uint_32 w, h;\n\tint bitDepth, colorType, interlaceType;\n\tpng_get_IHDR(png, info, &w, &h, &bitDepth, &colorType, &interlaceType, NULL, NULL);\n\t\n\t// 16 bit -> 8 bit\n\tpng_set_strip_16(png);\n\t\n\t// 1, 2, 4 bit -> 8 bit\n\tif (bitDepth < 8) {\n\t\tpng_set_packing(png);\n\t}\n\n\tif (colorType & PNG_COLOR_MASK_PALETTE) {\n\t\tpng_set_expand(png);\n\t}\n\t\n\tif (!(colorType & PNG_COLOR_MASK_COLOR)) {\n\t\tpng_set_gray_to_rgb(png);\n\t}\n\n\t// set paletted or RGB images with transparency to full alpha so we get RGBA\n\tif (png_get_valid(png, info, PNG_INFO_tRNS)) {\n\t\tpng_set_tRNS_to_alpha(png);\n\t}\n\t\n\t// make sure every pixel has an alpha value\n\tif (!(colorType & PNG_COLOR_MASK_ALPHA)) {\n\t\tpng_set_filler(png, 255, PNG_FILLER_AFTER);\n\t}\n\t\n\tpng_read_update_info(png, info);\n\n\tunsigned char* out = malloc(w * h * 4);\n\t*out_w = w;\n\t*out_h = h;\n\t\n\t// png_uint_32 rowBytes = png_get_rowbytes(png, info);\n\tpng_bytep row_pointers[h];\n\tfor (png_uint_32 row = 0; row < h; row++ ) {\n\t\trow_pointers[row] = (png_bytep)(out + (row * w * 4));\n\t}\n\t\n\tpng_read_image(png, row_pointers);\n\tpng_read_end(png, info);\n\tpng_destroy_read_struct( &png, &info, NULL);\n\t\n\treturn out;\n}\n\n\n// -----------------------------------------------------------------------------\n// stb_image encode callback\n\nvoid stbi_write_callback(void *context, void *data, int size) {\n\tint *encoded_size = (int *)context;\n\t*encoded_size += size;\n\t// In theory we'd need to do another malloc(), memcpy() and free() here to \n\t// be fair to the other decode functions...\n}\n\n\n// -----------------------------------------------------------------------------\n// function to load a whole file into memory\n\nvoid *fload(const char *path, int *out_size) {\n\tFILE *fh = fopen(path, \"rb\");\n\tif (!fh) {\n\t\tERROR(\"Can't open file\");\n\t}\n\n\tfseek(fh, 0, SEEK_END);\n\tint size = ftell(fh);\n\tfseek(fh, 0, SEEK_SET);\n\n\tvoid *buffer = malloc(size);\n\tif (!buffer) {\n\t\tERROR(\"Malloc for %d bytes failed\", size);\n\t}\n\n\tif (!fread(buffer, size, 1, fh)) {\n\t\tERROR(\"Can't read file %s\", path);\n\t}\n\tfclose(fh);\n\n\t*out_size = size;\n\treturn buffer;\n}\n\n\n// -----------------------------------------------------------------------------\n// benchmark runner\n\n\nint opt_runs = 1;\nint opt_nopng = 0;\nint opt_nowarmup = 0;\nint opt_noverify = 0;\nint opt_nodecode = 0;\nint opt_noencode = 0;\nint opt_norecurse = 0;\nint opt_onlytotals = 0;\n\nenum {\n\tLIBPNG,\n\tSTBI,\n\tQOI,\n\tBENCH_COUNT /* must be the last element */\n};\nstatic const char *const lib_names[BENCH_COUNT] = {\n\t// NOTE: pad with spaces so everything lines up properly\n\t[LIBPNG] =  \"libpng: \",\n\t[STBI]   =  \"stbi:   \",\n\t[QOI]    =  \"qoi:    \",\n};\n\ntypedef struct {\n\tuint64_t size;\n\tuint64_t encode_time;\n\tuint64_t decode_time;\n} benchmark_lib_result_t;\n\ntypedef struct {\n\tint count;\n\tuint64_t raw_size;\n\tuint64_t px;\n\tint w;\n\tint h;\n\tbenchmark_lib_result_t libs[BENCH_COUNT];\n} benchmark_result_t;\n\n\nvoid benchmark_print_result(benchmark_result_t res) {\n\tres.px /= res.count;\n\tres.raw_size /= res.count;\n\n\tdouble px = res.px;\n\tprintf(\"          decode ms   encode ms   decode mpps   encode mpps   size kb    rate\\n\");\n\tfor (int i = 0; i < BENCH_COUNT; ++i) {\n\t\tif (opt_nopng && (i == LIBPNG || i == STBI)) {\n\t\t\tcontinue;\n\t\t}\n\t\tres.libs[i].encode_time /= res.count;\n\t\tres.libs[i].decode_time /= res.count;\n\t\tres.libs[i].size /= res.count;\n\t\tprintf(\n\t\t\t\"%s   %8.1f    %8.1f      %8.2f      %8.2f  %8ld   %4.1f%%\\n\",\n\t\t\tlib_names[i],\n\t\t\t(double)res.libs[i].decode_time/1000000.0,\n\t\t\t(double)res.libs[i].encode_time/1000000.0,\n\t\t\t(res.libs[i].decode_time > 0 ? px / ((double)res.libs[i].decode_time/1000.0) : 0),\n\t\t\t(res.libs[i].encode_time > 0 ? px / ((double)res.libs[i].encode_time/1000.0) : 0),\n\t\t\tres.libs[i].size/1024,\n\t\t\t((double)res.libs[i].size/(double)res.raw_size) * 100.0\n\t\t);\n\t}\n\tprintf(\"\\n\");\n}\n\n// Run __VA_ARGS__ a number of times and measure the time taken. The first\n// run is ignored.\n#define BENCHMARK_FN(NOWARMUP, RUNS, AVG_TIME, ...) \\\n\tdo { \\\n\t\tuint64_t time = 0; \\\n\t\tfor (int i = NOWARMUP; i <= RUNS; i++) { \\\n\t\t\tuint64_t time_start = ns(); \\\n\t\t\t__VA_ARGS__ \\\n\t\t\tuint64_t time_end = ns(); \\\n\t\t\tif (i > 0) { \\\n\t\t\t\ttime += time_end - time_start; \\\n\t\t\t} \\\n\t\t} \\\n\t\tAVG_TIME = time / RUNS; \\\n\t} while (0)\n\n\nbenchmark_result_t benchmark_image(const char *path) {\n\tint encoded_png_size;\n\tint encoded_qoi_size;\n\tint w;\n\tint h;\n\tint channels;\n\n\t// Load the encoded PNG, encoded QOI and raw pixels into memory\n\tif(!stbi_info(path, &w, &h, &channels)) {\n\t\tERROR(\"Error decoding header %s\", path);\n\t}\n\n\tif (channels != 3) {\n\t\tchannels = 4;\n\t}\n\n\tvoid *pixels = (void *)stbi_load(path, &w, &h, NULL, channels);\n\tvoid *encoded_png = fload(path, &encoded_png_size);\n\tvoid *encoded_qoi = qoi_encode(pixels, &(qoi_desc){\n\t\t\t.width = w,\n\t\t\t.height = h, \n\t\t\t.channels = channels,\n\t\t\t.colorspace = QOI_SRGB\n\t\t}, &encoded_qoi_size);\n\n\tif (!pixels || !encoded_qoi || !encoded_png) {\n\t\tERROR(\"Error encoding %s\", path);\n\t}\n\n\t// Verify QOI Output\n\n\tif (!opt_noverify) {\n\t\tqoi_desc dc;\n\t\tvoid *pixels_qoi = qoi_decode(encoded_qoi, encoded_qoi_size, &dc, channels);\n\t\tif (memcmp(pixels, pixels_qoi, w * h * channels) != 0) {\n\t\t\tERROR(\"QOI roundtrip pixel mismatch for %s\", path);\n\t\t}\n\t\tfree(pixels_qoi);\n\t}\n\n\n\n\tbenchmark_result_t res = {0};\n\tres.count = 1;\n\tres.raw_size = w * h * channels;\n\tres.px = w * h;\n\tres.w = w;\n\tres.h = h;\n\n\n\t// Decoding\n\n\tif (!opt_nodecode) {\n\t\tif (!opt_nopng) {\n\t\t\tBENCHMARK_FN(opt_nowarmup, opt_runs, res.libs[LIBPNG].decode_time, {\n\t\t\t\tint dec_w, dec_h;\n\t\t\t\tvoid *dec_p = libpng_decode(encoded_png, encoded_png_size, &dec_w, &dec_h);\n\t\t\t\tfree(dec_p);\n\t\t\t});\n\n\t\t\tBENCHMARK_FN(opt_nowarmup, opt_runs, res.libs[STBI].decode_time, {\n\t\t\t\tint dec_w, dec_h, dec_channels;\n\t\t\t\tvoid *dec_p = stbi_load_from_memory(encoded_png, encoded_png_size, &dec_w, &dec_h, &dec_channels, 4);\n\t\t\t\tfree(dec_p);\n\t\t\t});\n\t\t}\n\n\t\tBENCHMARK_FN(opt_nowarmup, opt_runs, res.libs[QOI].decode_time, {\n\t\t\tqoi_desc desc;\n\t\t\tvoid *dec_p = qoi_decode(encoded_qoi, encoded_qoi_size, &desc, 4);\n\t\t\tfree(dec_p);\n\t\t});\n\t}\n\n\n\t// Encoding\n\tif (!opt_noencode) {\n\t\tif (!opt_nopng) {\n\t\t\tBENCHMARK_FN(opt_nowarmup, opt_runs, res.libs[LIBPNG].encode_time, {\n\t\t\t\tint enc_size;\n\t\t\t\tvoid *enc_p = libpng_encode(pixels, w, h, channels, &enc_size);\n\t\t\t\tres.libs[LIBPNG].size = enc_size;\n\t\t\t\tfree(enc_p);\n\t\t\t});\n\n\t\t\tBENCHMARK_FN(opt_nowarmup, opt_runs, res.libs[STBI].encode_time, {\n\t\t\t\tint enc_size = 0;\n\t\t\t\tstbi_write_png_to_func(stbi_write_callback, &enc_size, w, h, channels, pixels, 0);\n\t\t\t\tres.libs[STBI].size = enc_size;\n\t\t\t});\n\t\t}\n\n\t\tBENCHMARK_FN(opt_nowarmup, opt_runs, res.libs[QOI].encode_time, {\n\t\t\tint enc_size;\n\t\t\tvoid *enc_p = qoi_encode(pixels, &(qoi_desc){\n\t\t\t\t.width = w,\n\t\t\t\t.height = h, \n\t\t\t\t.channels = channels,\n\t\t\t\t.colorspace = QOI_SRGB\n\t\t\t}, &enc_size);\n\t\t\tres.libs[QOI].size = enc_size;\n\t\t\tfree(enc_p);\n\t\t});\n\t}\n\n\tfree(pixels);\n\tfree(encoded_png);\n\tfree(encoded_qoi);\n\n\treturn res;\n}\n\nvoid benchmark_directory(const char *path, benchmark_result_t *grand_total) {\n\tDIR *dir = opendir(path);\n\tif (!dir) {\n\t\tERROR(\"Couldn't open directory %s\", path);\n\t}\n\n\tstruct dirent *file;\n\n\tif (!opt_norecurse) {\n\t\tfor (int i = 0; (file = readdir(dir)) != NULL; i++) {\n\t\t\tif (\n\t\t\t\tfile->d_type & DT_DIR &&\n\t\t\t\tstrcmp(file->d_name, \".\") != 0 &&\n\t\t\t\tstrcmp(file->d_name, \"..\") != 0\n\t\t\t) {\n\t\t\t\tchar subpath[1024];\n\t\t\t\tsnprintf(subpath, 1024, \"%s/%s\", path, file->d_name);\n\t\t\t\tbenchmark_directory(subpath, grand_total);\n\t\t\t}\n\t\t}\n\t\trewinddir(dir);\n\t}\n\n\tbenchmark_result_t dir_total = {0};\n\t\n\tint has_shown_head = 0;\n\tfor (int i = 0; (file = readdir(dir)) != NULL; i++) {\n\t\tif (strcmp(file->d_name + strlen(file->d_name) - 4, \".png\") != 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!has_shown_head) {\n\t\t\thas_shown_head = 1;\n\t\t\tprintf(\"## Benchmarking %s/*.png -- %d runs\\n\\n\", path, opt_runs);\n\t\t}\n\n\t\tchar *file_path = malloc(strlen(file->d_name) + strlen(path)+8);\n\t\tsprintf(file_path, \"%s/%s\", path, file->d_name);\n\t\t\n\t\tbenchmark_result_t res = benchmark_image(file_path);\n\n\t\tif (!opt_onlytotals) {\n\t\t\tprintf(\"## %s size: %dx%d\\n\", file_path, res.w, res.h);\n\t\t\tbenchmark_print_result(res);\n\t\t}\n\n\t\tfree(file_path);\n\t\t\n\t\tdir_total.count++;\n\t\tdir_total.raw_size += res.raw_size;\n\t\tdir_total.px += res.px;\n\t\tfor (int i = 0; i < BENCH_COUNT; ++i) {\n\t\t\tdir_total.libs[i].encode_time += res.libs[i].encode_time;\n\t\t\tdir_total.libs[i].decode_time += res.libs[i].decode_time;\n\t\t\tdir_total.libs[i].size += res.libs[i].size;\n\t\t}\n\n\t\tgrand_total->count++;\n\t\tgrand_total->raw_size += res.raw_size;\n\t\tgrand_total->px += res.px;\n\t\tfor (int i = 0; i < BENCH_COUNT; ++i) {\n\t\t\tgrand_total->libs[i].encode_time += res.libs[i].encode_time;\n\t\t\tgrand_total->libs[i].decode_time += res.libs[i].decode_time;\n\t\t\tgrand_total->libs[i].size += res.libs[i].size;\n\t\t}\n\t}\n\tclosedir(dir);\n\n\tif (dir_total.count > 0) {\n\t\tprintf(\"## Total for %s\\n\", path);\n\t\tbenchmark_print_result(dir_total);\n\t}\n}\n\nint main(int argc, char **argv) {\n\tif (argc < 3) {\n\t\tprintf(\"Usage: qoibench <iterations> <directory> [options]\\n\");\n\t\tprintf(\"Options:\\n\");\n\t\tprintf(\"    --nowarmup ... don't perform a warmup run\\n\");\n\t\tprintf(\"    --nopng ...... don't run png encode/decode\\n\");\n\t\tprintf(\"    --noverify ... don't verify qoi roundtrip\\n\");\n\t\tprintf(\"    --noencode ... don't run encoders\\n\");\n\t\tprintf(\"    --nodecode ... don't run decoders\\n\");\n\t\tprintf(\"    --norecurse .. don't descend into directories\\n\");\n\t\tprintf(\"    --onlytotals . don't print individual image results\\n\");\n\t\tprintf(\"Examples\\n\");\n\t\tprintf(\"    qoibench 10 images/textures/\\n\");\n\t\tprintf(\"    qoibench 1 images/textures/ --nopng --nowarmup\\n\");\n\t\texit(1);\n\t}\n\n\tfor (int i = 3; i < argc; i++) {\n\t\tif (strcmp(argv[i], \"--nowarmup\") == 0) { opt_nowarmup = 1; }\n\t\telse if (strcmp(argv[i], \"--nopng\") == 0) { opt_nopng = 1; }\n\t\telse if (strcmp(argv[i], \"--noverify\") == 0) { opt_noverify = 1; }\n\t\telse if (strcmp(argv[i], \"--noencode\") == 0) { opt_noencode = 1; }\n\t\telse if (strcmp(argv[i], \"--nodecode\") == 0) { opt_nodecode = 1; }\n\t\telse if (strcmp(argv[i], \"--norecurse\") == 0) { opt_norecurse = 1; }\n\t\telse if (strcmp(argv[i], \"--onlytotals\") == 0) { opt_onlytotals = 1; }\n\t\telse { ERROR(\"Unknown option %s\", argv[i]); }\n\t}\n\n\topt_runs = atoi(argv[1]);\n\tif (opt_runs <=0) {\n\t\tERROR(\"Invalid number of runs %d\", opt_runs);\n\t}\n\n\tbenchmark_result_t grand_total = {0};\n\tbenchmark_directory(argv[2], &grand_total);\n\n\tif (grand_total.count > 0) {\n\t\tprintf(\"# Grand total for %s\\n\", argv[2]);\n\t\tbenchmark_print_result(grand_total);\n\t}\n\telse {\n\t\tprintf(\"No images found in %s\\n\", argv[2]);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "qoiconv.c",
    "content": "/*\n\nCopyright (c) 2021, Dominic Szablewski - https://phoboslab.org\nSPDX-License-Identifier: MIT\n\n\nCommand line tool to convert between png <> qoi format\n\nRequires:\n\t-\"stb_image.h\" (https://github.com/nothings/stb/blob/master/stb_image.h)\n\t-\"stb_image_write.h\" (https://github.com/nothings/stb/blob/master/stb_image_write.h)\n\t-\"qoi.h\" (https://github.com/phoboslab/qoi/blob/master/qoi.h)\n\nCompile with: \n\tgcc qoiconv.c -std=c99 -O3 -o qoiconv\n\n*/\n\n\n#define STB_IMAGE_IMPLEMENTATION\n#define STBI_ONLY_PNG\n#define STBI_NO_LINEAR\n#include \"stb_image.h\"\n\n#define STB_IMAGE_WRITE_IMPLEMENTATION\n#include \"stb_image_write.h\"\n\n#define QOI_IMPLEMENTATION\n#include \"qoi.h\"\n\n\n#define STR_ENDS_WITH(S, E) (strcmp(S + strlen(S) - (sizeof(E)-1), E) == 0)\n\nint main(int argc, char **argv) {\n\tif (argc < 3) {\n\t\tputs(\"Usage: qoiconv <infile> <outfile>\");\n\t\tputs(\"Examples:\");\n\t\tputs(\"  qoiconv input.png output.qoi\");\n\t\tputs(\"  qoiconv input.qoi output.png\");\n\t\texit(1);\n\t}\n\n\tvoid *pixels = NULL;\n\tint w, h, channels;\n\tif (STR_ENDS_WITH(argv[1], \".png\")) {\n\t\tif(!stbi_info(argv[1], &w, &h, &channels)) {\n\t\t\tprintf(\"Couldn't read header %s\\n\", argv[1]);\n\t\t\texit(1);\n\t\t}\n\n\t\t// Force all odd encodings to be RGBA\n\t\tif(channels != 3) {\n\t\t\tchannels = 4;\n\t\t}\n\n\t\tpixels = (void *)stbi_load(argv[1], &w, &h, NULL, channels);\n\t}\n\telse if (STR_ENDS_WITH(argv[1], \".qoi\")) {\n\t\tqoi_desc desc;\n\t\tpixels = qoi_read(argv[1], &desc, 0);\n\t\tchannels = desc.channels;\n\t\tw = desc.width;\n\t\th = desc.height;\n\t}\n\n\tif (pixels == NULL) {\n\t\tprintf(\"Couldn't load/decode %s\\n\", argv[1]);\n\t\texit(1);\n\t}\n\n\tint encoded = 0;\n\tif (STR_ENDS_WITH(argv[2], \".png\")) {\n\t\tencoded = stbi_write_png(argv[2], w, h, channels, pixels, 0);\n\t}\n\telse if (STR_ENDS_WITH(argv[2], \".qoi\")) {\n\t\tencoded = qoi_write(argv[2], pixels, &(qoi_desc){\n\t\t\t.width = w,\n\t\t\t.height = h, \n\t\t\t.channels = channels,\n\t\t\t.colorspace = QOI_SRGB\n\t\t});\n\t}\n\n\tif (!encoded) {\n\t\tprintf(\"Couldn't write/encode %s\\n\", argv[2]);\n\t\texit(1);\n\t}\n\n\tfree(pixels);\n\treturn 0;\n}\n"
  },
  {
    "path": "qoifuzz.c",
    "content": "/*\n\nCopyright (c) 2021, Dominic Szablewski - https://phoboslab.org\nSPDX-License-Identifier: MIT\n\n\nclang fuzzing harness for qoi_decode\n\nCompile and run with: \n\tclang -fsanitize=address,fuzzer -g -O0 qoifuzz.c && ./a.out\n\n*/\n\n\n#define QOI_IMPLEMENTATION\n#include \"qoi.h\"\n#include <stddef.h>\n#include <stdint.h>\n\nint LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {\n\tint w, h;\n\tif (size < 4) {\n\t\treturn 0;\n\t}\n\n\tqoi_desc desc;\n\tvoid* decoded = qoi_decode((void*)(data + 4), (int)(size - 4), &desc, *((int *)data));\n\tif (decoded != NULL) {\n\t\tfree(decoded);\n\t}\n\treturn 0;\n}\n"
  }
]