1 module gamut.codecs.qoi; 2 3 /* 4 5 QOI - The "Quite OK Image" format for fast, lossless image compression 6 7 Dominic Szablewski - https://phoboslab.org 8 9 10 -- LICENSE: The MIT License(MIT) 11 12 Copyright(c) 2021 Dominic Szablewski 13 Copyright(c) 2022 Guillaume Piolat (D translation) 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 -- About 33 34 QOI encodes and decodes images in a lossless format. Compared to stb_image and 35 stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and 36 20% better compression. 37 38 39 -- Documentation 40 41 This library provides the following functions; 42 - qoi_decode -- decode the raw bytes of a QOI image from memory 43 - qoi_encode -- encode an rgba buffer into a QOI image in memory 44 45 46 -- Data Format 47 48 A QOI file has a 14 byte header, followed by any number of data "chunks" and an 49 8-byte end marker. 50 51 struct qoi_header_t { 52 char magic[4]; // magic bytes "qoif" 53 uint32_t width; // image width in pixels (BE) 54 uint32_t height; // image height in pixels (BE) 55 uint8_t channels; // 3 = RGB, 4 = RGBA 56 uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear 57 }; 58 59 Images are encoded row by row, left to right, top to bottom. The decoder and 60 encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An 61 image is complete when all pixels specified by width * height have been covered. 62 63 Pixels are encoded as 64 - a run of the previous pixel 65 - an index into an array of previously seen pixels 66 - a difference to the previous pixel value in r,g,b 67 - full r,g,b or r,g,b,a values 68 69 The color channels are assumed to not be premultiplied with the alpha channel 70 ("un-premultiplied alpha"). 71 72 A running array[64] (zero-initialized) of previously seen pixel values is 73 maintained by the encoder and decoder. Each pixel that is seen by the encoder 74 and decoder is put into this array at the position formed by a hash function of 75 the color value. In the encoder, if the pixel value at the index matches the 76 current pixel, this index position is written to the stream as QOI_OP_INDEX. 77 The hash function for the index is: 78 79 index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 80 81 Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The 82 bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All 83 values encoded in these data bits have the most significant bit on the left. 84 85 The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the 86 presence of an 8-bit tag first. 87 88 The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. 89 90 91 The possible chunks are: 92 93 94 .- QOI_OP_INDEX ----------. 95 | Byte[0] | 96 | 7 6 5 4 3 2 1 0 | 97 |-------+-----------------| 98 | 0 0 | index | 99 `-------------------------` 100 2-bit tag b00 101 6-bit index into the color index array: 0..63 102 103 A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the 104 same index. QOI_OP_RUN should be used instead. 105 106 107 .- QOI_OP_DIFF -----------. 108 | Byte[0] | 109 | 7 6 5 4 3 2 1 0 | 110 |-------+-----+-----+-----| 111 | 0 1 | dr | dg | db | 112 `-------------------------` 113 2-bit tag b01 114 2-bit red channel difference from the previous pixel between -2..1 115 2-bit green channel difference from the previous pixel between -2..1 116 2-bit blue channel difference from the previous pixel between -2..1 117 118 The difference to the current channel values are using a wraparound operation, 119 so "1 - 2" will result in 255, while "255 + 1" will result in 0. 120 121 Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as 122 0 (b00). 1 is stored as 3 (b11). 123 124 The alpha value remains unchanged from the previous pixel. 125 126 127 .- QOI_OP_LUMA -------------------------------------. 128 | Byte[0] | Byte[1] | 129 | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 130 |-------+-----------------+-------------+-----------| 131 | 1 0 | green diff | dr - dg | db - dg | 132 `---------------------------------------------------` 133 2-bit tag b10 134 6-bit green channel difference from the previous pixel -32..31 135 4-bit red channel difference minus green channel difference -8..7 136 4-bit blue channel difference minus green channel difference -8..7 137 138 The green channel is used to indicate the general direction of change and is 139 encoded in 6 bits. The red and blue channels (dr and db) base their diffs off 140 of the green channel difference and are encoded in 4 bits. I.e.: 141 dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) 142 db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) 143 144 The difference to the current channel values are using a wraparound operation, 145 so "10 - 13" will result in 253, while "250 + 7" will result in 1. 146 147 Values are stored as unsigned integers with a bias of 32 for the green channel 148 and a bias of 8 for the red and blue channel. 149 150 The alpha value remains unchanged from the previous pixel. 151 152 153 .- QOI_OP_RUN ------------. 154 | Byte[0] | 155 | 7 6 5 4 3 2 1 0 | 156 |-------+-----------------| 157 | 1 1 | run | 158 `-------------------------` 159 2-bit tag b11 160 6-bit run-length repeating the previous pixel: 1..62 161 162 The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 163 (b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and 164 QOI_OP_RGBA tags. 165 166 167 .- QOI_OP_RGB ------------------------------------------. 168 | Byte[0] | Byte[1] | Byte[2] | Byte[3] | 169 | 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 170 |-------------------------+---------+---------+---------| 171 | 1 1 1 1 1 1 1 0 | red | green | blue | 172 `-------------------------------------------------------` 173 8-bit tag b11111110 174 8-bit red channel value 175 8-bit green channel value 176 8-bit blue channel value 177 178 The alpha value remains unchanged from the previous pixel. 179 180 181 .- QOI_OP_RGBA ---------------------------------------------------. 182 | Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | 183 | 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 184 |-------------------------+---------+---------+---------+---------| 185 | 1 1 1 1 1 1 1 1 | red | green | blue | alpha | 186 `-----------------------------------------------------------------` 187 8-bit tag b11111111 188 8-bit red channel value 189 8-bit green channel value 190 8-bit blue channel value 191 8-bit alpha channel value 192 193 */ 194 195 196 /* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions. 197 It describes either the input format (for qoi_write and qoi_encode), or is 198 filled with the description read from the file header (for qoi_read and 199 qoi_decode). 200 201 The colorspace in this qoi_desc is an enum where 202 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel 203 1 = all channels are linear 204 You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely 205 informative. It will be saved to the file header, but does not affect 206 how chunks are en-/decoded. */ 207 208 import core.stdc.string: memset; 209 import core.stdc.stdlib: malloc, free; 210 211 nothrow @nogc: 212 213 enum QOI_SRGB = 0; 214 enum QOI_LINEAR = 1; 215 216 struct qoi_desc 217 { 218 uint width; 219 uint height; 220 int pitchBytes; // number of bytes between start of lines. 221 ubyte channels; 222 ubyte colorspace; 223 } 224 225 226 227 alias QOI_MALLOC = malloc; 228 alias QOI_FREE = free; 229 230 enum int QOI_OP_INDEX = 0x00; /* 00xxxxxx */ 231 enum int QOI_OP_DIFF = 0x40; /* 01xxxxxx */ 232 enum int QOI_OP_LUMA = 0x80; /* 10xxxxxx */ 233 enum int QOI_OP_RUN = 0xc0; /* 11xxxxxx */ 234 enum int QOI_OP_RGB = 0xfe; /* 11111110 */ 235 enum int QOI_OP_RGBA = 0xff; /* 11111111 */ 236 237 enum int QOI_MASK_2 = 0xc0; /* 11000000 */ 238 239 int QOI_COLOR_HASH(qoi_rgba_t C) 240 { 241 return (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11); 242 } 243 244 enum uint QOI_MAGIC = 0x716F6966; // "qoif" 245 enum QOI_HEADER_SIZE = 14; 246 247 /* 2GB is the max file size that this implementation can safely handle. We guard 248 against anything larger than that, assuming the worst case with 5 bytes per 249 pixel, rounded down to a nice clean value. 400 million pixels ought to be 250 enough for anybody. */ 251 enum uint QOI_PIXELS_MAX = 400000000; 252 253 struct RGBA 254 { 255 ubyte r, g, b, a; 256 } 257 static assert(RGBA.sizeof == 4); 258 259 struct qoi_rgba_t 260 { 261 union 262 { 263 RGBA rgba; 264 uint v; 265 } 266 } 267 268 static immutable ubyte[8] qoi_padding = [0,0,0,0,0,0,0,1]; 269 270 void qoi_write_32(ubyte* bytes, int *p, uint v) 271 { 272 bytes[(*p)++] = (0xff000000 & v) >> 24; 273 bytes[(*p)++] = (0x00ff0000 & v) >> 16; 274 bytes[(*p)++] = (0x0000ff00 & v) >> 8; 275 bytes[(*p)++] = (0x000000ff & v); 276 } 277 278 uint qoi_read_32(const(ubyte)* bytes, int *p) 279 { 280 uint a = bytes[(*p)++]; 281 uint b = bytes[(*p)++]; 282 uint c = bytes[(*p)++]; 283 uint d = bytes[(*p)++]; 284 return a << 24 | b << 16 | c << 8 | d; 285 } 286 287 /* Encode raw RGB or RGBA pixels into a QOI image in memory. 288 289 The function either returns null on failure (invalid parameters or malloc 290 failed) or a pointer to the encoded data on success. On success the out_len 291 is set to the size in bytes of the encoded data. 292 293 The returned qoi data should be free()d after use. */ 294 version(encodeQOI) 295 void *qoi_encode(const(ubyte)* data, const(qoi_desc)* desc, int *out_len) 296 { 297 int i, max_size, p, run; 298 int px_len, px_end, px_pos, channels; 299 ubyte *bytes; 300 qoi_rgba_t[64] index; 301 qoi_rgba_t px, px_prev; 302 303 if ( 304 data == null || out_len == null || desc == null || 305 desc.width == 0 || desc.height == 0 || 306 desc.channels < 3 || desc.channels > 4 || 307 desc.colorspace > 1 || 308 desc.height >= QOI_PIXELS_MAX / desc.width 309 ) { 310 return null; 311 } 312 313 max_size = 314 desc.width * desc.height * (desc.channels + 1) + 315 QOI_HEADER_SIZE + cast(int)qoi_padding.sizeof; 316 317 p = 0; 318 bytes = cast(ubyte *) QOI_MALLOC(max_size); 319 if (!bytes) 320 { 321 return null; 322 } 323 324 qoi_write_32(bytes, &p, QOI_MAGIC); 325 qoi_write_32(bytes, &p, desc.width); 326 qoi_write_32(bytes, &p, desc.height); 327 bytes[p++] = desc.channels; 328 bytes[p++] = desc.colorspace; 329 330 memset(index.ptr, 0, 64 * qoi_rgba_t.sizeof); 331 332 run = 0; 333 px_prev.rgba.r = 0; 334 px_prev.rgba.g = 0; 335 px_prev.rgba.b = 0; 336 px_prev.rgba.a = 255; 337 px = px_prev; 338 339 px_len = desc.width * desc.height * desc.channels; 340 px_end = px_len - desc.channels; 341 channels = desc.channels; 342 343 for (int posy = 0; posy < desc.height; ++posy) 344 { 345 const(ubyte)* line = data + desc.pitchBytes * posy; 346 347 for (int posx = 0; posx < desc.width; ++posx) 348 { 349 if (channels == 4) 350 { 351 px = *cast(qoi_rgba_t *)(&line[posx * 4]); 352 } 353 else { 354 px.rgba.r = line[posx * 3 + 0]; 355 px.rgba.g = line[posx * 3 + 1]; 356 px.rgba.b = line[posx * 3 + 2]; 357 } 358 359 if (px.v == px_prev.v) { 360 run++; 361 if (run == 62 || px_pos == px_end) { 362 bytes[p++] = cast(ubyte)(QOI_OP_RUN | (run - 1)); 363 run = 0; 364 } 365 } 366 else { 367 int index_pos; 368 369 if (run > 0) { 370 bytes[p++] = cast(ubyte)(QOI_OP_RUN | (run - 1)); 371 run = 0; 372 } 373 374 index_pos = QOI_COLOR_HASH(px) % 64; 375 376 if (index[index_pos].v == px.v) { 377 bytes[p++] = cast(ubyte)(QOI_OP_INDEX | index_pos); 378 } 379 else { 380 index[index_pos] = px; 381 382 if (px.rgba.a == px_prev.rgba.a) { 383 byte vr = cast(byte)(px.rgba.r - px_prev.rgba.r); 384 byte vg = cast(byte)(px.rgba.g - px_prev.rgba.g); 385 byte vb = cast(byte)(px.rgba.b - px_prev.rgba.b); 386 byte vg_r = cast(byte)(vr - vg); 387 byte vg_b = cast(byte)(vb - vg); 388 389 if ( 390 vr > -3 && vr < 2 && 391 vg > -3 && vg < 2 && 392 vb > -3 && vb < 2 393 ) { 394 bytes[p++] = cast(ubyte)(QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2)); 395 } 396 else if ( 397 vg_r > -9 && vg_r < 8 && 398 vg > -33 && vg < 32 && 399 vg_b > -9 && vg_b < 8 400 ) { 401 bytes[p++] = cast(ubyte)(QOI_OP_LUMA | (vg + 32)); 402 bytes[p++] = cast(ubyte)( (vg_r + 8) << 4 | (vg_b + 8) ); 403 } 404 else { 405 bytes[p++] = QOI_OP_RGB; 406 bytes[p++] = px.rgba.r; 407 bytes[p++] = px.rgba.g; 408 bytes[p++] = px.rgba.b; 409 } 410 } 411 else { 412 bytes[p++] = QOI_OP_RGBA; 413 bytes[p++] = px.rgba.r; 414 bytes[p++] = px.rgba.g; 415 bytes[p++] = px.rgba.b; 416 bytes[p++] = px.rgba.a; 417 } 418 } 419 } 420 px_prev = px; 421 px_pos += channels; 422 } 423 } 424 425 assert(px_pos == px_len); 426 427 for (i = 0; i < cast(int)(qoi_padding.length); i++) 428 { 429 bytes[p++] = qoi_padding[i]; 430 } 431 432 // PERF, realloc here to take less memory 433 // Check encoding speed reg in QOIX example 434 435 *out_len = p; 436 return bytes; 437 } 438 439 440 /** Decode a QOI image from memory. 441 442 The function either returns null on failure (invalid parameters or malloc 443 failed) or a pointer to the decoded pixels. On success, the qoi_desc struct 444 is filled with the description from the file header. 445 446 The returned pixel data should be free()d after use. */ 447 version(decodeQOI) 448 void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) 449 { 450 const(ubyte)* bytes; 451 uint header_magic; 452 ubyte* pixels; 453 qoi_rgba_t[64] index; 454 qoi_rgba_t px; 455 int px_len, chunks_len, px_pos; 456 int p = 0, run = 0; 457 458 if ((channels != 0 && channels != 3 && channels != 4) || 459 size < QOI_HEADER_SIZE + cast(int)(qoi_padding.sizeof) 460 ) { 461 return null; 462 } 463 464 bytes = cast(const(ubyte)*)data; 465 466 header_magic = qoi_read_32(bytes, &p); 467 desc.width = qoi_read_32(bytes, &p); 468 desc.height = qoi_read_32(bytes, &p); 469 desc.channels = bytes[p++]; // Return original number of channels. 470 desc.colorspace = bytes[p++]; 471 472 if ( 473 desc.width == 0 || desc.height == 0 || 474 desc.channels < 3 || desc.channels > 4 || 475 desc.colorspace > 1 || 476 header_magic != QOI_MAGIC || 477 desc.height >= QOI_PIXELS_MAX / desc.width 478 ) { 479 return null; 480 } 481 482 if (channels == 0) { 483 channels = desc.channels; 484 } 485 486 px_len = desc.width * desc.height * channels; 487 pixels = cast(ubyte*) QOI_MALLOC(px_len); 488 if (!pixels) { 489 return null; 490 } 491 492 memset(index.ptr, 0, 64 * qoi_rgba_t.sizeof); 493 px.rgba.r = 0; 494 px.rgba.g = 0; 495 px.rgba.b = 0; 496 px.rgba.a = 255; 497 498 chunks_len = size - cast(int)(qoi_padding.length); 499 for (px_pos = 0; px_pos < px_len; px_pos += channels) { 500 if (run > 0) { 501 run--; 502 } 503 else if (p < chunks_len) { 504 int b1 = bytes[p++]; 505 506 if (b1 == QOI_OP_RGB) { 507 px.rgba.r = bytes[p++]; 508 px.rgba.g = bytes[p++]; 509 px.rgba.b = bytes[p++]; 510 } 511 else if (b1 == QOI_OP_RGBA) { 512 px.rgba.r = bytes[p++]; 513 px.rgba.g = bytes[p++]; 514 px.rgba.b = bytes[p++]; 515 px.rgba.a = bytes[p++]; 516 } 517 else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { 518 px = index[b1]; 519 } 520 else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { 521 px.rgba.r += ((b1 >> 4) & 0x03) - 2; 522 px.rgba.g += ((b1 >> 2) & 0x03) - 2; 523 px.rgba.b += ( b1 & 0x03) - 2; 524 } 525 else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { 526 int b2 = bytes[p++]; 527 int vg = (b1 & 0x3f) - 32; 528 px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); 529 px.rgba.g += vg; 530 px.rgba.b += vg - 8 + (b2 & 0x0f); 531 } 532 else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { 533 run = (b1 & 0x3f); 534 } 535 536 index[QOI_COLOR_HASH(px) % 64] = px; 537 } 538 539 if (channels == 4) { 540 *cast(qoi_rgba_t*)(pixels + px_pos) = px; 541 } 542 else { 543 pixels[px_pos + 0] = px.rgba.r; 544 pixels[px_pos + 1] = px.rgba.g; 545 pixels[px_pos + 2] = px.rgba.b; 546 } 547 } 548 549 return pixels; 550 }