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 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
|
||||
|
|
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));
|
||||
}
|
||||
|
||||
#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;
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue