Util: Add alpha-based mImage compositing functions

This commit is contained in:
Vicki Pfau 2023-04-03 02:45:49 -07:00
parent cfd5572fb6
commit c884560fdb
3 changed files with 166 additions and 36 deletions

View File

@ -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

View File

@ -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;

View File

@ -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),
) )