Import Upstream version 0+git20220615+ds
Gürkan Myczko authored 1 year, 10 months ago
Gürkan Myczko committed 1 year, 10 months ago
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) |
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 | } |