diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 0a1b8d7b0..0e0ee55bb 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -77,6 +77,7 @@ enum mColorFormat { mCOLOR_RGB8 = 0x10000, mCOLOR_BGR8 = 0x20000, mCOLOR_L8 = 0x40000, + mCOLOR_PAL8 = 0x80000, mCOLOR_ANY = -1 }; @@ -91,10 +92,12 @@ enum mColorFormat { struct mImage { void* data; + uint32_t* palette; unsigned width; unsigned height; unsigned stride; unsigned depth; + unsigned palSize; enum mColorFormat format; }; @@ -146,6 +149,7 @@ static inline unsigned mColorFormatBytes(enum mColorFormat format) { case mCOLOR_BGR8: return 3; case mCOLOR_L8: + case mCOLOR_PAL8: return 1; case mCOLOR_ANY: break; @@ -175,6 +179,7 @@ static inline bool mColorFormatHasAlpha(enum mColorFormat format) { case mCOLOR_ABGR5: case mCOLOR_RGBA5: case mCOLOR_BGRA5: + case mCOLOR_PAL8: return true; case mCOLOR_ANY: break; diff --git a/src/util/image.c b/src/util/image.c index f149ce68e..c686c297a 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -135,17 +135,51 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { case 1: if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) { image->format = mCOLOR_L8; - image->depth = 1; - image->data = malloc(width * height); - if (!PNGReadPixels8(png, info, image->data, width, height, width)) { - free(image->data); - free(image); - PNGReadClose(png, info, end); + } else { + png_colorp palette; + png_bytep trns; + int count; + int trnsCount = 0; + image->format = mCOLOR_PAL8; + if (png_get_PLTE(png, info, &palette, &count) == 0) { 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: // Not supported yet free(image); @@ -169,6 +203,10 @@ struct mImage* mImageLoadVF(struct VFile* vf) { } 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)); newImage->width = image->width; 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 size_t x, y; - 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); + if (image->format == mCOLOR_PAL8) { + 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 = 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; } void mImageDestroy(struct mImage* image) { + if (image->palette) { + free(image->palette); + } free(image->data); 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) { - 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) { @@ -531,6 +590,7 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat b = color; break; + case mCOLOR_PAL8: case mCOLOR_ANY: 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 color = (55 * r + 184 * g + 18 * b) >> 8; break; + case mCOLOR_PAL8: case mCOLOR_ANY: return 0; } diff --git a/src/util/test/image.c b/src/util/test/image.c index f909da910..41c29bf6d 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -464,9 +464,8 @@ M_TEST_DEFINE(loadPng24) { 0x51, 0xc0, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 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); vf->close(vf); @@ -483,9 +482,8 @@ M_TEST_DEFINE(loadPng24) { mImageDestroy(image); } - M_TEST_DEFINE(loadPng32) { - unsigned char data[] = { + 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, 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, 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); vf->close(vf); @@ -513,6 +510,37 @@ M_TEST_DEFINE(loadPng32) { 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) { struct mImage* image = mImageCreate(1, 1, mCOLOR_ABGR8); mImageSetPixel(image, 0, 0, 0x01234567); @@ -1069,6 +1097,7 @@ M_TEST_SUITE_DEFINE(Image, #ifdef USE_PNG cmocka_unit_test(loadPng24), cmocka_unit_test(loadPng32), + cmocka_unit_test(loadPngPalette), cmocka_unit_test(savePngNative), cmocka_unit_test(savePngNonNative), cmocka_unit_test(savePngRoundTrip),