diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index b1c14c8ee..74793af82 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -22,9 +22,9 @@ typedef uint32_t color_t; #define M_G5(X) (((X) >> 5) & 0x1F) #define M_B5(X) (((X) >> 10) & 0x1F) -#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 2) -#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 2) -#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 2) +#define M_R8(X) ((M_R5(X) * 0x21) >> 2) +#define M_G8(X) ((M_G5(X) * 0x21) >> 2) +#define M_B8(X) ((M_B5(X) * 0x21) >> 2) #define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) #define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3)) @@ -81,7 +81,54 @@ enum mColorFormat { mCOLOR_ANY = -1 }; +struct mImage { + void* data; + unsigned width; + unsigned height; + unsigned stride; + unsigned depth; + enum mColorFormat format; +}; + +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); +void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color); + +uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); + #ifndef PYCPARSE +static inline unsigned mColorFormatBytes(enum mColorFormat format) { + switch (format) { + case mCOLOR_XBGR8: + case mCOLOR_XRGB8: + case mCOLOR_BGRX8: + case mCOLOR_RGBX8: + case mCOLOR_ABGR8: + case mCOLOR_ARGB8: + case mCOLOR_BGRA8: + case mCOLOR_RGBA8: + return 4; + case mCOLOR_RGB5: + case mCOLOR_BGR5: + case mCOLOR_RGB565: + case mCOLOR_BGR565: + case mCOLOR_ARGB5: + case mCOLOR_ABGR5: + case mCOLOR_RGBA5: + case mCOLOR_BGRA5: + return 2; + case mCOLOR_RGB8: + case mCOLOR_BGR8: + return 3; + case mCOLOR_L8: + return 1; + case mCOLOR_ANY: + break; + } + return 0; +} + static inline color_t mColorFrom555(uint16_t value) { #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 11c9d27d1..eb82215d7 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCE_FILES convolve.c elf-read.c geometry.c + image.c image/export.c image/png-io.c patch.c @@ -34,7 +35,9 @@ set(GUI_FILES gui/menu.c) set(TEST_FILES + test/color.c test/geometry.c + test/image.c test/sfo.c test/string-parser.c test/string-utf8.c diff --git a/src/util/image.c b/src/util/image.c new file mode 100644 index 000000000..73018cc29 --- /dev/null +++ b/src/util/image.c @@ -0,0 +1,269 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#define PIXEL(IM, X, Y) \ + (void*) (((IM)->stride * (Y) + (X)) * (IM)->depth + (uintptr_t) (IM)->data) + +uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) { + if (x >= image->width || y >= image->height) { + return 0; + } + const void* pixel = PIXEL(image, x, y); + uint32_t color; + switch (image->depth) { + case 1: + color = *(const uint8_t*) pixel; + break; + case 2: + color = *(const uint16_t*) pixel; + break; + case 4: + color = *(const uint32_t*) pixel; + break; + case 3: +#ifdef __BIG_ENDIAN__ + color = ((const uint8_t*) pixel)[0] << 16; + color |= ((const uint8_t*) pixel)[1] << 8; + color |= ((const uint8_t*) pixel)[2]; +#else + color = ((const uint8_t*) pixel)[0]; + color |= ((const uint8_t*) pixel)[1] << 8; + color |= ((const uint8_t*) pixel)[2] << 16; +#endif + break; + } + return color; +} + +uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y) { + return mColorConvert(mImageGetPixelRaw(image, x, y), image->format, mCOLOR_ARGB8); +} + +void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color) { + if (x >= image->width || y >= image->height) { + return; + } + void* pixel = PIXEL(image, x, y); + switch (image->depth) { + case 1: + *(uint8_t*) pixel = color; + break; + case 2: + *(uint16_t*) pixel = color; + break; + case 4: + *(uint32_t*) pixel = color; + break; + case 3: +#ifdef __BIG_ENDIAN__ + ((uint8_t*) pixel)[0] = color >> 16; + ((uint8_t*) pixel)[1] = color >> 8; + ((uint8_t*) pixel)[2] = color; +#else + ((uint8_t*) pixel)[0] = color; + ((uint8_t*) pixel)[1] = color >> 8; + ((uint8_t*) pixel)[2] = color >> 16; +#endif + break; + } +} + +void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color) { + mImageSetPixelRaw(image, x, y, mColorConvert(color, mCOLOR_ARGB8, image->format)); +} + +uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { + if (from == to) { + return color; + } + + int r; + int g; + int b; + int a = 0xFF; + + switch (from) { + case mCOLOR_ARGB8: + a = color >> 24; + // Fall through + case mCOLOR_XRGB8: + case mCOLOR_RGB8: + r = (color >> 16) & 0xFF; + g = (color >> 8) & 0xFF; + b = color & 0xFF; + break; + + case mCOLOR_ABGR8: + a = color >> 24; + // Fall through + case mCOLOR_XBGR8: + case mCOLOR_BGR8: + b = (color >> 16) & 0xFF; + g = (color >> 8) & 0xFF; + r = color & 0xFF; + break; + + case mCOLOR_RGBA8: + a = color & 0xFF; + // Fall through + case mCOLOR_RGBX8: + r = (color >> 24) & 0xFF; + g = (color >> 16) & 0xFF; + b = (color >> 8) & 0xFF; + break; + + case mCOLOR_BGRA8: + a = color & 0xFF; + // Fall through + case mCOLOR_BGRX8: + b = (color >> 24) & 0xFF; + g = (color >> 16) & 0xFF; + r = (color >> 8) & 0xFF; + break; + + case mCOLOR_ARGB5: + a = (color >> 15) * 0xFF; + // Fall through + case mCOLOR_RGB5: + r = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x1F) * 0x21) >> 2; + b = ((color & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_ABGR5: + a = (color >> 15) * 0xFF; + // Fall through + case mCOLOR_BGR5: + b = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x1F) * 0x21) >> 2; + r = ((color & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_RGBA5: + a = (color & 1) * 0xFF; + r = (((color >> 11) & 0x1F) * 0x21) >> 2; + g = (((color >> 6) & 0x1F) * 0x21) >> 2; + b = (((color >> 1) & 0x1F) * 0x21) >> 2; + break; + case mCOLOR_BGRA5: + a = (color & 1) * 0xFF; + b = (((color >> 11) & 0x1F) * 0x21) >> 2; + g = (((color >> 6) & 0x1F) * 0x21) >> 2; + r = (((color >> 1) & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_RGB565: + r = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x3F) * 0x41) >> 4; + b = ((color & 0x1F) * 0x21) >> 2; + break; + case mCOLOR_BGR565: + b = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x3F) * 0x41) >> 4; + r = ((color & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_L8: + r = color; + g = color; + b = color; + break; + + case mCOLOR_ANY: + return 0; + } + + color = 0; + switch (to) { + case mCOLOR_XRGB8: + a = 0xFF; + // Fall through + case mCOLOR_ARGB8: + color |= a << 24; + // Fall through + case mCOLOR_RGB8: + color |= r << 16; + color |= g << 8; + color |= b; + break; + case mCOLOR_XBGR8: + a = 0xFF; + // Fall through + case mCOLOR_ABGR8: + color |= a << 24; + // Fall through + case mCOLOR_BGR8: + color |= b << 16; + color |= g << 8; + color |= r; + break; + case mCOLOR_RGBX8: + a = 0xFF; + // Fall through + case mCOLOR_RGBA8: + color |= a; + color |= r << 24; + color |= g << 16; + color |= b << 8; + break; + case mCOLOR_BGRX8: + a = 0xFF; + // Fall through + case mCOLOR_BGRA8: + color |= a; + color |= b << 24; + color |= g << 16; + color |= r << 8; + break; + case mCOLOR_ARGB5: + color |= (!!a << 15); + // Fall through + case mCOLOR_RGB5: + color |= (r >> 3) << 10; + color |= (g >> 3) << 5; + color |= b >> 3; + break; + case mCOLOR_ABGR5: + color |= (!!a << 15); + // Fall through + case mCOLOR_BGR5: + color |= (b >> 3) << 10; + color |= (g >> 3) << 5; + color |= r >> 3; + break; + case mCOLOR_RGBA5: + color |= !!a; + color |= (r >> 3) << 11; + color |= (g >> 3) << 6; + color |= (b >> 3) << 1; + break; + case mCOLOR_BGRA5: + color |= !!a; + color |= (b >> 3) << 11; + color |= (g >> 3) << 6; + color |= (r >> 3) << 1; + break; + case mCOLOR_RGB565: + color |= (r >> 3) << 11; + color |= (g >> 2) << 5; + color |= b >> 3; + break; + case mCOLOR_BGR565: + color |= (b >> 3) << 11; + color |= (g >> 2) << 5; + color |= r >> 3; + break; + case mCOLOR_L8: + // sRGB primaries in fixed point, roughly fudged to saturate to 0xFFFF + color = (55 * r + 184 * g + 18 * b) >> 8; + break; + case mCOLOR_ANY: + return 0; + } + + return color; +} diff --git a/src/util/test/color.c b/src/util/test/color.c new file mode 100644 index 000000000..ab4b58ac3 --- /dev/null +++ b/src/util/test/color.c @@ -0,0 +1,163 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include + +M_TEST_DEFINE(channelSwap32) { + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_ABGR8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ABGR8, mCOLOR_ARGB8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_XRGB8, mCOLOR_XBGR8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_XBGR8, mCOLOR_XRGB8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xAABBCC, mCOLOR_RGB8, mCOLOR_BGR8), 0xCCBBAA); + assert_int_equal(mColorConvert(0xAABBCC, mCOLOR_BGR8, mCOLOR_RGB8), 0xCCBBAA); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_RGBA8), 0xAABBCCFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ABGR8, mCOLOR_BGRA8), 0xAABBCCFF); + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_RGBA8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_BGRA8, mCOLOR_ABGR8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_BGRA8), 0xCCBBAAFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ABGR8, mCOLOR_RGBA8), 0xCCBBAAFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_RGBA8, mCOLOR_ABGR8), 0xCCBBAAFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_BGRA8, mCOLOR_ARGB8), 0xCCBBAAFF); +} + +M_TEST_DEFINE(channelSwap16) { + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_ARGB5, mCOLOR_ABGR5), 0x83FF); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_ABGR5, mCOLOR_ARGB5), 0x83FF); + assert_int_equal(mColorConvert(0x7FE0, mCOLOR_RGB5, mCOLOR_BGR5), 0x03FF); + assert_int_equal(mColorConvert(0x7FE0, mCOLOR_BGR5, mCOLOR_RGB5), 0x03FF); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_RGB565, mCOLOR_BGR565), 0x07FF); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_BGR565, mCOLOR_RGB565), 0x07FF); + + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_ARGB5, mCOLOR_RGBA5), 0xFFC1); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_RGBA5, mCOLOR_ARGB5), 0x7FF0); +} + +M_TEST_DEFINE(convertQuantizeOpaque) { + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0x1B << 6) | (0x1C << 1) | 1); + + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0x1B << 6) | (0x1A << 1) | 1); + + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0x1C << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0x1B << 7) | (0x2C << 1) | 1); + assert_int_equal(mColorConvert(0xACBCCC, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0x1B << 7) | (0x3C << 1) | 1); + + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0x1A << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0x1B << 7) | (0x2A << 1) | 1); + assert_int_equal(mColorConvert(0xACBCCC, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0x1B << 7) | (0x3A << 1) | 1); +} + +M_TEST_DEFINE(convertQuantizeTransparent) { + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), 0x8000 | (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), 0x8000 | (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), 0x8000 | (0xA << 11) | (0xB << 6) | (0xC << 1)); + + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), 0x8000 | (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), 0x8000 | (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), 0x8000 | (0xC << 11) | (0xB << 6) | (0xA << 1)); + + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), 1 | (0xA << 12) | (0xB << 7) | (0xC << 2)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), (0xA << 12) | (0xB << 7) | (0xC << 2)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), 1 | (0xA << 12) | (0xB << 7) | (0xC << 2)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), 1 | (0xA << 12) | (0xB << 7) | (0xC << 2)); + + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), 1 | (0xC << 12) | (0xB << 7) | (0xA << 2)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), (0xC << 12) | (0xB << 7) | (0xA << 2)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), 1 | (0xC << 12) | (0xB << 7) | (0xA << 2)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), 1 | (0xC << 12) | (0xB << 7) | (0xA << 2)); +} + +M_TEST_DEFINE(convertToOpaque) { + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCCFE, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCC01, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCC00, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xAABBCCFE, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xAABBCC01, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xAABBCC00, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + + assert_int_equal(mColorConvert(0x7FFF, mCOLOR_ARGB5, mCOLOR_RGB5), 0x7FFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_ARGB5, mCOLOR_RGB5), 0x7FFF); + + assert_int_equal(mColorConvert(0xFFFE, mCOLOR_RGBA5, mCOLOR_RGB5), 0x7FFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_RGBA5, mCOLOR_RGB5), 0x7FFF); +} + +M_TEST_DEFINE(convertToAlpha) { + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0x7FFF, mCOLOR_RGB5, mCOLOR_ARGB5), 0xFFFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_RGB5, mCOLOR_ARGB5), 0xFFFF); + + assert_int_equal(mColorConvert(0x7FFF, mCOLOR_RGB5, mCOLOR_RGBA5), 0xFFFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_RGB5, mCOLOR_RGBA5), 0xFFFF); +} + +M_TEST_DEFINE(convertFromGray) { + int i; + for (i = 0; i < 256; ++i) { + assert_int_equal(mColorConvert(i, mCOLOR_L8, mCOLOR_RGB8), (i << 16) | (i << 8) | i); + assert_int_equal(mColorConvert(i, mCOLOR_L8, mCOLOR_ARGB8), 0xFF000000 | (i << 16) | (i << 8) | i); + } +} + +M_TEST_DEFINE(convertToGray) { + int i; + for (i = 0; i < 256; ++i) { + assert_int_equal(mColorConvert((i << 16) | (i << 8) | i, mCOLOR_RGB8, mCOLOR_L8), i); + assert_int_equal(mColorConvert((i << 16) | (i << 8) | i, mCOLOR_ARGB8, mCOLOR_L8), i); + assert_int_equal(mColorConvert(0xFF000000 | (i << 16) | (i << 8) | i, mCOLOR_ARGB8, mCOLOR_L8), i); + } +} + +M_TEST_SUITE_DEFINE(Color, + cmocka_unit_test(channelSwap32), + cmocka_unit_test(channelSwap16), + cmocka_unit_test(convertQuantizeOpaque), + cmocka_unit_test(convertQuantizeTransparent), + cmocka_unit_test(convertToOpaque), + cmocka_unit_test(convertToAlpha), + cmocka_unit_test(convertFromGray), + cmocka_unit_test(convertToGray), +) diff --git a/src/util/test/image.c b/src/util/test/image.c new file mode 100644 index 000000000..518515c83 --- /dev/null +++ b/src/util/test/image.c @@ -0,0 +1,450 @@ +/* Copyright (c) 2013-2023 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include + +M_TEST_DEFINE(pitchRead) { + static uint8_t buffer[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + + struct mImage image = { + .data = buffer, + .height = 1 + }; + int i; + + image.depth = 1; + image.width = 12; + image.format = mCOLOR_L8; + + for (i = 0; i < 12; ++i) { + assert_int_equal(mImageGetPixelRaw(&image, i, 0), i); + } + + image.depth = 2; + image.width = 6; + image.format = mCOLOR_RGB5; + + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 2) << 8 | (i * 2 + 1)); +#else + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 2 + 1) << 8 | (i * 2)); +#endif + } + + image.depth = 3; + image.width = 4; + image.format = mCOLOR_RGB8; + + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + + image.depth = 4; + image.width = 3; + image.format = mCOLOR_ARGB8; + + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } +} + +M_TEST_DEFINE(strideRead) { + static uint8_t buffer[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + + struct mImage image = { + .data = buffer, + .width = 1 + }; + int i; + + image.depth = 1; + image.stride = 1; + image.height = 12; + image.format = mCOLOR_L8; + + for (i = 0; i < 12; ++i) { + assert_int_equal(mImageGetPixelRaw(&image, 0, i), i); + } + + image.depth = 1; + image.stride = 2; + image.height = 6; + image.format = mCOLOR_L8; + + for (i = 0; i < 6; ++i) { + assert_int_equal(mImageGetPixelRaw(&image, 0, i), i * 2); + } + + image.depth = 2; + image.stride = 1; + image.height = 6; + image.format = mCOLOR_RGB5; + + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 2) << 8 | (i * 2 + 1)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 2 + 1) << 8 | (i * 2)); +#endif + } + + image.depth = 2; + image.stride = 2; + image.height = 3; + image.format = mCOLOR_RGB5; + + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4) << 8 | (i * 4 + 1)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4 + 1) << 8 | (i * 4)); +#endif + } + + image.depth = 3; + image.stride = 1; + image.height = 4; + image.format = mCOLOR_RGB8; + + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + + image.depth = 3; + image.stride = 2; + image.height = 2; + image.format = mCOLOR_RGB8; + + for (i = 0; i < 2; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 6) << 16 | (i * 6 + 1) << 8 | (i * 6 + 2)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 6 + 2) << 16 | (i * 6 + 1) << 8 | (i * 6)); +#endif + } + + image.depth = 4; + image.stride = 1; + image.height = 3; + image.format = mCOLOR_ARGB8; + + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } +} + +M_TEST_DEFINE(oobRead) { + static uint8_t buffer[8] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + + struct mImage image = { + .data = buffer, + .width = 1, + .height = 1, + .stride = 1 + }; + + image.depth = 1; + image.format = mCOLOR_L8; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); + + image.depth = 2; + image.format = mCOLOR_RGB5; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFFFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); + + image.depth = 3; + image.format = mCOLOR_RGB8; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFFFFFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); + + image.depth = 4; + image.format = mCOLOR_ARGB8; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFFFFFFFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); +} + +M_TEST_DEFINE(pitchWrite) { + static const uint8_t baseline[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + + uint8_t buffer[12]; + + struct mImage image = { + .data = buffer, + .height = 1 + }; + int i; + + image.depth = 1; + image.width = 12; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 12; ++i) { + mImageSetPixelRaw(&image, i, 0, i); + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); + + image.depth = 2; + image.width = 6; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, i, 0, (i * 2) << 8 | (i * 2 + 1)); +#else + mImageSetPixelRaw(&image, i, 0, (i * 2 + 1) << 8 | (i * 2)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); + + image.depth = 3; + image.width = 4; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, i, 0, (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + mImageSetPixelRaw(&image, i, 0, (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); + + image.depth = 4; + image.width = 3; + image.format = mCOLOR_ARGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, i, 0, (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + mImageSetPixelRaw(&image, i, 0, (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); +} + +M_TEST_DEFINE(strideWrite) { + static const uint8_t baseline[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + static const uint8_t baseline2x1[12] = { + 0x0, 0x0, 0x2, 0x0, 0x4, 0x0, 0x6, 0x0, 0x8, 0x0, 0xA, 0x0 + }; + static const uint8_t baseline2x2[12] = { + 0x0, 0x1, 0x0, 0x0, 0x4, 0x5, 0x0, 0x0, 0x8, 0x9, 0x0, 0x0 + }; + static const uint8_t baseline3x2[12] = { + 0x0, 0x1, 0x2, 0x0, 0x0, 0x0, 0x6, 0x7, 0x8, 0x0, 0x0, 0x0 + }; + + uint8_t buffer[12]; + + struct mImage image = { + .data = buffer, + .width = 1 + }; + int i; + + image.depth = 1; + image.stride = 1; + image.height = 12; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 12; ++i) { + mImageSetPixelRaw(&image, 0, i, i); + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); + + image.depth = 1; + image.stride = 2; + image.height = 6; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 6; ++i) { + mImageSetPixelRaw(&image, 0, i, i * 2); + } + assert_memory_equal(baseline2x1, buffer, sizeof(buffer)); + + image.depth = 2; + image.stride = 1; + image.height = 6; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 2) << 8 | (i * 2 + 1)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 2 + 1) << 8 | (i * 2)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); + + image.depth = 2; + image.stride = 2; + image.height = 3; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 4) << 8 | (i * 4 + 1)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 4 + 1) << 8 | (i * 4)); +#endif + } + assert_memory_equal(baseline2x2, buffer, sizeof(buffer)); + + image.depth = 3; + image.stride = 1; + image.height = 4; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); + + image.depth = 3; + image.stride = 2; + image.height = 2; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 2; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 6) << 16 | (i * 6 + 1) << 8 | (i * 6 + 2)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 6 + 2) << 16 | (i * 6 + 1) << 8 | (i * 6)); +#endif + } + assert_memory_equal(baseline3x2, buffer, sizeof(buffer)); + + image.depth = 4; + image.stride = 1; + image.height = 3; + image.format = mCOLOR_ARGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); +} + +M_TEST_DEFINE(oobWrite) { + static uint8_t buffer[8]; + + struct mImage image = { + .data = buffer, + .width = 1, + .height = 1, + .stride = 1 + }; + + image.depth = 1; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF }), sizeof(buffer)); + + image.depth = 2; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFFFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF }), sizeof(buffer)); + + image.depth = 3; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFFFFFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF }), sizeof(buffer)); + + image.depth = 4; + image.format = mCOLOR_ARGB8; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFFFFFFFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF, 0xFF }), sizeof(buffer)); +} + +M_TEST_SUITE_DEFINE(Image, + cmocka_unit_test(pitchRead), + cmocka_unit_test(strideRead), + cmocka_unit_test(oobRead), + cmocka_unit_test(pitchWrite), + cmocka_unit_test(strideWrite), + cmocka_unit_test(oobWrite), +)