diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 544e38e54..0cb4dc24a 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -94,6 +94,7 @@ struct VFile; struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format); struct mImage* mImageLoad(const char* path); struct mImage* mImageLoadVF(struct VFile* vf); +struct mImage* mImageConvertToFormat(const struct mImage*, enum mColorFormat format); void mImageDestroy(struct mImage*); uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y); diff --git a/src/util/image.c b/src/util/image.c index a0053b573..0e8a0fbed 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -11,6 +11,8 @@ #define PIXEL(IM, X, Y) \ (void*) (((IM)->stride * (Y) + (X)) * (IM)->depth + (uintptr_t) (IM)->data) +#define ROW(IM, Y) PIXEL(IM, 0, Y) + struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format) { struct mImage* image = calloc(1, sizeof(struct mImage)); if (!image) { @@ -107,6 +109,47 @@ struct mImage* mImageLoadVF(struct VFile* vf) { return NULL; } +struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorFormat format) { + struct mImage* newImage = calloc(1, sizeof(*newImage)); + newImage->width = image->width; + newImage->height = image->height; + newImage->format = format; + if (format == image->format) { + newImage->depth = image->depth; + newImage->stride = image->stride; + newImage->data = malloc(image->stride * image->height * image->depth); + memcpy(newImage->data, image->data, image->stride * image->height * image->depth); + return newImage; + } + newImage->depth = mColorFormatBytes(format); + newImage->stride = image->width; + newImage->data = malloc(image->width * image->height * newImage->depth); + + // 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 = 0; + memcpy(&color, (void*) src, image->depth); +#ifdef __BIG_ENDIAN__ + if (image->depth < 4) { + color >>= (32 - 8 * image->depth); + } +#endif + color = mColorConvert(color, image->format, format); +#ifdef __BIG_ENDIAN__ + if (newImage->depth < 4) { + color <<= (32 - 8 * newImage->depth); + } +#endif + memcpy((void*) dst, &color, newImage->depth); + } + } + return newImage; +} + void mImageDestroy(struct mImage* image) { free(image->data); free(image); diff --git a/src/util/test/image.c b/src/util/test/image.c index 579e8c44b..7903a2dcb 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -502,6 +502,156 @@ M_TEST_DEFINE(loadPng32) { } #endif +M_TEST_DEFINE(convert1x1) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = mImageCreate(1, 1, formats[i]); + mImageSetPixel(src, 0, 0, 0xFF181818); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(convert2x1) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = mImageCreate(2, 1, formats[i]); + mImageSetPixel(src, 0, 0, 0xFF181818); + mImageSetPixel(src, 1, 0, 0xFF101010); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + assert_int_equal(mImageGetPixel(dst, 1, 0), 0xFF101010); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(convert1x2) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = calloc(1, sizeof(*src)); + src->width = 1; + src->height = 2; + src->stride = 8; // Use an unusual stride to make sure the right parts get copied + src->format = formats[i]; + src->depth = mColorFormatBytes(src->format); + src->data = calloc(src->stride * src->depth, src->height); + mImageSetPixel(src, 0, 0, 0xFF181818); + mImageSetPixel(src, 0, 1, 0xFF101010); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + assert_int_equal(mImageGetPixel(dst, 0, 1), 0xFF101010); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(convert2x2) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = calloc(1, sizeof(*src)); + src->width = 2; + src->height = 2; + src->stride = 8; // Use an unusual stride to make sure the right parts get copied + src->format = formats[i]; + src->depth = mColorFormatBytes(src->format); + src->data = calloc(src->stride * src->depth, src->height); + mImageSetPixel(src, 0, 0, 0xFF181818); + mImageSetPixel(src, 0, 1, 0xFF101010); + mImageSetPixel(src, 1, 0, 0xFF000000); + mImageSetPixel(src, 1, 1, 0xFF080808); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + assert_int_equal(mImageGetPixel(dst, 0, 1), 0xFF101010); + assert_int_equal(mImageGetPixel(dst, 1, 0), 0xFF000000); + assert_int_equal(mImageGetPixel(dst, 1, 1), 0xFF080808); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(pitchRead), cmocka_unit_test(strideRead), @@ -513,4 +663,8 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(loadPng24), cmocka_unit_test(loadPng32), #endif + cmocka_unit_test(convert1x1), + cmocka_unit_test(convert2x1), + cmocka_unit_test(convert1x2), + cmocka_unit_test(convert2x2), )