-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allows to decode QOI images. See https://qoiformat.org for format description. Resolves #195, #196. Co-authored-by: sepi <[email protected]> Co-authored-by: Sashko <[email protected]> Signed-off-by: Artem Senichev <[email protected]>
- Loading branch information
1 parent
d6c9820
commit 3866207
Showing
7 changed files
with
147 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// SPDX-License-Identifier: MIT | ||
// QOI format decoder. | ||
// Copyright (C) 2024 Artem Senichev <[email protected]> | ||
|
||
#include "../loader.h" | ||
|
||
#include <arpa/inet.h> | ||
#include <string.h> | ||
|
||
// Chunk tags | ||
#define QOI_OP_INDEX 0x00 | ||
#define QOI_OP_DIFF 0x40 | ||
#define QOI_OP_LUMA 0x80 | ||
#define QOI_OP_RUN 0xc0 | ||
#define QOI_OP_RGB 0xfe | ||
#define QOI_OP_RGBA 0xff | ||
|
||
// Mask of second byte in steram | ||
#define QOI_MASK_2 0xc0 | ||
|
||
// Size of color map | ||
#define QOI_CLRMAP_SIZE 64 | ||
// Calc color index in map | ||
#define QOI_CLRMAP_INDEX(r, g, b, a) \ | ||
((r * 3 + g * 5 + b * 7 + a * 11) % QOI_CLRMAP_SIZE) | ||
|
||
// QOI signature | ||
static const uint8_t signature[] = { 'q', 'o', 'i', 'f' }; | ||
|
||
// QOI file header | ||
struct __attribute__((__packed__)) qoi_header { | ||
uint8_t magic[4]; // Magic bytes "qoif" | ||
uint32_t width; // Image width in pixels | ||
uint32_t height; // Image height in pixels | ||
uint8_t channels; // Number of color channels: 3 = RGB, 4 = RGBA | ||
uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear | ||
}; | ||
|
||
// QOI loader implementation | ||
enum loader_status decode_qoi(struct image* ctx, const uint8_t* data, | ||
size_t size) | ||
{ | ||
const struct qoi_header* qoi = (const struct qoi_header*)data; | ||
argb_t color_map[QOI_CLRMAP_SIZE]; | ||
struct pixmap* pm; | ||
uint8_t a, r, g, b; | ||
size_t total_pixels; | ||
size_t rlen; | ||
size_t pos; | ||
|
||
// check signature | ||
if (size < sizeof(*qoi) || | ||
memcmp(qoi->magic, signature, sizeof(signature))) { | ||
return ldr_unsupported; | ||
} | ||
// check format | ||
if (qoi->width == 0 || qoi->height == 0 || qoi->channels < 3 || | ||
qoi->channels > 4) { | ||
return ldr_fmterror; | ||
} | ||
|
||
// allocate image buffer | ||
pm = image_allocate_frame(ctx, htonl(qoi->width), htonl(qoi->height)); | ||
if (!pm) { | ||
return ldr_fmterror; | ||
} | ||
|
||
// initialize decoder state | ||
r = 0; | ||
g = 0; | ||
b = 0; | ||
a = 0xff; | ||
rlen = 0; | ||
pos = sizeof(struct qoi_header); | ||
total_pixels = pm->width * pm->height; | ||
memset(color_map, 0, sizeof(color_map)); | ||
|
||
// decode image | ||
for (size_t i = 0; i < total_pixels; ++i) { | ||
if (rlen > 0) { | ||
--rlen; | ||
} else { | ||
uint8_t tag; | ||
if (pos >= size) { | ||
break; | ||
} | ||
tag = data[pos++]; | ||
|
||
if (tag == QOI_OP_RGB) { | ||
if (pos + 3 >= size) { | ||
goto fail; | ||
} | ||
r = data[pos++]; | ||
g = data[pos++]; | ||
b = data[pos++]; | ||
} else if (tag == QOI_OP_RGBA) { | ||
if (pos + 4 >= size) { | ||
goto fail; | ||
} | ||
r = data[pos++]; | ||
g = data[pos++]; | ||
b = data[pos++]; | ||
a = data[pos++]; | ||
} else if ((tag & QOI_MASK_2) == QOI_OP_INDEX) { | ||
const argb_t clr = color_map[tag & 0x3f]; | ||
a = ARGB_GET_A(clr); | ||
r = ARGB_GET_R(clr); | ||
g = ARGB_GET_G(clr); | ||
b = ARGB_GET_B(clr); | ||
} else if ((tag & QOI_MASK_2) == QOI_OP_DIFF) { | ||
r += (int8_t)((tag >> 4) & 3) - 2; | ||
g += (int8_t)((tag >> 2) & 3) - 2; | ||
b += (int8_t)(tag & 3) - 2; | ||
} else if ((tag & QOI_MASK_2) == QOI_OP_LUMA) { | ||
uint8_t diff; | ||
int8_t diff_green; | ||
if (pos + 1 >= size) { | ||
goto fail; | ||
} | ||
diff = data[pos++]; | ||
diff_green = (int8_t)(tag & 0x3f) - 32; | ||
r += diff_green - 8 + ((diff >> 4) & 0x0f); | ||
g += diff_green; | ||
b += diff_green - 8 + (diff & 0x0f); | ||
} else if ((tag & QOI_MASK_2) == QOI_OP_RUN) { | ||
rlen = (tag & 0x3f); | ||
} | ||
color_map[QOI_CLRMAP_INDEX(r, g, b, a)] = ARGB(a, r, g, b); | ||
} | ||
pm->data[i] = ARGB(a, r, g, b); | ||
} | ||
|
||
image_set_format(ctx, "QOI %dbpp", qoi->channels * 8); | ||
ctx->alpha = (qoi->channels == 4); | ||
return ldr_success; | ||
|
||
fail: | ||
image_free_frames(ctx); | ||
return ldr_fmterror; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters