|
| 1 | +#include "proto.h" |
| 2 | + |
| 3 | +#define equalpixels(p1, p2) ((p1).r == (p2).r && (p1).g == (p2).g && (p1).b == (p2).b && (p1).a == (p2).a) |
| 4 | + |
| 5 | +void generate_QOI_data (struct context * context) { |
| 6 | + if (context -> source -> frames > 1) throw(context, PLUM_ERR_NO_MULTI_FRAME); |
| 7 | + if (!(context -> source -> width && context -> source -> height)) throw(context, PLUM_ERR_IMAGE_TOO_LARGE); |
| 8 | + unsigned char * header = append_output_node(context, 14); |
| 9 | + bytewrite(header, 0x71, 0x6f, 0x69, 0x66); |
| 10 | + write_be32_unaligned(header + 4, context -> source -> width); |
| 11 | + write_be32_unaligned(header + 8, context -> source -> height); |
| 12 | + uint8_t channels = 3 + image_has_transparency(context -> source); |
| 13 | + header[12] = channels; |
| 14 | + header[13] = 0; |
| 15 | + uint32_t * data; |
| 16 | + if ((context -> source -> color_format & PLUM_COLOR_MASK) == PLUM_COLOR_32) |
| 17 | + data = context -> source -> data; |
| 18 | + else { |
| 19 | + data = ctxmalloc(context, sizeof *data * context -> source -> width * context -> source -> height); |
| 20 | + plum_convert_colors(data, context -> source -> data, (size_t) context -> source -> width * context -> source -> height, |
| 21 | + PLUM_COLOR_32, context -> source -> color_format); |
| 22 | + } |
| 23 | + size_t max_size = context -> source -> width * context -> source -> height * (channels + 1) + 22; |
| 24 | + unsigned char * node = append_output_node(context, max_size); |
| 25 | + unsigned char * output = node; |
| 26 | + struct QOI_pixel lookup[64] = {0}; |
| 27 | + struct QOI_pixel px = {.r = 0, .g = 0, .b = 0, .a = 0xff}; |
| 28 | + struct QOI_pixel prev = px; |
| 29 | + unsigned char run = 0; |
| 30 | + for (uint_fast64_t cell = 0; cell < context -> source -> width * context -> source -> height; cell ++) { |
| 31 | + px.r = data[cell]; |
| 32 | + px.g = data[cell] >> 8; |
| 33 | + px.b = data[cell] >> 16; |
| 34 | + px.a = data[cell] >> 24; |
| 35 | + if (equalpixels(px, prev)) { |
| 36 | + run ++; |
| 37 | + if (run == 62 || cell == context -> source -> width * context -> source -> height - 1) { |
| 38 | + *(output ++) = 0xc0 | (run - 1); |
| 39 | + run = 0; |
| 40 | + } |
| 41 | + } else { |
| 42 | + if (run > 0) { |
| 43 | + *(output ++) = 0xc0 | (run - 1); |
| 44 | + run = 0; |
| 45 | + } |
| 46 | + uint8_t index = (px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11) % sizeof lookup; |
| 47 | + if (equalpixels(px, lookup[index])) |
| 48 | + *(output ++) = index; |
| 49 | + else { |
| 50 | + lookup[index] = px; |
| 51 | + if (px.a == prev.a) { |
| 52 | + int8_t dr = px.r - prev.r, dg = px.g - prev.g, db = px.b - prev.b; |
| 53 | + int8_t drg = dr - dg, dbg = db - dg; |
| 54 | + if (dr >= -2 && dr < 2 && dg >= -2 && dg < 2 && db >= -2 && db < 2) |
| 55 | + *(output ++) = 0x40 | ((dr + 2) << 4) | ((dg + 2) << 2) | (db + 2); |
| 56 | + else if (drg >= -8 && drg < 8 && dg >= -32 && dg < 32 && dbg >= -8 && dbg < 8) |
| 57 | + output += byteappend(output, 0x80 | (dg + 32), ((drg + 8) << 4) | (dbg + 8)); |
| 58 | + else |
| 59 | + output += byteappend(output, 0xfe, px.r, px.g, px.b); |
| 60 | + } else |
| 61 | + output += byteappend(output, 0xff, px.r, px.g, px.b, px.a); |
| 62 | + } |
| 63 | + } |
| 64 | + prev = px; |
| 65 | + } |
| 66 | + output += byteappend(output, 0, 0, 0, 0, 0, 0, 0, 1); |
| 67 | + resize_output_node(context, node, output - node); |
| 68 | + if (data != context -> source -> data) ctxfree(context, data); |
| 69 | +} |
| 70 | + |
| 71 | +#undef equalpixels |
0 commit comments