diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 83dec79a7..6dd3cee73 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -108,6 +108,8 @@ void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color); void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y); +void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y); +void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha); uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); @@ -250,6 +252,56 @@ ATTRIBUTE_UNUSED static unsigned mColorMix5Bit(int weightA, unsigned colorA, int #endif return c; } + +ATTRIBUTE_UNUSED static uint32_t mColorMixARGB8(uint32_t colorA, uint32_t colorB) { + uint32_t alpha = colorA >> 24; + if (!alpha) { + return colorB; + } + + uint32_t color = 0; + uint32_t a, b; + a = colorA & 0xFF00FF; + a *= alpha + 1; + color += (a >> 8) & 0xFF00FF; + + a = colorB & 0xFF00FF; + a *= 0x100 - alpha; + color += (a >> 8) & 0xFF00FF; + + if (color & 0x100) { + color &= ~0xFF; + color |= 0xFF; + } + if (color & 0x1000000) { + color &= ~0xFF0000; + color |= 0xFF0000; + } + + b = 0; + a = colorA & 0xFF00; + a *= alpha + 1; + b += a & 0xFF0000; + + a = colorB & 0xFF00; + a *= 0x100 - alpha; + b += a & 0xFF0000; + + if (b & 0x1000000) { + b &= ~0xFF0000; + b |= 0xFF0000; + } + color |= b >> 8; + + alpha += colorB >> 24; + if (alpha > 0xFF) { + color |= 0xFF000000; + } else { + color |= alpha << 24; + } + + return color; +} #endif CXX_GUARD_END diff --git a/src/util/image.c b/src/util/image.c index 722c13857..e37089d2a 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -318,43 +318,43 @@ void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color mImageSetPixelRaw(image, x, y, mColorConvert(color, mCOLOR_ARGB8, image->format)); } +#define COMPOSITE_BOUNDS_INIT \ + struct mRectangle dstRect = { \ + .x = 0, \ + .y = 0, \ + .width = image->width, \ + .height = image->height \ + }; \ + struct mRectangle srcRect = { \ + .x = x, \ + .y = y, \ + .width = source->width, \ + .height = source->height \ + }; \ + if (!mRectangleIntersection(&srcRect, &dstRect)) { \ + return; \ + } \ + int srcStartX; \ + int srcStartY; \ + int dstStartX; \ + int dstStartY; \ + if (x < 0) { \ + dstStartX = 0; \ + srcStartX = -x; \ + } else { \ + srcStartX = 0; \ + dstStartX = srcRect.x; \ + } \ + if (y < 0) { \ + dstStartY = 0; \ + srcStartY = -y; \ + } else { \ + srcStartY = 0; \ + dstStartY = srcRect.y; \ + } + void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) { - struct mRectangle dstRect = { - .x = 0, - .y = 0, - .width = image->width, - .height = image->height - }; - struct mRectangle srcRect = { - .x = x, - .y = y, - .width = source->width, - .height = source->height - }; - if (!mRectangleIntersection(&srcRect, &dstRect)) { - return; - } - - int srcStartX; - int srcStartY; - int dstStartX; - int dstStartY; - - if (x < 0) { - dstStartX = 0; - srcStartX = -x; - } else { - srcStartX = 0; - dstStartX = srcRect.x; - } - - if (y < 0) { - dstStartY = 0; - srcStartY = -y; - } else { - srcStartY = 0; - dstStartY = srcRect.y; - } + COMPOSITE_BOUNDS_INIT; for (y = 0; y < srcRect.height; ++y) { uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); @@ -368,6 +368,74 @@ void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) } } +void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y) { + if (!mColorFormatHasAlpha(source->format)) { + mImageBlit(image, source, x, y); + return; + } + + COMPOSITE_BOUNDS_INIT; + + for (y = 0; y < srcRect.height; ++y) { + uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); + uintptr_t dstPixel = (uintptr_t) PIXEL(image, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { + uint32_t color, colorB; + GET_PIXEL(color, srcPixel, source->depth); + color = mColorConvert(color, source->format, mCOLOR_ARGB8); + if (color < 0xFF000000) { + GET_PIXEL(colorB, dstPixel, image->depth); + colorB = mColorConvert(colorB, image->format, mCOLOR_ARGB8); + color = mColorMixARGB8(color, colorB); + } + color = mColorConvert(color, mCOLOR_ARGB8, image->format); + PUT_PIXEL(color, dstPixel, image->depth); + } + } +} + +void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha) { + if (alpha >= 1 && alpha < 257.f / 256.f) { + mImageComposite(image, source, x, y); + return; + } + if (alpha <= 0) { + return; + } + if (alpha > 256) { + // TODO: Add a slow path for alpha > 1, since we need to check saturation only on this path + alpha = 256; + } + + COMPOSITE_BOUNDS_INIT; + + int fixedAlpha = alpha * 0x200; + + for (y = 0; y < srcRect.height; ++y) { + uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); + uintptr_t dstPixel = (uintptr_t) PIXEL(image, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { + uint32_t color, colorB; + GET_PIXEL(color, srcPixel, source->depth); + color = mColorConvert(color, source->format, mCOLOR_ARGB8); + uint32_t alpha = (color >> 24) * fixedAlpha; + alpha >>= 9; + if (alpha > 0xFF) { + alpha = 0xFF; + } + color &= 0x00FFFFFF; + color |= alpha << 24; + + GET_PIXEL(colorB, dstPixel, image->depth); + colorB = mColorConvert(colorB, image->format, mCOLOR_ARGB8); + + color = mColorMixARGB8(color, colorB); + color = mColorConvert(color, mCOLOR_ARGB8, image->format); + PUT_PIXEL(color, dstPixel, image->depth); + } + } +} + uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { if (from == to) { return color; diff --git a/src/util/test/color.c b/src/util/test/color.c index ab4b58ac3..f01fd2d2f 100644 --- a/src/util/test/color.c +++ b/src/util/test/color.c @@ -151,6 +151,15 @@ M_TEST_DEFINE(convertToGray) { } } +M_TEST_DEFINE(alphaBlendARGB8) { + assert_int_equal(mColorMixARGB8(0xFF012345, 0xFF987654), 0xFF012345); + assert_int_equal(mColorMixARGB8(0x00012345, 0xFF987654), 0xFF987654); + assert_int_equal(mColorMixARGB8(0x80012345, 0xFF987654), 0xFF4C4C4C); + assert_int_equal(mColorMixARGB8(0x80012345, 0x40987654), 0xC04C4C4C); + assert_int_equal(mColorMixARGB8(0x01012345, 0xFF987654), 0xFF977553); + assert_int_equal(mColorMixARGB8(0x01012345, 0xFD987654), 0xFE977553); +} + M_TEST_SUITE_DEFINE(Color, cmocka_unit_test(channelSwap32), cmocka_unit_test(channelSwap16), @@ -160,4 +169,5 @@ M_TEST_SUITE_DEFINE(Color, cmocka_unit_test(convertToAlpha), cmocka_unit_test(convertFromGray), cmocka_unit_test(convertToGray), + cmocka_unit_test(alphaBlendARGB8), )