Codebase list qoi / 7ee42e1
Import Upstream version 0+git20220615+ds Gürkan Myczko authored 1 year, 10 months ago Gürkan Myczko committed 1 year, 10 months ago
7 changed file(s) with 1642 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 images/
1 stb_image.h
2 stb_image_write.h
3 qoibench
4 qoiconv
0 CC ?= gcc
1 CFLAGS_BENCH ?= -std=gnu99 -O3
2 LFLAGS_BENCH ?= -lpng
3 CFLAGS_CONV ?= -std=c99 -O3
4
5 TARGET_BENCH ?= qoibench
6 TARGET_CONV ?= qoiconv
7
8 all: $(TARGET_BENCH) $(TARGET_CONV)
9
10 bench: $(TARGET_BENCH)
11
12 $(TARGET_BENCH):$(TARGET_BENCH).c $(LFLAGS_BENCH)
13 $(CC) $(CFLAGS_BENCH) $(TARGET_BENCH).c -o $(TARGET_BENCH) $(LFLAGS_BENCH)
14
15 conv: $(TARGET_CONV)
16 $(TARGET_CONV):$(TARGET_CONV).c
17 $(CC) $(CFLAGS_CONV) $(TARGET_CONV).c -o $(TARGET_CONV)
18
19 .PHONY: clean
20 clean:
21 $(RM) $(TARGET_BENCH) $(TARGET_CONV)
0 ![QOI Logo](https://qoiformat.org/qoi-logo.svg)
1
2 # QOI - The “Quite OK Image Format” for fast, lossless image compression
3
4 Single-file MIT licensed library for C/C++
5
6 See [qoi.h](https://github.com/phoboslab/qoi/blob/master/qoi.h) for
7 the documentation and format specification.
8
9 More info at https://qoiformat.org
10
11
12 ## Why?
13
14 Compared to stb_image and stb_image_write QOI offers 20x-50x faster encoding,
15 3x-4x faster decoding and 20% better compression. It's also stupidly simple and
16 fits in about 300 lines of C.
17
18
19 ## Example Usage
20
21 - [qoiconv.c](https://github.com/phoboslab/qoi/blob/master/qoiconv.c)
22 converts between png <> qoi
23 - [qoibench.c](https://github.com/phoboslab/qoi/blob/master/qoibench.c)
24 a simple wrapper to benchmark stbi, libpng and qoi
25
26
27 ## Limitations
28
29 The QOI file format allows for huge images with up to 18 exa-pixels. A streaming
30 en-/decoder can handle these with minimal RAM requirements, assuming there is
31 enough storage space.
32
33 This particular implementation of QOI however is limited to images with a
34 maximum size of 400 million pixels. It will safely refuse to en-/decode anything
35 larger than that. This is not a streaming en-/decoder. It loads the whole image
36 file into RAM before doing any work and is not extensively optimized for
37 performance (but it's still very fast).
38
39 If this is a limitation for your use case, please look into any of the other
40 implementations listed below.
41
42
43 ## Improvements, New Versions and Contributing
44
45 The QOI format has been finalized. It was a conscious decision to **not** have a
46 version number in the file header. If you have a working QOI implementation today,
47 you can rest assured that it will be compatible with all QOI files tomorrow.
48
49 There are a lot of interesting ideas for a successor of QOI, but none of these will
50 be implemented here. That doesn't mean you shouldn't experiment with QOI, but please
51 be aware that pull requests that change the format will not be accepted.
52
53 Likewise, pull requests for performance improvements will probably not be accepted
54 either, as this "reference implementation" tries to be as easy to read as possible.
55
56
57 ## Tools
58
59 - https://github.com/floooh/qoiview - native QOI viewer
60 - https://github.com/pfusik/qoi-ci/releases/tag/qoi-ci-1.1.1 - QOI Plugin installer for GIMP, Imagine, Paint.NET and XnView MP
61 - https://github.com/iOrange/QoiFileTypeNet/releases/tag/v0.2 - QOI Plugin for Paint.NET
62 - https://github.com/iOrange/QOIThumbnailProvider - Add thumbnails for QOI images in Windows Explorer
63 - https://github.com/Tom94/tev - another native QOI viewer (allows pixel peeping and comparison with other image formats)
64 - https://apps.apple.com/br/app/qoiconverterx/id1602159820 QOI <=> PNG converter available on the Mac App Store
65 - https://github.com/kaetemi/qoi-max - QOI Bitmap I/O Plugin for 3ds Max
66 - https://raylibtech.itch.io/rtexviewer - texture viewer, supports QOI
67 - https://raylibtech.itch.io/rtexpacker - texture packer, supports QOI
68 - https://github.com/DmitriySalnikov/godot_qoi - QOI GDNative Addon for Godot Engine
69 - https://gitlab.com/dan9er/farbfeld-convert-qoi - QOI <=> farbfeld converter
70 - https://github.com/xiaozhuai/jetbrains-qoi - [QOI Support](https://plugins.jetbrains.com/plugin/19352-qoi-support) for Jetbrains' IDE.
71
72
73 ## Implementations & Bindings of QOI
74
75 - https://github.com/pfusik/qoi-ci (Ć, transpiled to C, C++, C#, Java, JavaScript, Python and Swift)
76 - https://github.com/kodonnell/qoi (Python)
77 - https://github.com/JaffaKetchup/dqoi (Dart, with Flutter support)
78 - https://github.com/Cr4xy/lua-qoi (Lua)
79 - https://github.com/superzazu/SDL_QOI (C, SDL2 bindings)
80 - https://github.com/saharNooby/qoi-java (Java)
81 - https://github.com/MasterQ32/zig-qoi (Zig)
82 - https://github.com/rbino/qoix (Elixir)
83 - https://github.com/NUlliiON/QoiSharp (C#)
84 - https://github.com/aldanor/qoi-rust (Rust)
85 - https://github.com/zakarumych/rapid-qoi (Rust)
86 - https://github.com/takeyourhatoff/qoi (Go)
87 - https://github.com/DosWorld/pasqoi (Pascal)
88 - https://github.com/elihwyma/Swift-QOI (Swift)
89 - https://github.com/xfmoulet/qoi (Go)
90 - https://erratique.ch/software/qoic (OCaml)
91 - https://github.com/arian/go-qoi (Go)
92 - https://github.com/kchapelier/qoijs (JavaScript)
93 - https://github.com/KristofferC/QOI.jl (Julia)
94 - https://github.com/shadowMitia/libqoi/ (C++)
95 - https://github.com/MKCG/php-qoi (PHP)
96 - https://github.com/LightHouseSoftware/qoiformats (D)
97 - https://github.com/mhoward540/qoi-nim (Nim)
98 - https://github.com/wx257osn2/qoixx (C++)
99 - https://github.com/Tiefseetauchner/lr-paint (Processing)
100 - https://github.com/amstan/qoi-fpga (FPGA: verilog)
101 - https://github.com/musabkilic/qoi-decoder (Python)
102 - https://github.com/mathpn/py-qoi (Python)
103 - https://github.com/JohannesFriedrich/qoi4R (R)
104 - https://github.com/shraiwi/mini-qoi (C, streaming decoder)
105 - https://github.com/10maurycy10/libqoi/ (Rust)
106 - https://github.com/0xd34df00d/hsqoi (Haskell)
107 - https://github.com/418Coffee/qoi-v (V)
108 - https://github.com/Imagine-Programming/QoiImagePlugin (PureBasic)
109 - https://github.com/Fabien-Chouteau/qoi-spark (Ada/SPARK formally proven)
110
111 ## QOI Support in Other Software
112
113 - [Amiga OS QOI datatype](https://github.com/dgaw/qoi-datatype) adds support for decoding QOI images to the Amiga operating system.
114 - [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/QOILoader.h)
115 - [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)
116 - [Rebol3](https://github.com/Oldes/Rebol3/issues/39) supports decoding and encoding QOI using a native codec
117 - [c-ray](https://github.com/vkoskiv/c-ray) supports QOI natively
118 - [SAIL](https://sail.software) image decoding library, supports decoding and encoding QOI images
119 - [Orx](https://github.com/orx/orx) 2D game engine, supports QOI natively
120 - [IrfanView](https://www.irfanview.com) supports decoding and encoding QOI through its Formats plugin
121 - [ImageMagick](https://github.com/ImageMagick/ImageMagick) supports decoding and encoding QOI, since 7.1.0-20
122 - [barebox](https://barebox.org) bootloader, supports decoding QOI images for splash logo, since v2022.03.0
123 - [KorGE](https://korge.org) & KorIM Kotlin 2D game engine and imaging library, supports decoding and encoding QOI natively since 2.7.0
124 - [DOjS](https://github.com/SuperIlu/DOjS) DOS JavaScript Canvas implementation supports loading QOI files
125 - [XnView MP](https://www.xnview.com/en/xnviewmp/) supports decoding QOI since 1.00
126
127 ## Packages
128
129 [AUR](https://aur.archlinux.org/pkgbase/qoi-git/) - system-wide qoi.h, qoiconv and qoibench install as split packages.
130
131
132 ## Implementations not yet conforming to the final specification
133
134 These implementations are based on the pre-release version of QOI. Resulting files are not compatible with the current version.
135
136 - https://github.com/ChevyRay/qoi_rs (Rust)
137 - https://github.com/panzi/jsqoi (TypeScript)
+669
-0
qoi.h less more
0 /*
1
2 QOI - The "Quite OK Image" format for fast, lossless image compression
3
4 Dominic Szablewski - https://phoboslab.org
5
6
7 -- LICENSE: The MIT License(MIT)
8
9 Copyright(c) 2021 Dominic Szablewski
10
11 Permission is hereby granted, free of charge, to any person obtaining a copy of
12 this software and associated documentation files(the "Software"), to deal in
13 the Software without restriction, including without limitation the rights to
14 use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
15 of the Software, and to permit persons to whom the Software is furnished to do
16 so, subject to the following conditions :
17 The above copyright notice and this permission notice shall be included in all
18 copies or substantial portions of the Software.
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
22 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 SOFTWARE.
26
27
28 -- About
29
30 QOI encodes and decodes images in a lossless format. Compared to stb_image and
31 stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
32 20% better compression.
33
34
35 -- Synopsis
36
37 // Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this
38 // library to create the implementation.
39
40 #define QOI_IMPLEMENTATION
41 #include "qoi.h"
42
43 // Encode and store an RGBA buffer to the file system. The qoi_desc describes
44 // the input pixel data.
45 qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
46 .width = 1920,
47 .height = 1080,
48 .channels = 4,
49 .colorspace = QOI_SRGB
50 });
51
52 // Load and decode a QOI image from the file system into a 32bbp RGBA buffer.
53 // The qoi_desc struct will be filled with the width, height, number of channels
54 // and colorspace read from the file header.
55 qoi_desc desc;
56 void *rgba_pixels = qoi_read("image.qoi", &desc, 4);
57
58
59
60 -- Documentation
61
62 This library provides the following functions;
63 - qoi_read -- read and decode a QOI file
64 - qoi_decode -- decode the raw bytes of a QOI image from memory
65 - qoi_write -- encode and write a QOI file
66 - qoi_encode -- encode an rgba buffer into a QOI image in memory
67
68 See the function declaration below for the signature and more information.
69
70 If you don't want/need the qoi_read and qoi_write functions, you can define
71 QOI_NO_STDIO before including this library.
72
73 This library uses malloc() and free(). To supply your own malloc implementation
74 you can define QOI_MALLOC and QOI_FREE before including this library.
75
76 This library uses memset() to zero-initialize the index. To supply your own
77 implementation you can define QOI_ZEROARR before including this library.
78
79
80 -- Data Format
81
82 A QOI file has a 14 byte header, followed by any number of data "chunks" and an
83 8-byte end marker.
84
85 struct qoi_header_t {
86 char magic[4]; // magic bytes "qoif"
87 uint32_t width; // image width in pixels (BE)
88 uint32_t height; // image height in pixels (BE)
89 uint8_t channels; // 3 = RGB, 4 = RGBA
90 uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
91 };
92
93 Images are encoded row by row, left to right, top to bottom. The decoder and
94 encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An
95 image is complete when all pixels specified by width * height have been covered.
96
97 Pixels are encoded as
98 - a run of the previous pixel
99 - an index into an array of previously seen pixels
100 - a difference to the previous pixel value in r,g,b
101 - full r,g,b or r,g,b,a values
102
103 The color channels are assumed to not be premultiplied with the alpha channel
104 ("un-premultiplied alpha").
105
106 A running array[64] (zero-initialized) of previously seen pixel values is
107 maintained by the encoder and decoder. Each pixel that is seen by the encoder
108 and decoder is put into this array at the position formed by a hash function of
109 the color value. In the encoder, if the pixel value at the index matches the
110 current pixel, this index position is written to the stream as QOI_OP_INDEX.
111 The hash function for the index is:
112
113 index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
114
115 Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
116 bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
117 values encoded in these data bits have the most significant bit on the left.
118
119 The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
120 presence of an 8-bit tag first.
121
122 The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte.
123
124
125 The possible chunks are:
126
127
128 .- QOI_OP_INDEX ----------.
129 | Byte[0] |
130 | 7 6 5 4 3 2 1 0 |
131 |-------+-----------------|
132 | 0 0 | index |
133 `-------------------------`
134 2-bit tag b00
135 6-bit index into the color index array: 0..63
136
137 A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the
138 same index. QOI_OP_RUN should be used instead.
139
140
141 .- QOI_OP_DIFF -----------.
142 | Byte[0] |
143 | 7 6 5 4 3 2 1 0 |
144 |-------+-----+-----+-----|
145 | 0 1 | dr | dg | db |
146 `-------------------------`
147 2-bit tag b01
148 2-bit red channel difference from the previous pixel between -2..1
149 2-bit green channel difference from the previous pixel between -2..1
150 2-bit blue channel difference from the previous pixel between -2..1
151
152 The difference to the current channel values are using a wraparound operation,
153 so "1 - 2" will result in 255, while "255 + 1" will result in 0.
154
155 Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
156 0 (b00). 1 is stored as 3 (b11).
157
158 The alpha value remains unchanged from the previous pixel.
159
160
161 .- QOI_OP_LUMA -------------------------------------.
162 | Byte[0] | Byte[1] |
163 | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 |
164 |-------+-----------------+-------------+-----------|
165 | 1 0 | green diff | dr - dg | db - dg |
166 `---------------------------------------------------`
167 2-bit tag b10
168 6-bit green channel difference from the previous pixel -32..31
169 4-bit red channel difference minus green channel difference -8..7
170 4-bit blue channel difference minus green channel difference -8..7
171
172 The green channel is used to indicate the general direction of change and is
173 encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
174 of the green channel difference and are encoded in 4 bits. I.e.:
175 dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
176 db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
177
178 The difference to the current channel values are using a wraparound operation,
179 so "10 - 13" will result in 253, while "250 + 7" will result in 1.
180
181 Values are stored as unsigned integers with a bias of 32 for the green channel
182 and a bias of 8 for the red and blue channel.
183
184 The alpha value remains unchanged from the previous pixel.
185
186
187 .- QOI_OP_RUN ------------.
188 | Byte[0] |
189 | 7 6 5 4 3 2 1 0 |
190 |-------+-----------------|
191 | 1 1 | run |
192 `-------------------------`
193 2-bit tag b11
194 6-bit run-length repeating the previous pixel: 1..62
195
196 The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
197 (b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
198 QOI_OP_RGBA tags.
199
200
201 .- QOI_OP_RGB ------------------------------------------.
202 | Byte[0] | Byte[1] | Byte[2] | Byte[3] |
203 | 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
204 |-------------------------+---------+---------+---------|
205 | 1 1 1 1 1 1 1 0 | red | green | blue |
206 `-------------------------------------------------------`
207 8-bit tag b11111110
208 8-bit red channel value
209 8-bit green channel value
210 8-bit blue channel value
211
212 The alpha value remains unchanged from the previous pixel.
213
214
215 .- QOI_OP_RGBA ---------------------------------------------------.
216 | Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
217 | 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
218 |-------------------------+---------+---------+---------+---------|
219 | 1 1 1 1 1 1 1 1 | red | green | blue | alpha |
220 `-----------------------------------------------------------------`
221 8-bit tag b11111111
222 8-bit red channel value
223 8-bit green channel value
224 8-bit blue channel value
225 8-bit alpha channel value
226
227 */
228
229
230 /* -----------------------------------------------------------------------------
231 Header - Public functions */
232
233 #ifndef QOI_H
234 #define QOI_H
235
236 #ifdef __cplusplus
237 extern "C" {
238 #endif
239
240 /* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
241 It describes either the input format (for qoi_write and qoi_encode), or is
242 filled with the description read from the file header (for qoi_read and
243 qoi_decode).
244
245 The colorspace in this qoi_desc is an enum where
246 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
247 1 = all channels are linear
248 You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
249 informative. It will be saved to the file header, but does not affect
250 how chunks are en-/decoded. */
251
252 #define QOI_SRGB 0
253 #define QOI_LINEAR 1
254
255 typedef struct {
256 unsigned int width;
257 unsigned int height;
258 unsigned char channels;
259 unsigned char colorspace;
260 } qoi_desc;
261
262 #ifndef QOI_NO_STDIO
263
264 /* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
265 system. The qoi_desc struct must be filled with the image width, height,
266 number of channels (3 = RGB, 4 = RGBA) and the colorspace.
267
268 The function returns 0 on failure (invalid parameters, or fopen or malloc
269 failed) or the number of bytes written on success. */
270
271 int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
272
273
274 /* Read and decode a QOI image from the file system. If channels is 0, the
275 number of channels from the file header is used. If channels is 3 or 4 the
276 output format will be forced into this number of channels.
277
278 The function either returns NULL on failure (invalid data, or malloc or fopen
279 failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
280 will be filled with the description from the file header.
281
282 The returned pixel data should be free()d after use. */
283
284 void *qoi_read(const char *filename, qoi_desc *desc, int channels);
285
286 #endif /* QOI_NO_STDIO */
287
288
289 /* Encode raw RGB or RGBA pixels into a QOI image in memory.
290
291 The function either returns NULL on failure (invalid parameters or malloc
292 failed) or a pointer to the encoded data on success. On success the out_len
293 is set to the size in bytes of the encoded data.
294
295 The returned qoi data should be free()d after use. */
296
297 void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
298
299
300 /* Decode a QOI image from memory.
301
302 The function either returns NULL on failure (invalid parameters or malloc
303 failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
304 is filled with the description from the file header.
305
306 The returned pixel data should be free()d after use. */
307
308 void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
309
310
311 #ifdef __cplusplus
312 }
313 #endif
314 #endif /* QOI_H */
315
316
317 /* -----------------------------------------------------------------------------
318 Implementation */
319
320 #ifdef QOI_IMPLEMENTATION
321 #include <stdlib.h>
322 #include <string.h>
323
324 #ifndef QOI_MALLOC
325 #define QOI_MALLOC(sz) malloc(sz)
326 #define QOI_FREE(p) free(p)
327 #endif
328 #ifndef QOI_ZEROARR
329 #define QOI_ZEROARR(a) memset((a),0,sizeof(a))
330 #endif
331
332 #define QOI_OP_INDEX 0x00 /* 00xxxxxx */
333 #define QOI_OP_DIFF 0x40 /* 01xxxxxx */
334 #define QOI_OP_LUMA 0x80 /* 10xxxxxx */
335 #define QOI_OP_RUN 0xc0 /* 11xxxxxx */
336 #define QOI_OP_RGB 0xfe /* 11111110 */
337 #define QOI_OP_RGBA 0xff /* 11111111 */
338
339 #define QOI_MASK_2 0xc0 /* 11000000 */
340
341 #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
342 #define QOI_MAGIC \
343 (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
344 ((unsigned int)'i') << 8 | ((unsigned int)'f'))
345 #define QOI_HEADER_SIZE 14
346
347 /* 2GB is the max file size that this implementation can safely handle. We guard
348 against anything larger than that, assuming the worst case with 5 bytes per
349 pixel, rounded down to a nice clean value. 400 million pixels ought to be
350 enough for anybody. */
351 #define QOI_PIXELS_MAX ((unsigned int)400000000)
352
353 typedef union {
354 struct { unsigned char r, g, b, a; } rgba;
355 unsigned int v;
356 } qoi_rgba_t;
357
358 static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
359
360 static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
361 bytes[(*p)++] = (0xff000000 & v) >> 24;
362 bytes[(*p)++] = (0x00ff0000 & v) >> 16;
363 bytes[(*p)++] = (0x0000ff00 & v) >> 8;
364 bytes[(*p)++] = (0x000000ff & v);
365 }
366
367 static unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
368 unsigned int a = bytes[(*p)++];
369 unsigned int b = bytes[(*p)++];
370 unsigned int c = bytes[(*p)++];
371 unsigned int d = bytes[(*p)++];
372 return a << 24 | b << 16 | c << 8 | d;
373 }
374
375 void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
376 int i, max_size, p, run;
377 int px_len, px_end, px_pos, channels;
378 unsigned char *bytes;
379 const unsigned char *pixels;
380 qoi_rgba_t index[64];
381 qoi_rgba_t px, px_prev;
382
383 if (
384 data == NULL || out_len == NULL || desc == NULL ||
385 desc->width == 0 || desc->height == 0 ||
386 desc->channels < 3 || desc->channels > 4 ||
387 desc->colorspace > 1 ||
388 desc->height >= QOI_PIXELS_MAX / desc->width
389 ) {
390 return NULL;
391 }
392
393 max_size =
394 desc->width * desc->height * (desc->channels + 1) +
395 QOI_HEADER_SIZE + sizeof(qoi_padding);
396
397 p = 0;
398 bytes = (unsigned char *) QOI_MALLOC(max_size);
399 if (!bytes) {
400 return NULL;
401 }
402
403 qoi_write_32(bytes, &p, QOI_MAGIC);
404 qoi_write_32(bytes, &p, desc->width);
405 qoi_write_32(bytes, &p, desc->height);
406 bytes[p++] = desc->channels;
407 bytes[p++] = desc->colorspace;
408
409
410 pixels = (const unsigned char *)data;
411
412 QOI_ZEROARR(index);
413
414 run = 0;
415 px_prev.rgba.r = 0;
416 px_prev.rgba.g = 0;
417 px_prev.rgba.b = 0;
418 px_prev.rgba.a = 255;
419 px = px_prev;
420
421 px_len = desc->width * desc->height * desc->channels;
422 px_end = px_len - desc->channels;
423 channels = desc->channels;
424
425 for (px_pos = 0; px_pos < px_len; px_pos += channels) {
426 px.rgba.r = pixels[px_pos + 0];
427 px.rgba.g = pixels[px_pos + 1];
428 px.rgba.b = pixels[px_pos + 2];
429
430 if (channels == 4) {
431 px.rgba.a = pixels[px_pos + 3];
432 }
433
434 if (px.v == px_prev.v) {
435 run++;
436 if (run == 62 || px_pos == px_end) {
437 bytes[p++] = QOI_OP_RUN | (run - 1);
438 run = 0;
439 }
440 }
441 else {
442 int index_pos;
443
444 if (run > 0) {
445 bytes[p++] = QOI_OP_RUN | (run - 1);
446 run = 0;
447 }
448
449 index_pos = QOI_COLOR_HASH(px) % 64;
450
451 if (index[index_pos].v == px.v) {
452 bytes[p++] = QOI_OP_INDEX | index_pos;
453 }
454 else {
455 index[index_pos] = px;
456
457 if (px.rgba.a == px_prev.rgba.a) {
458 signed char vr = px.rgba.r - px_prev.rgba.r;
459 signed char vg = px.rgba.g - px_prev.rgba.g;
460 signed char vb = px.rgba.b - px_prev.rgba.b;
461
462 signed char vg_r = vr - vg;
463 signed char vg_b = vb - vg;
464
465 if (
466 vr > -3 && vr < 2 &&
467 vg > -3 && vg < 2 &&
468 vb > -3 && vb < 2
469 ) {
470 bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
471 }
472 else if (
473 vg_r > -9 && vg_r < 8 &&
474 vg > -33 && vg < 32 &&
475 vg_b > -9 && vg_b < 8
476 ) {
477 bytes[p++] = QOI_OP_LUMA | (vg + 32);
478 bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8);
479 }
480 else {
481 bytes[p++] = QOI_OP_RGB;
482 bytes[p++] = px.rgba.r;
483 bytes[p++] = px.rgba.g;
484 bytes[p++] = px.rgba.b;
485 }
486 }
487 else {
488 bytes[p++] = QOI_OP_RGBA;
489 bytes[p++] = px.rgba.r;
490 bytes[p++] = px.rgba.g;
491 bytes[p++] = px.rgba.b;
492 bytes[p++] = px.rgba.a;
493 }
494 }
495 }
496 px_prev = px;
497 }
498
499 for (i = 0; i < (int)sizeof(qoi_padding); i++) {
500 bytes[p++] = qoi_padding[i];
501 }
502
503 *out_len = p;
504 return bytes;
505 }
506
507 void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
508 const unsigned char *bytes;
509 unsigned int header_magic;
510 unsigned char *pixels;
511 qoi_rgba_t index[64];
512 qoi_rgba_t px;
513 int px_len, chunks_len, px_pos;
514 int p = 0, run = 0;
515
516 if (
517 data == NULL || desc == NULL ||
518 (channels != 0 && channels != 3 && channels != 4) ||
519 size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
520 ) {
521 return NULL;
522 }
523
524 bytes = (const unsigned char *)data;
525
526 header_magic = qoi_read_32(bytes, &p);
527 desc->width = qoi_read_32(bytes, &p);
528 desc->height = qoi_read_32(bytes, &p);
529 desc->channels = bytes[p++];
530 desc->colorspace = bytes[p++];
531
532 if (
533 desc->width == 0 || desc->height == 0 ||
534 desc->channels < 3 || desc->channels > 4 ||
535 desc->colorspace > 1 ||
536 header_magic != QOI_MAGIC ||
537 desc->height >= QOI_PIXELS_MAX / desc->width
538 ) {
539 return NULL;
540 }
541
542 if (channels == 0) {
543 channels = desc->channels;
544 }
545
546 px_len = desc->width * desc->height * channels;
547 pixels = (unsigned char *) QOI_MALLOC(px_len);
548 if (!pixels) {
549 return NULL;
550 }
551
552 QOI_ZEROARR(index);
553 px.rgba.r = 0;
554 px.rgba.g = 0;
555 px.rgba.b = 0;
556 px.rgba.a = 255;
557
558 chunks_len = size - (int)sizeof(qoi_padding);
559 for (px_pos = 0; px_pos < px_len; px_pos += channels) {
560 if (run > 0) {
561 run--;
562 }
563 else if (p < chunks_len) {
564 int b1 = bytes[p++];
565
566 if (b1 == QOI_OP_RGB) {
567 px.rgba.r = bytes[p++];
568 px.rgba.g = bytes[p++];
569 px.rgba.b = bytes[p++];
570 }
571 else if (b1 == QOI_OP_RGBA) {
572 px.rgba.r = bytes[p++];
573 px.rgba.g = bytes[p++];
574 px.rgba.b = bytes[p++];
575 px.rgba.a = bytes[p++];
576 }
577 else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
578 px = index[b1];
579 }
580 else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
581 px.rgba.r += ((b1 >> 4) & 0x03) - 2;
582 px.rgba.g += ((b1 >> 2) & 0x03) - 2;
583 px.rgba.b += ( b1 & 0x03) - 2;
584 }
585 else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
586 int b2 = bytes[p++];
587 int vg = (b1 & 0x3f) - 32;
588 px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
589 px.rgba.g += vg;
590 px.rgba.b += vg - 8 + (b2 & 0x0f);
591 }
592 else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
593 run = (b1 & 0x3f);
594 }
595
596 index[QOI_COLOR_HASH(px) % 64] = px;
597 }
598
599 pixels[px_pos + 0] = px.rgba.r;
600 pixels[px_pos + 1] = px.rgba.g;
601 pixels[px_pos + 2] = px.rgba.b;
602
603 if (channels == 4) {
604 pixels[px_pos + 3] = px.rgba.a;
605 }
606 }
607
608 return pixels;
609 }
610
611 #ifndef QOI_NO_STDIO
612 #include <stdio.h>
613
614 int qoi_write(const char *filename, const void *data, const qoi_desc *desc) {
615 FILE *f = fopen(filename, "wb");
616 int size;
617 void *encoded;
618
619 if (!f) {
620 return 0;
621 }
622
623 encoded = qoi_encode(data, desc, &size);
624 if (!encoded) {
625 fclose(f);
626 return 0;
627 }
628
629 fwrite(encoded, 1, size, f);
630 fclose(f);
631
632 QOI_FREE(encoded);
633 return size;
634 }
635
636 void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
637 FILE *f = fopen(filename, "rb");
638 int size, bytes_read;
639 void *pixels, *data;
640
641 if (!f) {
642 return NULL;
643 }
644
645 fseek(f, 0, SEEK_END);
646 size = ftell(f);
647 if (size <= 0) {
648 fclose(f);
649 return NULL;
650 }
651 fseek(f, 0, SEEK_SET);
652
653 data = QOI_MALLOC(size);
654 if (!data) {
655 fclose(f);
656 return NULL;
657 }
658
659 bytes_read = fread(data, 1, size, f);
660 fclose(f);
661
662 pixels = qoi_decode(data, bytes_read, desc, channels);
663 QOI_FREE(data);
664 return pixels;
665 }
666
667 #endif /* QOI_NO_STDIO */
668 #endif /* QOI_IMPLEMENTATION */
0 /*
1
2 Simple benchmark suite for png, stbi and qoi
3
4 Requires libpng, "stb_image.h" and "stb_image_write.h"
5 Compile with:
6 gcc qoibench.c -std=gnu99 -lpng -O3 -o qoibench
7
8 Dominic Szablewski - https://phoboslab.org
9
10
11 -- LICENSE: The MIT License(MIT)
12
13 Copyright(c) 2021 Dominic Szablewski
14
15 Permission is hereby granted, free of charge, to any person obtaining a copy of
16 this software and associated documentation files(the "Software"), to deal in
17 the Software without restriction, including without limitation the rights to
18 use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
19 of the Software, and to permit persons to whom the Software is furnished to do
20 so, subject to the following conditions :
21 The above copyright notice and this permission notice shall be included in all
22 copies or substantial portions of the Software.
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30
31 */
32
33 #include <stdio.h>
34 #include <dirent.h>
35 #include <png.h>
36
37 #define STB_IMAGE_IMPLEMENTATION
38 #define STBI_ONLY_PNG
39 #define STBI_NO_LINEAR
40 #include "stb_image.h"
41
42 #define STB_IMAGE_WRITE_IMPLEMENTATION
43 #include "stb_image_write.h"
44
45 #define QOI_IMPLEMENTATION
46 #include "qoi.h"
47
48
49
50
51 // -----------------------------------------------------------------------------
52 // Cross platform high resolution timer
53 // From https://gist.github.com/ForeverZer0/0a4f80fc02b96e19380ebb7a3debbee5
54
55 #include <stdint.h>
56 #if defined(__linux)
57 #define HAVE_POSIX_TIMER
58 #include <time.h>
59 #ifdef CLOCK_MONOTONIC
60 #define CLOCKID CLOCK_MONOTONIC
61 #else
62 #define CLOCKID CLOCK_REALTIME
63 #endif
64 #elif defined(__APPLE__)
65 #define HAVE_MACH_TIMER
66 #include <mach/mach_time.h>
67 #elif defined(_WIN32)
68 #define WIN32_LEAN_AND_MEAN
69 #include <windows.h>
70 #endif
71
72 static uint64_t ns() {
73 static uint64_t is_init = 0;
74 #if defined(__APPLE__)
75 static mach_timebase_info_data_t info;
76 if (0 == is_init) {
77 mach_timebase_info(&info);
78 is_init = 1;
79 }
80 uint64_t now;
81 now = mach_absolute_time();
82 now *= info.numer;
83 now /= info.denom;
84 return now;
85 #elif defined(__linux)
86 static struct timespec linux_rate;
87 if (0 == is_init) {
88 clock_getres(CLOCKID, &linux_rate);
89 is_init = 1;
90 }
91 uint64_t now;
92 struct timespec spec;
93 clock_gettime(CLOCKID, &spec);
94 now = spec.tv_sec * 1.0e9 + spec.tv_nsec;
95 return now;
96 #elif defined(_WIN32)
97 static LARGE_INTEGER win_frequency;
98 if (0 == is_init) {
99 QueryPerformanceFrequency(&win_frequency);
100 is_init = 1;
101 }
102 LARGE_INTEGER now;
103 QueryPerformanceCounter(&now);
104 return (uint64_t) ((1e9 * now.QuadPart) / win_frequency.QuadPart);
105 #endif
106 }
107
108 #define STRINGIFY(x) #x
109 #define TOSTRING(x) STRINGIFY(x)
110 #define ERROR(...) printf("abort at line " TOSTRING(__LINE__) ": " __VA_ARGS__); printf("\n"); exit(1)
111
112
113 // -----------------------------------------------------------------------------
114 // libpng encode/decode wrappers
115 // Seriously, who thought this was a good abstraction for an API to read/write
116 // images?
117
118 typedef struct {
119 int size;
120 int capacity;
121 unsigned char *data;
122 } libpng_write_t;
123
124 void libpng_encode_callback(png_structp png_ptr, png_bytep data, png_size_t length) {
125 libpng_write_t *write_data = (libpng_write_t*)png_get_io_ptr(png_ptr);
126 if (write_data->size + length >= write_data->capacity) {
127 ERROR("PNG write");
128 }
129 memcpy(write_data->data + write_data->size, data, length);
130 write_data->size += length;
131 }
132
133 void *libpng_encode(void *pixels, int w, int h, int channels, int *out_len) {
134 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
135 if (!png) {
136 ERROR("png_create_write_struct");
137 }
138
139 png_infop info = png_create_info_struct(png);
140 if (!info) {
141 ERROR("png_create_info_struct");
142 }
143
144 if (setjmp(png_jmpbuf(png))) {
145 ERROR("png_jmpbuf");
146 }
147
148 // Output is 8bit depth, RGBA format.
149 png_set_IHDR(
150 png,
151 info,
152 w, h,
153 8,
154 channels == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA,
155 PNG_INTERLACE_NONE,
156 PNG_COMPRESSION_TYPE_DEFAULT,
157 PNG_FILTER_TYPE_DEFAULT
158 );
159
160 png_bytep row_pointers[h];
161 for(int y = 0; y < h; y++){
162 row_pointers[y] = ((unsigned char *)pixels + y * w * channels);
163 }
164
165 libpng_write_t write_data = {
166 .size = 0,
167 .capacity = w * h * channels,
168 .data = malloc(w * h * channels)
169 };
170
171 png_set_rows(png, info, row_pointers);
172 png_set_write_fn(png, &write_data, libpng_encode_callback, NULL);
173 png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
174
175 png_destroy_write_struct(&png, &info);
176
177 *out_len = write_data.size;
178 return write_data.data;
179 }
180
181
182 typedef struct {
183 int pos;
184 int size;
185 unsigned char *data;
186 } libpng_read_t;
187
188 void png_decode_callback(png_structp png, png_bytep data, png_size_t length) {
189 libpng_read_t *read_data = (libpng_read_t*)png_get_io_ptr(png);
190 if (read_data->pos + length > read_data->size) {
191 ERROR("PNG read %ld bytes at pos %d (size: %d)", length, read_data->pos, read_data->size);
192 }
193 memcpy(data, read_data->data + read_data->pos, length);
194 read_data->pos += length;
195 }
196
197 void png_warning_callback(png_structp png_ptr, png_const_charp warning_msg) {
198 // Ignore warnings about sRGB profiles and such.
199 }
200
201 void *libpng_decode(void *data, int size, int *out_w, int *out_h) {
202 png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, png_warning_callback);
203 if (!png) {
204 ERROR("png_create_read_struct");
205 }
206
207 png_infop info = png_create_info_struct(png);
208 if (!info) {
209 ERROR("png_create_info_struct");
210 }
211
212 libpng_read_t read_data = {
213 .pos = 0,
214 .size = size,
215 .data = data
216 };
217
218 png_set_read_fn(png, &read_data, png_decode_callback);
219 png_set_sig_bytes(png, 0);
220 png_read_info(png, info);
221
222 png_uint_32 w, h;
223 int bitDepth, colorType, interlaceType;
224 png_get_IHDR(png, info, &w, &h, &bitDepth, &colorType, &interlaceType, NULL, NULL);
225
226 // 16 bit -> 8 bit
227 png_set_strip_16(png);
228
229 // 1, 2, 4 bit -> 8 bit
230 if (bitDepth < 8) {
231 png_set_packing(png);
232 }
233
234 if (colorType & PNG_COLOR_MASK_PALETTE) {
235 png_set_expand(png);
236 }
237
238 if (!(colorType & PNG_COLOR_MASK_COLOR)) {
239 png_set_gray_to_rgb(png);
240 }
241
242 // set paletted or RGB images with transparency to full alpha so we get RGBA
243 if (png_get_valid(png, info, PNG_INFO_tRNS)) {
244 png_set_tRNS_to_alpha(png);
245 }
246
247 // make sure every pixel has an alpha value
248 if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
249 png_set_filler(png, 255, PNG_FILLER_AFTER);
250 }
251
252 png_read_update_info(png, info);
253
254 unsigned char* out = malloc(w * h * 4);
255 *out_w = w;
256 *out_h = h;
257
258 // png_uint_32 rowBytes = png_get_rowbytes(png, info);
259 png_bytep row_pointers[h];
260 for (png_uint_32 row = 0; row < h; row++ ) {
261 row_pointers[row] = (png_bytep)(out + (row * w * 4));
262 }
263
264 png_read_image(png, row_pointers);
265 png_read_end(png, info);
266 png_destroy_read_struct( &png, &info, NULL);
267
268 return out;
269 }
270
271
272 // -----------------------------------------------------------------------------
273 // stb_image encode callback
274
275 void stbi_write_callback(void *context, void *data, int size) {
276 int *encoded_size = (int *)context;
277 *encoded_size += size;
278 // In theory we'd need to do another malloc(), memcpy() and free() here to
279 // be fair to the other decode functions...
280 }
281
282
283 // -----------------------------------------------------------------------------
284 // function to load a whole file into memory
285
286 void *fload(const char *path, int *out_size) {
287 FILE *fh = fopen(path, "rb");
288 if (!fh) {
289 ERROR("Can't open file");
290 }
291
292 fseek(fh, 0, SEEK_END);
293 int size = ftell(fh);
294 fseek(fh, 0, SEEK_SET);
295
296 void *buffer = malloc(size);
297 if (!buffer) {
298 ERROR("Malloc for %d bytes failed", size);
299 }
300
301 if (!fread(buffer, size, 1, fh)) {
302 ERROR("Can't read file %s", path);
303 }
304 fclose(fh);
305
306 *out_size = size;
307 return buffer;
308 }
309
310
311 // -----------------------------------------------------------------------------
312 // benchmark runner
313
314
315 int opt_runs = 1;
316 int opt_nopng = 0;
317 int opt_nowarmup = 0;
318 int opt_noverify = 0;
319 int opt_nodecode = 0;
320 int opt_noencode = 0;
321 int opt_norecurse = 0;
322 int opt_onlytotals = 0;
323
324
325 typedef struct {
326 uint64_t size;
327 uint64_t encode_time;
328 uint64_t decode_time;
329 } benchmark_lib_result_t;
330
331 typedef struct {
332 int count;
333 uint64_t raw_size;
334 uint64_t px;
335 int w;
336 int h;
337 benchmark_lib_result_t libpng;
338 benchmark_lib_result_t stbi;
339 benchmark_lib_result_t qoi;
340 } benchmark_result_t;
341
342
343 void benchmark_print_result(benchmark_result_t res) {
344 res.px /= res.count;
345 res.raw_size /= res.count;
346 res.libpng.encode_time /= res.count;
347 res.libpng.decode_time /= res.count;
348 res.libpng.size /= res.count;
349 res.stbi.encode_time /= res.count;
350 res.stbi.decode_time /= res.count;
351 res.stbi.size /= res.count;
352 res.qoi.encode_time /= res.count;
353 res.qoi.decode_time /= res.count;
354 res.qoi.size /= res.count;
355
356 double px = res.px;
357 printf(" decode ms encode ms decode mpps encode mpps size kb rate\n");
358 if (!opt_nopng) {
359 printf(
360 "libpng: %8.1f %8.1f %8.2f %8.2f %8ld %4.1f%%\n",
361 (double)res.libpng.decode_time/1000000.0,
362 (double)res.libpng.encode_time/1000000.0,
363 (res.libpng.decode_time > 0 ? px / ((double)res.libpng.decode_time/1000.0) : 0),
364 (res.libpng.encode_time > 0 ? px / ((double)res.libpng.encode_time/1000.0) : 0),
365 res.libpng.size/1024,
366 ((double)res.libpng.size/(double)res.raw_size) * 100.0
367 );
368 printf(
369 "stbi: %8.1f %8.1f %8.2f %8.2f %8ld %4.1f%%\n",
370 (double)res.stbi.decode_time/1000000.0,
371 (double)res.stbi.encode_time/1000000.0,
372 (res.stbi.decode_time > 0 ? px / ((double)res.stbi.decode_time/1000.0) : 0),
373 (res.stbi.encode_time > 0 ? px / ((double)res.stbi.encode_time/1000.0) : 0),
374 res.stbi.size/1024,
375 ((double)res.stbi.size/(double)res.raw_size) * 100.0
376 );
377 }
378 printf(
379 "qoi: %8.1f %8.1f %8.2f %8.2f %8ld %4.1f%%\n",
380 (double)res.qoi.decode_time/1000000.0,
381 (double)res.qoi.encode_time/1000000.0,
382 (res.qoi.decode_time > 0 ? px / ((double)res.qoi.decode_time/1000.0) : 0),
383 (res.qoi.encode_time > 0 ? px / ((double)res.qoi.encode_time/1000.0) : 0),
384 res.qoi.size/1024,
385 ((double)res.qoi.size/(double)res.raw_size) * 100.0
386 );
387 printf("\n");
388 }
389
390 // Run __VA_ARGS__ a number of times and measure the time taken. The first
391 // run is ignored.
392 #define BENCHMARK_FN(NOWARMUP, RUNS, AVG_TIME, ...) \
393 do { \
394 uint64_t time = 0; \
395 for (int i = NOWARMUP; i <= RUNS; i++) { \
396 uint64_t time_start = ns(); \
397 __VA_ARGS__ \
398 uint64_t time_end = ns(); \
399 if (i > 0) { \
400 time += time_end - time_start; \
401 } \
402 } \
403 AVG_TIME = time / RUNS; \
404 } while (0)
405
406
407 benchmark_result_t benchmark_image(const char *path) {
408 int encoded_png_size;
409 int encoded_qoi_size;
410 int w;
411 int h;
412 int channels;
413
414 // Load the encoded PNG, encoded QOI and raw pixels into memory
415 if(!stbi_info(path, &w, &h, &channels)) {
416 ERROR("Error decoding header %s", path);
417 }
418
419 if (channels != 3) {
420 channels = 4;
421 }
422
423 void *pixels = (void *)stbi_load(path, &w, &h, NULL, channels);
424 void *encoded_png = fload(path, &encoded_png_size);
425 void *encoded_qoi = qoi_encode(pixels, &(qoi_desc){
426 .width = w,
427 .height = h,
428 .channels = channels,
429 .colorspace = QOI_SRGB
430 }, &encoded_qoi_size);
431
432 if (!pixels || !encoded_qoi || !encoded_png) {
433 ERROR("Error decoding %s", path);
434 }
435
436 // Verify QOI Output
437
438 if (!opt_noverify) {
439 qoi_desc dc;
440 void *pixels_qoi = qoi_decode(encoded_qoi, encoded_qoi_size, &dc, channels);
441 if (memcmp(pixels, pixels_qoi, w * h * channels) != 0) {
442 ERROR("QOI roundtrip pixel mismatch for %s", path);
443 }
444 free(pixels_qoi);
445 }
446
447
448
449 benchmark_result_t res = {0};
450 res.count = 1;
451 res.raw_size = w * h * channels;
452 res.px = w * h;
453 res.w = w;
454 res.h = h;
455
456
457 // Decoding
458
459 if (!opt_nodecode) {
460 if (!opt_nopng) {
461 BENCHMARK_FN(opt_nowarmup, opt_runs, res.libpng.decode_time, {
462 int dec_w, dec_h;
463 void *dec_p = libpng_decode(encoded_png, encoded_png_size, &dec_w, &dec_h);
464 free(dec_p);
465 });
466
467 BENCHMARK_FN(opt_nowarmup, opt_runs, res.stbi.decode_time, {
468 int dec_w, dec_h, dec_channels;
469 void *dec_p = stbi_load_from_memory(encoded_png, encoded_png_size, &dec_w, &dec_h, &dec_channels, 4);
470 free(dec_p);
471 });
472 }
473
474 BENCHMARK_FN(opt_nowarmup, opt_runs, res.qoi.decode_time, {
475 qoi_desc desc;
476 void *dec_p = qoi_decode(encoded_qoi, encoded_qoi_size, &desc, 4);
477 free(dec_p);
478 });
479 }
480
481
482 // Encoding
483 if (!opt_noencode) {
484 if (!opt_nopng) {
485 BENCHMARK_FN(opt_nowarmup, opt_runs, res.libpng.encode_time, {
486 int enc_size;
487 void *enc_p = libpng_encode(pixels, w, h, channels, &enc_size);
488 res.libpng.size = enc_size;
489 free(enc_p);
490 });
491
492 BENCHMARK_FN(opt_nowarmup, opt_runs, res.stbi.encode_time, {
493 int enc_size = 0;
494 stbi_write_png_to_func(stbi_write_callback, &enc_size, w, h, channels, pixels, 0);
495 res.stbi.size = enc_size;
496 });
497 }
498
499 BENCHMARK_FN(opt_nowarmup, opt_runs, res.qoi.encode_time, {
500 int enc_size;
501 void *enc_p = qoi_encode(pixels, &(qoi_desc){
502 .width = w,
503 .height = h,
504 .channels = channels,
505 .colorspace = QOI_SRGB
506 }, &enc_size);
507 res.qoi.size = enc_size;
508 free(enc_p);
509 });
510 }
511
512 free(pixels);
513 free(encoded_png);
514 free(encoded_qoi);
515
516 return res;
517 }
518
519 void benchmark_directory(const char *path, benchmark_result_t *grand_total) {
520 DIR *dir = opendir(path);
521 if (!dir) {
522 ERROR("Couldn't open directory %s", path);
523 }
524
525 struct dirent *file;
526
527 if (!opt_norecurse) {
528 for (int i = 0; (file = readdir(dir)) != NULL; i++) {
529 if (
530 file->d_type & DT_DIR &&
531 strcmp(file->d_name, ".") != 0 &&
532 strcmp(file->d_name, "..") != 0
533 ) {
534 char subpath[1024];
535 snprintf(subpath, 1024, "%s/%s", path, file->d_name);
536 benchmark_directory(subpath, grand_total);
537 }
538 }
539 rewinddir(dir);
540 }
541
542 benchmark_result_t dir_total = {0};
543
544 int has_shown_head = 0;
545 for (int i = 0; (file = readdir(dir)) != NULL; i++) {
546 if (strcmp(file->d_name + strlen(file->d_name) - 4, ".png") != 0) {
547 continue;
548 }
549
550 if (!has_shown_head) {
551 has_shown_head = 1;
552 printf("## Benchmarking %s/*.png -- %d runs\n\n", path, opt_runs);
553 }
554
555 char *file_path = malloc(strlen(file->d_name) + strlen(path)+8);
556 sprintf(file_path, "%s/%s", path, file->d_name);
557
558 benchmark_result_t res = benchmark_image(file_path);
559
560 if (!opt_onlytotals) {
561 printf("## %s size: %dx%d\n", file_path, res.w, res.h);
562 benchmark_print_result(res);
563 }
564
565 free(file_path);
566
567 dir_total.count++;
568 dir_total.raw_size += res.raw_size;
569 dir_total.px += res.px;
570 dir_total.libpng.encode_time += res.libpng.encode_time;
571 dir_total.libpng.decode_time += res.libpng.decode_time;
572 dir_total.libpng.size += res.libpng.size;
573 dir_total.stbi.encode_time += res.stbi.encode_time;
574 dir_total.stbi.decode_time += res.stbi.decode_time;
575 dir_total.stbi.size += res.stbi.size;
576 dir_total.qoi.encode_time += res.qoi.encode_time;
577 dir_total.qoi.decode_time += res.qoi.decode_time;
578 dir_total.qoi.size += res.qoi.size;
579
580 grand_total->count++;
581 grand_total->raw_size += res.raw_size;
582 grand_total->px += res.px;
583 grand_total->libpng.encode_time += res.libpng.encode_time;
584 grand_total->libpng.decode_time += res.libpng.decode_time;
585 grand_total->libpng.size += res.libpng.size;
586 grand_total->stbi.encode_time += res.stbi.encode_time;
587 grand_total->stbi.decode_time += res.stbi.decode_time;
588 grand_total->stbi.size += res.stbi.size;
589 grand_total->qoi.encode_time += res.qoi.encode_time;
590 grand_total->qoi.decode_time += res.qoi.decode_time;
591 grand_total->qoi.size += res.qoi.size;
592 }
593 closedir(dir);
594
595 if (dir_total.count > 0) {
596 printf("## Total for %s\n", path);
597 benchmark_print_result(dir_total);
598 }
599 }
600
601 int main(int argc, char **argv) {
602 if (argc < 3) {
603 printf("Usage: qoibench <iterations> <directory> [options]\n");
604 printf("Options:\n");
605 printf(" --nowarmup ... don't perform a warmup run\n");
606 printf(" --nopng ...... don't run png encode/decode\n");
607 printf(" --noverify ... don't verify qoi roundtrip\n");
608 printf(" --noencode ... don't run encoders\n");
609 printf(" --nodecode ... don't run decoders\n");
610 printf(" --norecurse .. don't descend into directories\n");
611 printf(" --onlytotals . don't print individual image results\n");
612 printf("Examples\n");
613 printf(" qoibench 10 images/textures/\n");
614 printf(" qoibench 1 images/textures/ --nopng --nowarmup\n");
615 exit(1);
616 }
617
618 for (int i = 3; i < argc; i++) {
619 if (strcmp(argv[i], "--nowarmup") == 0) { opt_nowarmup = 1; }
620 else if (strcmp(argv[i], "--nopng") == 0) { opt_nopng = 1; }
621 else if (strcmp(argv[i], "--noverify") == 0) { opt_noverify = 1; }
622 else if (strcmp(argv[i], "--noencode") == 0) { opt_noencode = 1; }
623 else if (strcmp(argv[i], "--nodecode") == 0) { opt_nodecode = 1; }
624 else if (strcmp(argv[i], "--norecurse") == 0) { opt_norecurse = 1; }
625 else if (strcmp(argv[i], "--onlytotals") == 0) { opt_onlytotals = 1; }
626 else { ERROR("Unknown option %s", argv[i]); }
627 }
628
629 opt_runs = atoi(argv[1]);
630 if (opt_runs <=0) {
631 ERROR("Invalid number of runs %d", opt_runs);
632 }
633
634 benchmark_result_t grand_total = {0};
635 benchmark_directory(argv[2], &grand_total);
636
637 if (grand_total.count > 0) {
638 printf("# Grand total for %s\n", argv[2]);
639 benchmark_print_result(grand_total);
640 }
641 else {
642 printf("No images found in %s\n", argv[2]);
643 }
644
645 return 0;
646 }
0 /*
1
2 Command line tool to convert between png <> qoi format
3
4 Requires:
5 -"stb_image.h" (https://github.com/nothings/stb/blob/master/stb_image.h)
6 -"stb_image_write.h" (https://github.com/nothings/stb/blob/master/stb_image_write.h)
7 -"qoi.h" (https://github.com/phoboslab/qoi/blob/master/qoi.h)
8
9 Compile with:
10 gcc qoiconv.c -std=c99 -O3 -o qoiconv
11
12 Dominic Szablewski - https://phoboslab.org
13
14
15 -- LICENSE: The MIT License(MIT)
16
17 Copyright(c) 2021 Dominic Szablewski
18
19 Permission is hereby granted, free of charge, to any person obtaining a copy of
20 this software and associated documentation files(the "Software"), to deal in
21 the Software without restriction, including without limitation the rights to
22 use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
23 of the Software, and to permit persons to whom the Software is furnished to do
24 so, subject to the following conditions :
25 The above copyright notice and this permission notice shall be included in all
26 copies or substantial portions of the Software.
27 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
30 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 SOFTWARE.
34
35 */
36
37
38 #define STB_IMAGE_IMPLEMENTATION
39 #define STBI_ONLY_PNG
40 #define STBI_NO_LINEAR
41 #include "stb_image.h"
42
43 #define STB_IMAGE_WRITE_IMPLEMENTATION
44 #include "stb_image_write.h"
45
46 #define QOI_IMPLEMENTATION
47 #include "qoi.h"
48
49
50 #define STR_ENDS_WITH(S, E) (strcmp(S + strlen(S) - (sizeof(E)-1), E) == 0)
51
52 int main(int argc, char **argv) {
53 if (argc < 3) {
54 puts("Usage: qoiconv <infile> <outfile>");
55 puts("Examples:");
56 puts(" qoiconv input.png output.qoi");
57 puts(" qoiconv input.qoi output.png");
58 exit(1);
59 }
60
61 void *pixels = NULL;
62 int w, h, channels;
63 if (STR_ENDS_WITH(argv[1], ".png")) {
64 if(!stbi_info(argv[1], &w, &h, &channels)) {
65 printf("Couldn't read header %s\n", argv[1]);
66 exit(1);
67 }
68
69 // Force all odd encodings to be RGBA
70 if(channels != 3) {
71 channels = 4;
72 }
73
74 pixels = (void *)stbi_load(argv[1], &w, &h, NULL, channels);
75 }
76 else if (STR_ENDS_WITH(argv[1], ".qoi")) {
77 qoi_desc desc;
78 pixels = qoi_read(argv[1], &desc, 0);
79 channels = desc.channels;
80 w = desc.width;
81 h = desc.height;
82 }
83
84 if (pixels == NULL) {
85 printf("Couldn't load/decode %s\n", argv[1]);
86 exit(1);
87 }
88
89 int encoded = 0;
90 if (STR_ENDS_WITH(argv[2], ".png")) {
91 encoded = stbi_write_png(argv[2], w, h, channels, pixels, 0);
92 }
93 else if (STR_ENDS_WITH(argv[2], ".qoi")) {
94 encoded = qoi_write(argv[2], pixels, &(qoi_desc){
95 .width = w,
96 .height = h,
97 .channels = channels,
98 .colorspace = QOI_SRGB
99 });
100 }
101
102 if (!encoded) {
103 printf("Couldn't write/encode %s\n", argv[2]);
104 exit(1);
105 }
106
107 free(pixels);
108 return 0;
109 }
0 /*
1
2 clang fuzzing harness for qoi_decode
3
4 Compile and run with:
5 clang -fsanitize=address,fuzzer -g -O0 qoifuzz.c && ./a.out
6
7 Dominic Szablewski - https://phoboslab.org
8
9
10 -- LICENSE: The MIT License(MIT)
11
12 Copyright(c) 2021 Dominic Szablewski
13
14 Permission is hereby granted, free of charge, to any person obtaining a copy of
15 this software and associated documentation files(the "Software"), to deal in
16 the Software without restriction, including without limitation the rights to
17 use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
18 of the Software, and to permit persons to whom the Software is furnished to do
19 so, subject to the following conditions :
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29
30 */
31
32
33 #define QOI_IMPLEMENTATION
34 #include "qoi.h"
35 #include <stddef.h>
36 #include <stdint.h>
37
38 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
39 int w, h;
40 if (size < 4) {
41 return 0;
42 }
43
44 qoi_desc desc;
45 void* decoded = qoi_decode((void*)(data + 4), (int)(size - 4), &desc, *((int *)data));
46 if (decoded != NULL) {
47 free(decoded);
48 }
49 return 0;
50 }