Util: Preliminary palette support

This commit is contained in:
Vicki Pfau 2023-04-17 03:00:00 -07:00
parent f8b923015b
commit 618a51cabb
3 changed files with 118 additions and 23 deletions

View File

@ -77,6 +77,7 @@ enum mColorFormat {
mCOLOR_RGB8 = 0x10000, mCOLOR_RGB8 = 0x10000,
mCOLOR_BGR8 = 0x20000, mCOLOR_BGR8 = 0x20000,
mCOLOR_L8 = 0x40000, mCOLOR_L8 = 0x40000,
mCOLOR_PAL8 = 0x80000,
mCOLOR_ANY = -1 mCOLOR_ANY = -1
}; };
@ -91,10 +92,12 @@ enum mColorFormat {
struct mImage { struct mImage {
void* data; void* data;
uint32_t* palette;
unsigned width; unsigned width;
unsigned height; unsigned height;
unsigned stride; unsigned stride;
unsigned depth; unsigned depth;
unsigned palSize;
enum mColorFormat format; enum mColorFormat format;
}; };
@ -146,6 +149,7 @@ static inline unsigned mColorFormatBytes(enum mColorFormat format) {
case mCOLOR_BGR8: case mCOLOR_BGR8:
return 3; return 3;
case mCOLOR_L8: case mCOLOR_L8:
case mCOLOR_PAL8:
return 1; return 1;
case mCOLOR_ANY: case mCOLOR_ANY:
break; break;
@ -175,6 +179,7 @@ static inline bool mColorFormatHasAlpha(enum mColorFormat format) {
case mCOLOR_ABGR5: case mCOLOR_ABGR5:
case mCOLOR_RGBA5: case mCOLOR_RGBA5:
case mCOLOR_BGRA5: case mCOLOR_BGRA5:
case mCOLOR_PAL8:
return true; return true;
case mCOLOR_ANY: case mCOLOR_ANY:
break; break;

View File

@ -135,17 +135,51 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) {
case 1: case 1:
if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) { if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) {
image->format = mCOLOR_L8; image->format = mCOLOR_L8;
image->depth = 1; } else {
image->data = malloc(width * height); png_colorp palette;
if (!PNGReadPixels8(png, info, image->data, width, height, width)) { png_bytep trns;
free(image->data); int count;
free(image); int trnsCount = 0;
PNGReadClose(png, info, end); image->format = mCOLOR_PAL8;
if (png_get_PLTE(png, info, &palette, &count) == 0) {
return NULL; return NULL;
} }
break; if (count > 256) {
count = 256;
#ifndef NDEBUG
abort();
#endif
}
image->palette = malloc(1024);
image->palSize = count;
png_get_tRNS(png, info, &trns, &trnsCount, NULL);
int i;
for (i = 0; i < count; ++i) {
uint32_t color = palette[i].red << 16;
color |= palette[i].green << 8;
color |= palette[i].blue;
if (i < trnsCount) {
color |= trns[i] << 24;
} else {
color |= 0xFF000000;
}
image->palette[i] = color;
}
} }
// Fall through image->depth = 1;
image->data = malloc(width * height);
if (!PNGReadPixels8(png, info, image->data, width, height, width)) {
if (image->palette) {
free(image->palette);
}
free(image->data);
free(image);
PNGReadClose(png, info, end);
return NULL;
}
break;
default: default:
// Not supported yet // Not supported yet
free(image); free(image);
@ -169,6 +203,10 @@ struct mImage* mImageLoadVF(struct VFile* vf) {
} }
struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorFormat format) { struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorFormat format) {
if (format == mCOLOR_PAL8) {
// Quantization shouldn't be handled here
return NULL;
}
struct mImage* newImage = calloc(1, sizeof(*newImage)); struct mImage* newImage = calloc(1, sizeof(*newImage));
newImage->width = image->width; newImage->width = image->width;
newImage->height = image->height; newImage->height = image->height;
@ -186,20 +224,36 @@ struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorForm
// TODO: Implement more specializations, e.g. alpha narrowing/widening, channel swapping // TODO: Implement more specializations, e.g. alpha narrowing/widening, channel swapping
size_t x, y; size_t x, y;
for (y = 0; y < newImage->height; ++y) { if (image->format == mCOLOR_PAL8) {
uintptr_t src = (uintptr_t) ROW(image, y); for (y = 0; y < newImage->height; ++y) {
uintptr_t dst = (uintptr_t) ROW(newImage, y); uintptr_t src = (uintptr_t) ROW(image, y);
for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { uintptr_t dst = (uintptr_t) ROW(newImage, y);
uint32_t color; for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) {
GET_PIXEL(color, src, image->depth); uint32_t color;
color = mColorConvert(color, image->format, format); GET_PIXEL(color, src, image->depth);
PUT_PIXEL(color, dst, newImage->depth); color = image->palette[color];
PUT_PIXEL(color, dst, newImage->depth);
}
}
} else {
for (y = 0; y < newImage->height; ++y) {
uintptr_t src = (uintptr_t) ROW(image, y);
uintptr_t dst = (uintptr_t) ROW(newImage, y);
for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) {
uint32_t color;
GET_PIXEL(color, src, image->depth);
color = mColorConvert(color, image->format, format);
PUT_PIXEL(color, dst, newImage->depth);
}
} }
} }
return newImage; return newImage;
} }
void mImageDestroy(struct mImage* image) { void mImageDestroy(struct mImage* image) {
if (image->palette) {
free(image->palette);
}
free(image->data); free(image->data);
free(image); free(image);
} }
@ -280,7 +334,12 @@ uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) {
} }
uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y) { uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y) {
return mColorConvert(mImageGetPixelRaw(image, x, y), image->format, mCOLOR_ARGB8); uint32_t raw = mImageGetPixelRaw(image, x, y);
if (image->format == mCOLOR_PAL8) {
return image->palette[raw];
} else {
return mColorConvert(raw, image->format, mCOLOR_ARGB8);
}
} }
void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color) { void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color) {
@ -531,6 +590,7 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat
b = color; b = color;
break; break;
case mCOLOR_PAL8:
case mCOLOR_ANY: case mCOLOR_ANY:
return 0; return 0;
} }
@ -619,6 +679,7 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat
// sRGB primaries in fixed point, roughly fudged to saturate to 0xFFFF // sRGB primaries in fixed point, roughly fudged to saturate to 0xFFFF
color = (55 * r + 184 * g + 18 * b) >> 8; color = (55 * r + 184 * g + 18 * b) >> 8;
break; break;
case mCOLOR_PAL8:
case mCOLOR_ANY: case mCOLOR_ANY:
return 0; return 0;
} }

View File

@ -464,9 +464,8 @@ M_TEST_DEFINE(loadPng24) {
0x51, 0xc0, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x51, 0xc0, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82 0x42, 0x60, 0x82
}; };
size_t len = 75;
struct VFile* vf = VFileFromConstMemory(data, len); struct VFile* vf = VFileFromConstMemory(data, sizeof(data));
struct mImage* image = mImageLoadVF(vf); struct mImage* image = mImageLoadVF(vf);
vf->close(vf); vf->close(vf);
@ -483,9 +482,8 @@ M_TEST_DEFINE(loadPng24) {
mImageDestroy(image); mImageDestroy(image);
} }
M_TEST_DEFINE(loadPng32) { M_TEST_DEFINE(loadPng32) {
unsigned char data[] = { const uint8_t data[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
0x08, 0x06, 0x00, 0x00, 0x00, 0x72, 0xb6, 0x0d, 0x24, 0x00, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x00, 0x72, 0xb6, 0x0d, 0x24, 0x00, 0x00, 0x00,
@ -494,9 +492,8 @@ M_TEST_DEFINE(loadPng32) {
0xa7, 0x5a, 0x78, 0x58, 0x7b, 0x07, 0xac, 0xe9, 0x00, 0x3d, 0x95, 0x00, 0xa7, 0x5a, 0x78, 0x58, 0x7b, 0x07, 0xac, 0xe9, 0x00, 0x3d, 0x95, 0x00,
0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
}; };
unsigned int len = 83;
struct VFile* vf = VFileFromConstMemory(data, len); struct VFile* vf = VFileFromConstMemory(data, sizeof(data));
struct mImage* image = mImageLoadVF(vf); struct mImage* image = mImageLoadVF(vf);
vf->close(vf); vf->close(vf);
@ -513,6 +510,37 @@ M_TEST_DEFINE(loadPng32) {
mImageDestroy(image); mImageDestroy(image);
} }
M_TEST_DEFINE(loadPngPalette) {
const uint8_t data[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
0x08, 0x03, 0x00, 0x00, 0x00, 0x45, 0x68, 0xfd, 0x16, 0x00, 0x00, 0x00,
0x0c, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
0xff, 0x00, 0x00, 0x00, 0xff, 0x9b, 0xc0, 0x13, 0xdc, 0x00, 0x00, 0x00,
0x04, 0x74, 0x52, 0x4e, 0x53, 0x00, 0xc0, 0x80, 0x40, 0x6f, 0x63, 0x29,
0x01, 0x00, 0x00, 0x00, 0x0e, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63,
0x60, 0x60, 0x64, 0x60, 0x62, 0x06, 0x00, 0x00, 0x11, 0x00, 0x07, 0x69,
0xe2, 0x2a, 0x44, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82
};
struct VFile* vf = VFileFromConstMemory(data, sizeof(data));
struct mImage* image = mImageLoadVF(vf);
vf->close(vf);
assert_non_null(image);
assert_int_equal(image->width, 2);
assert_int_equal(image->height, 2);
assert_int_equal(image->format, mCOLOR_PAL8);
assert_int_equal(mImageGetPixel(image, 0, 0), 0x00000000);
assert_int_equal(mImageGetPixel(image, 1, 0), 0xC0FF0000);
assert_int_equal(mImageGetPixel(image, 0, 1), 0x8000FF00);
assert_int_equal(mImageGetPixel(image, 1, 1), 0x400000FF);
mImageDestroy(image);
}
M_TEST_DEFINE(savePngNative) { M_TEST_DEFINE(savePngNative) {
struct mImage* image = mImageCreate(1, 1, mCOLOR_ABGR8); struct mImage* image = mImageCreate(1, 1, mCOLOR_ABGR8);
mImageSetPixel(image, 0, 0, 0x01234567); mImageSetPixel(image, 0, 0, 0x01234567);
@ -1069,6 +1097,7 @@ M_TEST_SUITE_DEFINE(Image,
#ifdef USE_PNG #ifdef USE_PNG
cmocka_unit_test(loadPng24), cmocka_unit_test(loadPng24),
cmocka_unit_test(loadPng32), cmocka_unit_test(loadPng32),
cmocka_unit_test(loadPngPalette),
cmocka_unit_test(savePngNative), cmocka_unit_test(savePngNative),
cmocka_unit_test(savePngNonNative), cmocka_unit_test(savePngNonNative),
cmocka_unit_test(savePngRoundTrip), cmocka_unit_test(savePngRoundTrip),