mirror of
https://github.com/Karaka-Management/cOMS.git
synced 2026-01-10 19:08:39 +00:00
310 lines
8.6 KiB
C
Executable File
310 lines
8.6 KiB
C
Executable File
/**
|
|
* Jingga
|
|
*
|
|
* @copyright 2021, Dominic Szablewski - https://phoboslab.org
|
|
* @copyright Jingga
|
|
* @license OMS License 2.0
|
|
* @version 1.0.0
|
|
* @link https://jingga.app
|
|
*/
|
|
#ifndef COMS_IMAGE_QOI_H
|
|
#define COMS_IMAGE_QOI_H
|
|
|
|
#include "../stdlib/Types.h"
|
|
#include <string.h>
|
|
#include "Image.cpp"
|
|
|
|
#define QOI_OP_LUMA555 0b00000000
|
|
#define QOI_OP_LUMA222 0b10000000
|
|
#define QOI_OP_LUMA777 0b01000000
|
|
|
|
#define QOI_OP_RUN 0b11000000
|
|
|
|
// These definitions are important and impact how large our run can be:
|
|
// Run has 6 free bits -> 2^6 = 64
|
|
// However, the first bit is used to indicate RGB or RGBA -> 64 - 2^1 = 62
|
|
#define QOI_OP_RGB 0b11111110
|
|
#define QOI_OP_RGBA 0b11111111
|
|
|
|
#define QOI_MASK_1 0b10000000
|
|
#define QOI_MASK_2 0b11000000
|
|
#define QOI_MASK_3 0b11100000
|
|
|
|
// @performance I feel like there is some more optimization possible by handling fully transparent pixels in a special way
|
|
// @todo We need to implement monochrome handling, which is very important for game assets that often use monochrome assets for all kinds of things (e.g. translucency)
|
|
|
|
static const byte optable[128] = {
|
|
0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
|
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
|
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
|
|
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
|
|
};
|
|
|
|
int32 qoi_encode(const Image* image, byte* data) noexcept
|
|
{
|
|
byte* start = data;
|
|
data += image_header_to_data(image, data);
|
|
|
|
v4_byte index[64];
|
|
memset(index, 0, sizeof(index));
|
|
|
|
v4_byte px_prev = {0, 0, 0, 255};
|
|
v4_byte px = px_prev;
|
|
|
|
const int32 channels = (image->image_settings & IMAGE_SETTING_CHANNEL_COUNT);
|
|
|
|
int32 px_len = image->width * image->height * channels;
|
|
int32 px_end = px_len - channels;
|
|
|
|
int32 run = 0;
|
|
if (channels == 4) {
|
|
for (int32 px_pos = 0; px_pos < px_len; px_pos += 4) {
|
|
px.val = SWAP_ENDIAN_LITTLE(*((uint32 *) (image->pixels + px_pos)));
|
|
|
|
while(px.val == px_prev.val) {
|
|
++run;
|
|
if(px_pos == px_end) {
|
|
*data++ = (byte) (QOI_OP_RUN | (run - 1));
|
|
px_pos = px_len;
|
|
|
|
break;
|
|
} else if (run == 62) {
|
|
*data++ = (byte) (QOI_OP_RUN | (run - 1));
|
|
run = 0;
|
|
}
|
|
|
|
px_pos += 4;
|
|
px.val = SWAP_ENDIAN_LITTLE(*((uint32 *) (image->pixels + px_pos)));
|
|
}
|
|
|
|
if (run) {
|
|
*data++ = (byte) (QOI_OP_RUN | (run - 1));
|
|
run = 0;
|
|
}
|
|
|
|
if(px.a != px_prev.a){
|
|
*data++ = QOI_OP_RGBA;
|
|
*data++ = px.a;
|
|
}
|
|
|
|
signed char vr = px.r - px_prev.r;
|
|
signed char vg = px.g - px_prev.g;
|
|
signed char vb = px.b - px_prev.b;
|
|
|
|
signed char vg_r = vr - vg;
|
|
signed char vg_b = vb - vg;
|
|
|
|
byte ar = vg_r < 0 ? -vg_r - 1 : vg_r;
|
|
byte ag = vg < 0 ? -vg - 1 : vg;
|
|
byte ab = vg_b < 0 ? -vg_b - 1 : vg_b;
|
|
byte argb = ar | ag | ab;
|
|
|
|
switch(optable[argb]) {
|
|
case 0:
|
|
*data++ = QOI_OP_LUMA222 | ((vg_r + 2) << 4) | ((vg_b + 2) << 2) | (vg + 2);
|
|
break;
|
|
case 1:
|
|
*data++ = QOI_OP_LUMA555 | ((vg_b + 16) << 2) | ((vg_r + 16) >> 3);
|
|
*data++ = (((vg_r + 16) & 7) << 5) | (vg + 16);
|
|
break;
|
|
case 2:
|
|
*data++ = QOI_OP_LUMA777 | ((vg_b + 64) >> 2);
|
|
*data++ = (((vg_b + 64) & 3) << 6) | ((vg_r + 64) >> 1);
|
|
*data++ = (((vg_r + 64) & 1) << 7) | (vg + 64);
|
|
break;
|
|
case 3:
|
|
*data++ = QOI_OP_RGB;
|
|
*data++ = px.r;
|
|
*data++ = px.g;
|
|
*data++ = px.b;
|
|
break;
|
|
}
|
|
|
|
px_prev = px;
|
|
}
|
|
} else {
|
|
for (int32 px_pos = 0; px_pos < px_len; px_pos += 3) {
|
|
px.r = image->pixels[px_pos];
|
|
px.g = image->pixels[px_pos + 1];
|
|
px.b = image->pixels[px_pos + 2];
|
|
|
|
while(px.val == px_prev.val) {
|
|
++run;
|
|
if(px_pos == px_end) {
|
|
*data++ = (byte) (QOI_OP_RUN | (run - 1));
|
|
px_pos = px_len;
|
|
|
|
break;
|
|
} else if (run == 62) {
|
|
*data++ = (byte) (QOI_OP_RUN | (run - 1));
|
|
run = 0;
|
|
}
|
|
|
|
px_pos += 3;
|
|
px.r = image->pixels[px_pos];
|
|
px.g = image->pixels[px_pos + 1];
|
|
px.b = image->pixels[px_pos + 2];
|
|
}
|
|
|
|
if (run) {
|
|
*data++ = (byte) (QOI_OP_RUN | (run - 1));
|
|
run = 0;
|
|
}
|
|
|
|
signed char vr = px.r - px_prev.r;
|
|
signed char vg = px.g - px_prev.g;
|
|
signed char vb = px.b - px_prev.b;
|
|
|
|
signed char vg_r = vr - vg;
|
|
signed char vg_b = vb - vg;
|
|
|
|
byte ar = vg_r < 0 ? -vg_r - 1 : vg_r;
|
|
byte ag = vg < 0 ? -vg - 1 : vg;
|
|
byte ab = vg_b < 0 ? -vg_b - 1 : vg_b;
|
|
byte argb = ar | ag | ab;
|
|
|
|
switch(optable[argb]) {
|
|
case 0:
|
|
*data++ = QOI_OP_LUMA222 | ((vg_r + 2) << 4) | ((vg_b + 2) << 2) | (vg + 2);
|
|
break;
|
|
case 1:
|
|
*data++ = QOI_OP_LUMA555 | ((vg_b + 16) << 2) | ((vg_r + 16) >> 3);
|
|
*data++ = (((vg_r + 16) & 7) << 5) | (vg + 16);
|
|
break;
|
|
case 2:
|
|
*data++ = QOI_OP_LUMA777 | ((vg_b + 64) >> 2);
|
|
*data++ = (((vg_b + 64) & 3) << 6) | ((vg_r + 64) >> 1);
|
|
*data++ = (((vg_r + 64) & 1) << 7) | (vg + 64);
|
|
break;
|
|
case 3:
|
|
*data++ = QOI_OP_RGB;
|
|
*data++ = px.r;
|
|
*data++ = px.g;
|
|
*data++ = px.b;
|
|
break;
|
|
}
|
|
|
|
px_prev = px;
|
|
}
|
|
}
|
|
|
|
return (int32) (data - start);
|
|
}
|
|
|
|
static
|
|
int32 qoi_decode_4(const byte* data, Image* image) noexcept
|
|
{
|
|
uint32 px_len = image->width * image->height * 4;
|
|
v4_byte px = {0, 0, 0, 255};
|
|
v4_byte index[64] = {0};
|
|
int32 run = 0;
|
|
|
|
for (uint32 px_pos = 0; px_pos < px_len; px_pos += 4) {
|
|
if (run > 0) {
|
|
--run;
|
|
} else {
|
|
OP_RGBA_GOTO:
|
|
byte b1 = *data++;
|
|
|
|
if (b1 == QOI_OP_RGB) {
|
|
px.r = *data++;
|
|
px.g = *data++;
|
|
px.b = *data++;
|
|
} else if (b1 == QOI_OP_RGBA) {
|
|
px.a = *data++;
|
|
goto OP_RGBA_GOTO;
|
|
} else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA222) {
|
|
byte vg = (b1 & 3) - 2;
|
|
px.r += vg - 2 + ((b1 >> 4) & 3);
|
|
px.g += vg;
|
|
px.b += vg - 2 + ((b1 >> 2) & 3);
|
|
} else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA555) {
|
|
byte b2 = *data++;
|
|
byte vg = (b2 & 31) - 16;
|
|
px.r += vg - 16 + (((b1 & 3) << 3) | (b2 >> 5));
|
|
px.g += vg;
|
|
px.b += vg - 16 + ((b1 >> 2) & 31);
|
|
} else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA777) {
|
|
byte b2 = *data++;
|
|
byte b3 = *data++;
|
|
byte vg = (b3 & 0x7f) - 64;
|
|
px.r += vg - 64 + ((b2 & 0x3f) << 1) + (b3 >> 7);
|
|
px.g += vg;
|
|
px.b += vg - 64 + ((b1 & 0x1f) << 2) + (b2 >> 6);
|
|
} else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
|
|
run = (b1 & 0x3f);
|
|
}
|
|
}
|
|
|
|
*((uint32 *) &image->pixels[px_pos]) = SWAP_ENDIAN_LITTLE(px.val);
|
|
}
|
|
|
|
return px_len;
|
|
}
|
|
|
|
static
|
|
int32 qoi_decode_3(const byte* data, Image* image) noexcept
|
|
{
|
|
uint32 px_len = image->width * image->height * 3;
|
|
v3_byte px = {0, 0, 0};
|
|
int32 run = 0;
|
|
|
|
for (uint32 px_pos = 0; px_pos < px_len; px_pos += 3) {
|
|
if (run > 0) {
|
|
--run;
|
|
} else {
|
|
byte b1 = *data++;
|
|
|
|
if (b1 == QOI_OP_RGB) {
|
|
px.r = *data++;
|
|
px.g = *data++;
|
|
px.b = *data++;
|
|
} else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA222) {
|
|
byte vg = (b1 & 3) - 2;
|
|
px.r += vg - 2 + ((b1 >> 4) & 3);
|
|
px.g += vg;
|
|
px.b += vg - 2 + ((b1 >> 2) & 3);
|
|
} else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA555) {
|
|
byte b2 = *data++;
|
|
byte vg = (b2 & 31) - 16;
|
|
px.r += vg - 16 + (((b1 & 3) << 3) | (b2 >> 5));
|
|
px.g += vg;
|
|
px.b += vg - 16 + ((b1 >> 2) & 31);
|
|
} else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA777) {
|
|
byte b2 = *data++;
|
|
byte b3 = *data++;
|
|
byte vg = (b3 & 0x7f) - 64;
|
|
px.r += vg - 64 + ((b2 & 0x3f) << 1) + (b3 >> 7);
|
|
px.g += vg;
|
|
px.b += vg - 64 + ((b1 & 0x1f) << 2) + (b2 >> 6);
|
|
} else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
|
|
run = (b1 & 0x3f);
|
|
}
|
|
}
|
|
|
|
image->pixels[px_pos] = px.r;
|
|
image->pixels[px_pos + 1] = px.g;
|
|
image->pixels[px_pos + 2] = px.b;
|
|
}
|
|
|
|
return px_len;
|
|
}
|
|
|
|
int32 qoi_decode(const byte* data, Image* image) noexcept
|
|
{
|
|
LOG_3("QOI decode image");
|
|
int32 header_length = image_header_from_data(data, image);
|
|
|
|
const int32 channels = (image->image_settings & IMAGE_SETTING_CHANNEL_COUNT);
|
|
|
|
int32 len = 0;
|
|
if (channels == 4) {
|
|
len = qoi_decode_4(data + header_length, image);
|
|
} else if (channels == 3) {
|
|
len = qoi_decode_3(data + header_length, image);
|
|
}
|
|
|
|
return header_length + len;
|
|
}
|
|
|
|
#endif |