-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement QOI image format #10
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,16 @@ static inline void * append_output_node (struct context * context, size_t size) | |
return node -> data; | ||
} | ||
|
||
static inline void * resize_output_node (struct context * context, void * data, size_t size) { | ||
struct data_node * node = (struct data_node *) ((char *) data - offsetof(struct data_node, data)); | ||
if (node -> size != size) { | ||
node = ctxrealloc(context, node, sizeof *node + size); | ||
node -> size = size; | ||
if (node -> previous) node -> previous -> next = node; | ||
} | ||
return node -> data; | ||
} | ||
|
||
static inline bool bit_depth_less_than (uint32_t depth, uint32_t target) { | ||
// formally "less than or equal to", but that would be a very long name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. " |
||
return !((target - depth) & 0x80808080u); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#include "proto.h" | ||
|
||
#define pixelcolor(px) (((uint32_t) (px).a << 24) | ((uint32_t) (px).b << 16) | ((uint32_t) (px).g << 8) | (uint32_t) (px).r) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know this is basically |
||
|
||
void load_QOI_data (struct context * context, unsigned flags, size_t limit) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if (context -> size < 22) throw(context, PLUM_ERR_INVALID_FILE_FORMAT); | ||
context -> image -> type = PLUM_IMAGE_QOI; | ||
context -> image -> frames = 1; | ||
context -> image -> width = read_be32_unaligned(context -> data + 4); | ||
context -> image -> height = read_be32_unaligned(context -> data + 8); | ||
validate_image_size(context, limit); | ||
allocate_framebuffers(context, flags, false); | ||
add_color_depth_metadata(context, 8, 8, 8, 8, 0); | ||
size_t framesize = (size_t) context -> image -> width * context -> image -> height; | ||
uint32_t * frame = ctxmalloc(context, sizeof *frame * framesize); | ||
const unsigned char * data = context -> data + 14; | ||
const unsigned char * dataend = context -> data + context -> size; | ||
struct QOI_pixel lookup[64] = {0}; | ||
struct QOI_pixel px = {.r = 0, .g = 0, .b = 0, .a = 0xff}; | ||
for (size_t cell = 0; cell < framesize; cell ++) { | ||
if (data + 1 < dataend) { | ||
unsigned char v = *(data ++); | ||
if (v == 0xfe && data + 3 < dataend) { | ||
px.r = *(data ++); | ||
px.g = *(data ++); | ||
px.b = *(data ++); | ||
} else if (v == 0xff && data + 4 < dataend) { | ||
px.r = *(data ++); | ||
px.g = *(data ++); | ||
px.b = *(data ++); | ||
px.a = *(data ++); | ||
} else if (!(v & 0xc0) && v < sizeof lookup / sizeof *lookup) | ||
px = lookup[v]; | ||
else if ((v & 0xc0) == 0x40) { | ||
px.r += ((v >> 4) & 3) - 2; | ||
px.g += ((v >> 2) & 3) - 2; | ||
px.b += (v & 3) - 2; | ||
} else if ((v & 0xc0) == 0x80 && data + 1 < dataend) { | ||
unsigned char v2 = *(data ++); | ||
int8_t dg = (v & 0x3f) - 32; | ||
px.r += dg + ((v2 >> 4) & 0xf) - 8; | ||
px.g += dg; | ||
px.b += dg + (v2 & 0xf) - 8; | ||
} else if ((v & 0xc0) == 0xc0) { | ||
uint8_t run = v & 0x3f; | ||
if (cell + run >= framesize) throw(context, PLUM_ERR_INVALID_FILE_FORMAT); | ||
uint32_t color = pixelcolor(px); | ||
while (run --) frame[cell ++] = color; | ||
} else | ||
throw(context, PLUM_ERR_INVALID_FILE_FORMAT); | ||
lookup[(px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11) % (sizeof lookup / sizeof *lookup)] = px; | ||
} | ||
frame[cell] = pixelcolor(px); | ||
} | ||
plum_convert_colors(context -> image -> data8, frame, framesize, flags, PLUM_COLOR_32 | PLUM_ALPHA_INVERT); | ||
ctxfree(context, frame); | ||
} | ||
|
||
#undef pixelcolor |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
#include "proto.h" | ||
|
||
#define equalpixels(p1, p2) ((p1).r == (p2).r && (p1).g == (p2).g && (p1).b == (p2).b && (p1).a == (p2).a) | ||
|
||
void generate_QOI_data (struct context * context) { | ||
if (context -> source -> frames > 1) throw(context, PLUM_ERR_NO_MULTI_FRAME); | ||
if (!(context -> source -> width && context -> source -> height)) throw(context, PLUM_ERR_IMAGE_TOO_LARGE); | ||
unsigned char * header = append_output_node(context, 14); | ||
bytewrite(header, 0x71, 0x6f, 0x69, 0x66); | ||
write_be32_unaligned(header + 4, context -> source -> width); | ||
write_be32_unaligned(header + 8, context -> source -> height); | ||
uint8_t channels = 3 + image_has_transparency(context -> source); | ||
bytewrite(header + 12, channels, 0); | ||
size_t framesize = (size_t) context -> source -> width * context -> source -> height; | ||
uint32_t * data; | ||
if ((context -> source -> color_format & (PLUM_COLOR_MASK | PLUM_ALPHA_INVERT)) == (PLUM_COLOR_32 | PLUM_ALPHA_INVERT)) | ||
data = context -> source -> data; | ||
else { | ||
data = ctxmalloc(context, sizeof *data * framesize); | ||
plum_convert_colors(data, context -> source -> data, framesize, PLUM_COLOR_32 | PLUM_ALPHA_INVERT, context -> source -> color_format); | ||
} | ||
unsigned char * node = append_output_node(context, framesize * (channels + 1) + 8); | ||
unsigned char * output = node; | ||
struct QOI_pixel lookup[64] = {0}; | ||
struct QOI_pixel prev = {.r = 0, .g = 0, .b = 0, .a = 0xff}; | ||
uint8_t run = 0; | ||
for (size_t cell = 0; cell < framesize; cell ++) { | ||
struct QOI_pixel px = {.r = data[cell], .g = data[cell] >> 8, .b = data[cell] >> 16, .a = data[cell] >> 24}; | ||
Comment on lines
+27
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other possibilities which would use an extra line: uint32_t * dataend = data + framesize;
for (uint32_t * val = data; val < data; val ++) {
struct QOI_pixel px = {.r = *val, .g = *val >> 8, .b = *val >> 16, .a = *val >> 24};
...
if (run == 62 || val == dataend - 1) { for (size_t cell = 0; cell < framesize; cell ++) {
uint32_t val = data[cell];
struct QOI_pixel px = {.r = val, .g = val >> 8, .b = val >> 16, .a = val >> 24};
...
if (run == 62 || cell == framesize - 1) { |
||
if (equalpixels(px, prev)) { | ||
run ++; | ||
if (run == 62 || cell == framesize - 1) { | ||
*(output ++) = 0xc0 | (run - 1); | ||
run = 0; | ||
} | ||
} else { | ||
if (run) { | ||
*(output ++) = 0xc0 | (run - 1); | ||
run = 0; | ||
} | ||
uint8_t index = (px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11) % (sizeof lookup / sizeof *lookup); | ||
if (equalpixels(px, lookup[index])) | ||
*(output ++) = index; | ||
else { | ||
lookup[index] = px; | ||
if (px.a == prev.a) { | ||
int8_t dr = px.r - prev.r, dg = px.g - prev.g, db = px.b - prev.b; | ||
int8_t drg = dr - dg, dbg = db - dg; | ||
if (dr >= -2 && dr < 2 && dg >= -2 && dg < 2 && db >= -2 && db < 2) | ||
*(output ++) = 0x40 | ((dr + 2) << 4) | ((dg + 2) << 2) | (db + 2); | ||
else if (drg >= -8 && drg < 8 && dg >= -32 && dg < 32 && dbg >= -8 && dbg < 8) | ||
output += byteappend(output, 0x80 | (dg + 32), ((drg + 8) << 4) | (dbg + 8)); | ||
else | ||
output += byteappend(output, 0xfe, px.r, px.g, px.b); | ||
} else | ||
output += byteappend(output, 0xff, px.r, px.g, px.b, px.a); | ||
} | ||
prev = px; | ||
} | ||
} | ||
output += byteappend(output, 0, 0, 0, 0, 0, 0, 0, 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I considered using a |
||
resize_output_node(context, node, output - node); | ||
if (data != context -> source -> data) ctxfree(context, data); | ||
} | ||
|
||
#undef equalpixels |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -147,3 +147,10 @@ struct PNM_image_header { | |
size_t datastart; | ||
size_t datalength; | ||
}; | ||
|
||
struct QOI_pixel { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did consider using four local |
||
uint8_t r; | ||
uint8_t g; | ||
uint8_t b; | ||
uint8_t a; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The alternative I saw to "allocate one large-as-possible node and then shrink it after QOI compression is done" was "allocate lots of little nodes for every QOI chunk", and this seemed better.