mirror of https://github.com/mgba-emu/mgba.git
Util: Add alpha-based mImage compositing functions
This commit is contained in:
parent
cfd5572fb6
commit
c884560fdb
|
@ -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 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 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);
|
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
|
#endif
|
||||||
return c;
|
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
|
#endif
|
||||||
|
|
||||||
CXX_GUARD_END
|
CXX_GUARD_END
|
||||||
|
|
140
src/util/image.c
140
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));
|
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) {
|
void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) {
|
||||||
struct mRectangle dstRect = {
|
COMPOSITE_BOUNDS_INIT;
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (y = 0; y < srcRect.height; ++y) {
|
for (y = 0; y < srcRect.height; ++y) {
|
||||||
uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + 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) {
|
uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) {
|
||||||
if (from == to) {
|
if (from == to) {
|
||||||
return color;
|
return color;
|
||||||
|
|
|
@ -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,
|
M_TEST_SUITE_DEFINE(Color,
|
||||||
cmocka_unit_test(channelSwap32),
|
cmocka_unit_test(channelSwap32),
|
||||||
cmocka_unit_test(channelSwap16),
|
cmocka_unit_test(channelSwap16),
|
||||||
|
@ -160,4 +169,5 @@ M_TEST_SUITE_DEFINE(Color,
|
||||||
cmocka_unit_test(convertToAlpha),
|
cmocka_unit_test(convertToAlpha),
|
||||||
cmocka_unit_test(convertFromGray),
|
cmocka_unit_test(convertFromGray),
|
||||||
cmocka_unit_test(convertToGray),
|
cmocka_unit_test(convertToGray),
|
||||||
|
cmocka_unit_test(alphaBlendARGB8),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue