diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 74793af82..544e38e54 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -90,6 +90,12 @@ struct mImage { enum mColorFormat format; }; +struct VFile; +struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format); +struct mImage* mImageLoad(const char* path); +struct mImage* mImageLoadVF(struct VFile* vf); +void mImageDestroy(struct mImage*); + uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y); uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y); void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color); diff --git a/src/util/image.c b/src/util/image.c index 73018cc29..87807a85c 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -5,9 +5,108 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include +#include + #define PIXEL(IM, X, Y) \ (void*) (((IM)->stride * (Y) + (X)) * (IM)->depth + (uintptr_t) (IM)->data) +struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format) { + struct mImage* image = calloc(1, sizeof(struct mImage)); + if (!image) { + return NULL; + } + image->width = width; + image->height = height; + image->stride = width; + image->format = format; + image->depth = mColorFormatBytes(format); + image->data = calloc(width * height, image->depth); + if (!image->data) { + free(image); + return NULL; + } + return image; +} + +struct mImage* mImageLoad(const char* path) { + struct VFile* vf = VFileOpen(path, O_RDONLY); + if (!vf) { + return NULL; + } + struct mImage* image = mImageLoadVF(vf); + vf->close(vf); + return image; +} + +static struct mImage* mImageLoadPNG(struct VFile* vf) { + png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end) { + PNGReadClose(png, info, end); + return NULL; + } + + if (!PNGReadHeader(png, info)) { + PNGReadClose(png, info, end); + return NULL; + } + unsigned width = png_get_image_width(png, info); + unsigned height = png_get_image_height(png, info); + + struct mImage* image = calloc(1, sizeof(*image)); + + image->width = width; + image->height = height; + image->stride = width; + + switch (png_get_channels(png, info)) { + case 3: + image->format = mCOLOR_XBGR8; + image->depth = 4; + image->data = malloc(width * height * 4); + if (!PNGReadPixels(png, info, image->data, width, height, width)) { + free(image->data); + free(image); + PNGReadClose(png, info, end); + return NULL; + } + break; + case 4: + image->format = mCOLOR_ABGR8; + image->depth = 4; + image->data = malloc(width * height * 4); + if (!PNGReadPixelsA(png, info, image->data, width, height, width)) { + free(image->data); + free(image); + PNGReadClose(png, info, end); + return NULL; + } + break; + default: + // Not supported yet + free(image); + PNGReadClose(png, info, end); + return NULL; + } + return image; +} + +struct mImage* mImageLoadVF(struct VFile* vf) { + vf->seek(vf, 0, SEEK_SET); + if (isPNG(vf)) { + return mImageLoadPNG(vf); + } + vf->seek(vf, 0, SEEK_SET); + return NULL; +} + +void mImageDestroy(struct mImage* image) { + free(image->data); + free(image); +} + uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) { if (x >= image->width || y >= image->height) { return 0; diff --git a/src/util/test/image.c b/src/util/test/image.c index 518515c83..fd76c3d51 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -6,6 +6,7 @@ #include "util/test/suite.h" #include +#include M_TEST_DEFINE(pitchRead) { static uint8_t buffer[12] = { @@ -440,6 +441,65 @@ M_TEST_DEFINE(oobWrite) { assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF, 0xFF }), sizeof(buffer)); } +M_TEST_DEFINE(loadPng24) { + 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, 0x02, 0x00, 0x00, 0x00, 0xfd, 0xd4, 0x9a, 0x73, 0x00, 0x00, 0x00, + 0x12, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0xf8, 0xff, 0xff, 0x3f, + 0x03, 0x03, 0x03, 0x03, 0x84, 0x00, 0x00, 0x2a, 0xe3, 0x04, 0xfc, 0xe8, + 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 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_XBGR8); + + assert_int_equal(mImageGetPixel(image, 0, 0) & 0xFFFFFF, 0xFFFFFF); + assert_int_equal(mImageGetPixel(image, 1, 0) & 0xFFFFFF, 0x000000); + assert_int_equal(mImageGetPixel(image, 0, 1) & 0xFFFFFF, 0xFF0000); + assert_int_equal(mImageGetPixel(image, 1, 1) & 0xFFFFFF, 0x0000FF); + + mImageDestroy(image); +} + + +M_TEST_DEFINE(loadPng32) { + unsigned char 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, + 0x1a, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x05, 0xc1, 0x31, 0x01, 0x00, + 0x00, 0x08, 0xc0, 0x20, 0x6c, 0x66, 0x25, 0xfb, 0x1f, 0x13, 0xa6, 0x0a, + 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 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_ABGR8); + + assert_int_equal(mImageGetPixel(image, 0, 0) >> 24, 0xFF); + assert_int_equal(mImageGetPixel(image, 1, 0) >> 24, 0x70); + assert_int_equal(mImageGetPixel(image, 0, 1) >> 24, 0x40); + assert_int_equal(mImageGetPixel(image, 1, 1) >> 24, 0x00); + + mImageDestroy(image); +} + M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(pitchRead), cmocka_unit_test(strideRead), @@ -447,4 +507,6 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(pitchWrite), cmocka_unit_test(strideWrite), cmocka_unit_test(oobWrite), + cmocka_unit_test(loadPng24), + cmocka_unit_test(loadPng32), )