mirror of https://github.com/mgba-emu/mgba.git
Scripting: Export new image masking function
This commit is contained in:
parent
4fdadc585d
commit
776d52a2c6
|
@ -138,6 +138,7 @@ void mPainterInit(struct mPainter*, struct mImage* backing);
|
|||
void mPainterDrawRectangle(struct mPainter*, int x, int y, int width, int height);
|
||||
void mPainterDrawLine(struct mPainter*, int x1, int y1, int x2, int y2);
|
||||
void mPainterDrawCircle(struct mPainter*, int x, int y, int diameter);
|
||||
void mPainterDrawMask(struct mPainter*, const struct mImage* mask, int x, int y);
|
||||
|
||||
uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to);
|
||||
uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to);
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
local state = {}
|
||||
state.wheel = image.load(script.dir .. "/wheel.png")
|
||||
state.overlay = canvas:newLayer(state.wheel.width, state.wheel.height)
|
||||
state.painter = image.newPainter(state.overlay.image)
|
||||
state.phase = 0
|
||||
state.speed = 0.01
|
||||
state.painter:setFill(true)
|
||||
state.painter:setStrokeWidth(0)
|
||||
|
||||
function state.update()
|
||||
local r = math.fmod(state.phase * 3, math.pi * 2)
|
||||
local g = math.fmod(state.phase * 5, math.pi * 2)
|
||||
local b = math.fmod(state.phase * 7, math.pi * 2)
|
||||
local color = 0xFF000000
|
||||
color = color | math.floor((math.sin(r) + 1) * 127.5) << 16
|
||||
color = color | math.floor((math.sin(g) + 1) * 127.5) << 8
|
||||
color = color | math.floor((math.sin(b) + 1) * 127.5)
|
||||
|
||||
-- Clear image
|
||||
state.painter:setBlend(false)
|
||||
state.painter:setFillColor(0)
|
||||
state.painter:drawRectangle(0, 0, state.wheel.width, state.wheel.height)
|
||||
-- Draw mask
|
||||
state.painter:setBlend(true)
|
||||
state.painter:setFillColor(color | 0xFF000000)
|
||||
state.painter:drawMask(state.wheel, 0, 0)
|
||||
|
||||
state.overlay:update()
|
||||
state.phase = math.fmod(state.phase + state.speed, math.pi * 2 * 3 * 5 * 7)
|
||||
end
|
||||
|
||||
callbacks:add("frame", state.update)
|
||||
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -1,44 +1,61 @@
|
|||
math.randomseed(os.time())
|
||||
local state = {}
|
||||
state.logo = image.load(script.dir .. "/logo.png")
|
||||
state.overlay = canvas:newLayer(state.logo.width, state.logo.height)
|
||||
state.overlay.image:drawImageOpaque(state.logo, 0, 0)
|
||||
state.x = math.random() * (canvas:screenWidth() - state.logo.width)
|
||||
state.y = math.random() * (canvas:screenHeight() - state.logo.height)
|
||||
state.logo_fg = image.load(script.dir .. "/logo-fg.png")
|
||||
state.logo_bg = image.load(script.dir .. "/logo-bg.png")
|
||||
state.overlay = canvas:newLayer(state.logo_fg.width, state.logo_fg.height)
|
||||
state.x = math.random() * (canvas:screenWidth() - state.logo_fg.width)
|
||||
state.y = math.random() * (canvas:screenHeight() - state.logo_fg.height)
|
||||
state.overlay:setPosition(math.floor(state.x), math.floor(state.y))
|
||||
state.direction = math.floor(math.random() * 3)
|
||||
state.speed = 0.5
|
||||
|
||||
state.overlay:setPosition(math.floor(state.x), math.floor(state.y))
|
||||
state.overlay:update()
|
||||
function state.recolor()
|
||||
local r = math.floor(math.random() * 255)
|
||||
local g = math.floor(math.random() * 255)
|
||||
local b = math.floor(math.random() * 255)
|
||||
local color = 0xFF000000 | (r << 16) | (g << 8) | b
|
||||
state.overlay.image:drawImageOpaque(state.logo_bg, 0, 0)
|
||||
local painter = image.newPainter(state.overlay.image)
|
||||
painter:setFill(true)
|
||||
painter:setFillColor(color)
|
||||
painter:setBlend(true)
|
||||
painter:drawMask(state.logo_fg, 0, 0)
|
||||
state.overlay:update()
|
||||
end
|
||||
|
||||
function state.update()
|
||||
if state.direction & 1 == 1 then
|
||||
state.x = state.x + 1
|
||||
if state.x > canvas:screenWidth() - state.logo.width then
|
||||
state.x = (canvas:screenWidth() - state.logo.width) * 2 - state.x
|
||||
if state.x > canvas:screenWidth() - state.logo_fg.width then
|
||||
state.x = (canvas:screenWidth() - state.logo_fg.width) * 2 - state.x
|
||||
state.direction = state.direction ~ 1
|
||||
state.recolor()
|
||||
end
|
||||
else
|
||||
state.x = state.x - 1
|
||||
if state.x < 0 then
|
||||
state.x = -state.x
|
||||
state.direction = state.direction ~ 1
|
||||
state.recolor()
|
||||
end
|
||||
end
|
||||
if state.direction & 2 == 2 then
|
||||
state.y = state.y + 1
|
||||
if state.y > canvas:screenHeight() - state.logo.height then
|
||||
state.y = (canvas:screenHeight() - state.logo.height) * 2 - state.y
|
||||
if state.y > canvas:screenHeight() - state.logo_fg.height then
|
||||
state.y = (canvas:screenHeight() - state.logo_fg.height) * 2 - state.y
|
||||
state.direction = state.direction ~ 2
|
||||
state.recolor()
|
||||
end
|
||||
else
|
||||
state.y = state.y - 1
|
||||
if state.y < 0 then
|
||||
state.y = -state.y
|
||||
state.direction = state.direction ~ 2
|
||||
state.recolor()
|
||||
end
|
||||
end
|
||||
state.overlay:setPosition(math.floor(state.x), math.floor(state.y))
|
||||
end
|
||||
|
||||
state.recolor()
|
||||
callbacks:add("frame", state.update)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 888 B |
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
|
@ -141,6 +141,7 @@ mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, setStrokeColor, _mPainterSetStrokeC
|
|||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, drawRectangle, mPainterDrawRectangle, 4, S32, x, S32, y, S32, width, S32, height);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, drawLine, mPainterDrawLine, 4, S32, x1, S32, y1, S32, x2, S32, y2);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, drawCircle, mPainterDrawCircle, 3, S32, x, S32, y, S32, diameter);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, drawMask, mPainterDrawMask, 3, CS(mImage), mask, S32, x, S32, y);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mPainter)
|
||||
mSCRIPT_DEFINE_CLASS_DOCSTRING(
|
||||
|
@ -162,6 +163,13 @@ mSCRIPT_DEFINE_STRUCT(mPainter)
|
|||
mSCRIPT_DEFINE_STRUCT_METHOD(mPainter, drawLine)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Draw a circle with the specified diameter with the given origin at the top-left corner of the bounding box")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mPainter, drawCircle)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"Draw a mask image with each color channel multiplied by the current fill color. This can "
|
||||
"be useful for displaying graphics with dynamic colors. By making a grayscale template "
|
||||
"image on a transparent background in advance, a script can set the fill color to a desired "
|
||||
"target color and use this function to draw it into a destination image."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mPainter, drawMask)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptPainter, W(mPainter), _get, _mScriptPainterGet, 1, CHARP, name);
|
||||
|
|
|
@ -42,6 +42,53 @@
|
|||
memcpy((void*) (DST), &_color, (DEPTH)); \
|
||||
} while (0);
|
||||
|
||||
static uint32_t _mColorMultiply(uint32_t colorA, uint32_t colorB) {
|
||||
uint32_t color = 0;
|
||||
|
||||
uint32_t a, b;
|
||||
a = colorA & 0xFF;
|
||||
b = colorB & 0xFF;
|
||||
b = a * b;
|
||||
b /= 0xFF;
|
||||
if (b > 0xFF) {
|
||||
color |= 0xFF;
|
||||
} else {
|
||||
color |= b;
|
||||
}
|
||||
|
||||
a = (colorA >> 8) & 0xFF;
|
||||
b = (colorB >> 8) & 0xFF;
|
||||
b = a * b;
|
||||
b /= 0xFF;
|
||||
if (b > 0xFF) {
|
||||
color |= 0xFF00;
|
||||
} else {
|
||||
color |= b << 8;
|
||||
}
|
||||
|
||||
a = (colorA >> 16) & 0xFF;
|
||||
b = (colorB >> 16) & 0xFF;
|
||||
b = a * b;
|
||||
b /= 0xFF;
|
||||
if (b > 0xFF) {
|
||||
color |= 0xFF0000;
|
||||
} else {
|
||||
color |= b << 16;
|
||||
}
|
||||
|
||||
a = (colorA >> 24) & 0xFF;
|
||||
b = (colorB >> 24) & 0xFF;
|
||||
b = a * b;
|
||||
b /= 0xFF;
|
||||
if (b > 0xFF) {
|
||||
color |= 0xFF000000;
|
||||
} else {
|
||||
color |= b << 24;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format) {
|
||||
return mImageCreateWithStride(width, height, width, format);
|
||||
}
|
||||
|
@ -395,18 +442,18 @@ void mImageSetPaletteEntry(struct mImage* image, unsigned index, uint32_t color)
|
|||
image->palette[index] = color;
|
||||
}
|
||||
|
||||
#define COMPOSITE_BOUNDS_INIT \
|
||||
#define COMPOSITE_BOUNDS_INIT(SOURCE, DEST) \
|
||||
struct mRectangle dstRect = { \
|
||||
.x = 0, \
|
||||
.y = 0, \
|
||||
.width = image->width, \
|
||||
.height = image->height \
|
||||
.width = (DEST)->width, \
|
||||
.height = (DEST)->height \
|
||||
}; \
|
||||
struct mRectangle srcRect = { \
|
||||
.x = x, \
|
||||
.y = y, \
|
||||
.width = source->width, \
|
||||
.height = source->height \
|
||||
.width = (SOURCE)->width, \
|
||||
.height = (SOURCE)->height \
|
||||
}; \
|
||||
if (!mRectangleIntersection(&srcRect, &dstRect)) { \
|
||||
return; \
|
||||
|
@ -436,7 +483,7 @@ void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y)
|
|||
return;
|
||||
}
|
||||
|
||||
COMPOSITE_BOUNDS_INIT;
|
||||
COMPOSITE_BOUNDS_INIT(source, image);
|
||||
|
||||
for (y = 0; y < srcRect.height; ++y) {
|
||||
uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y);
|
||||
|
@ -461,7 +508,7 @@ void mImageComposite(struct mImage* image, const struct mImage* source, int x, i
|
|||
return;
|
||||
}
|
||||
|
||||
COMPOSITE_BOUNDS_INIT;
|
||||
COMPOSITE_BOUNDS_INIT(source, image);
|
||||
|
||||
for (y = 0; y < srcRect.height; ++y) {
|
||||
uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y);
|
||||
|
@ -498,7 +545,7 @@ void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source,
|
|||
alpha = 256;
|
||||
}
|
||||
|
||||
COMPOSITE_BOUNDS_INIT;
|
||||
COMPOSITE_BOUNDS_INIT(source, image);
|
||||
|
||||
int fixedAlpha = alpha * 0x200;
|
||||
|
||||
|
@ -821,6 +868,33 @@ void mPainterDrawCircle(struct mPainter* painter, int x, int y, int diameter) {
|
|||
}
|
||||
}
|
||||
|
||||
void mPainterDrawMask(struct mPainter* painter, const struct mImage* mask, int x, int y) {
|
||||
if (!painter->fill) {
|
||||
return;
|
||||
}
|
||||
|
||||
COMPOSITE_BOUNDS_INIT(mask, painter->backing);
|
||||
|
||||
for (y = 0; y < srcRect.height; ++y) {
|
||||
uintptr_t dstPixel = (uintptr_t) PIXEL(painter->backing, dstStartX, dstStartY + y);
|
||||
uintptr_t maskPixel = (uintptr_t) PIXEL(mask, srcStartX, srcStartY + y);
|
||||
for (x = 0; x < srcRect.width; ++x, dstPixel += painter->backing->depth, maskPixel += mask->depth) {
|
||||
uint32_t color;
|
||||
GET_PIXEL(color, maskPixel, mask->depth);
|
||||
color = mColorConvert(color, mask->format, mCOLOR_ARGB8);
|
||||
color = _mColorMultiply(painter->fillColor, color);
|
||||
if (painter->blend || painter->fillColor < 0xFF000000) {
|
||||
uint32_t current;
|
||||
GET_PIXEL(current, dstPixel, painter->backing->depth);
|
||||
current = mColorConvert(current, painter->backing->format, mCOLOR_ARGB8);
|
||||
color = mColorMixARGB8(color, current);
|
||||
}
|
||||
color = mColorConvert(color, mCOLOR_ARGB8, painter->backing->format);
|
||||
PUT_PIXEL(color, dstPixel, painter->backing->depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) {
|
||||
if (from == to) {
|
||||
return color;
|
||||
|
|
|
@ -2038,6 +2038,97 @@ M_TEST_DEFINE(painterDrawCircleInvalid) {
|
|||
mImageDestroy(image);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(painterDrawMask) {
|
||||
struct mImage* image;
|
||||
struct mImage* mask;
|
||||
struct mPainter painter;
|
||||
|
||||
image = mImageCreate(4, 4, mCOLOR_XRGB8);
|
||||
mPainterInit(&painter, image);
|
||||
painter.blend = false;
|
||||
painter.fill = true;
|
||||
|
||||
mask = mImageCreate(2, 2, mCOLOR_XRGB8);
|
||||
mImageSetPixel(mask, 0, 0, 0xFFFFFFFF);
|
||||
mImageSetPixel(mask, 1, 0, 0xFFFF0000);
|
||||
mImageSetPixel(mask, 0, 1, 0xFF00FF00);
|
||||
mImageSetPixel(mask, 1, 1, 0xFF0000FF);
|
||||
|
||||
painter.fillColor = 0xFFFFFFFF;
|
||||
mPainterDrawMask(&painter, mask, 0, 0);
|
||||
painter.fillColor = 0xFFFF0000;
|
||||
mPainterDrawMask(&painter, mask, 2, 0);
|
||||
painter.fillColor = 0xFF00FF00;
|
||||
mPainterDrawMask(&painter, mask, 0, 2);
|
||||
painter.fillColor = 0xFF0000FF;
|
||||
mPainterDrawMask(&painter, mask, 2, 2);
|
||||
|
||||
COMPARE4X(0xFFFFFF, 0xFF0000, 0xFF0000, 0xFF0000,
|
||||
0x00FF00, 0x0000FF, 0x000000, 0x000000,
|
||||
0x00FF00, 0x000000, 0x0000FF, 0x000000,
|
||||
0x00FF00, 0x000000, 0x000000, 0x0000FF);
|
||||
|
||||
painter.fillColor = 0xFF808080;
|
||||
mPainterDrawMask(&painter, mask, 0, 0);
|
||||
painter.fillColor = 0xFFFFFF00;
|
||||
mPainterDrawMask(&painter, mask, 2, 0);
|
||||
painter.fillColor = 0xFF00FFFF;
|
||||
mPainterDrawMask(&painter, mask, 0, 2);
|
||||
painter.fillColor = 0xFFFF00FF;
|
||||
mPainterDrawMask(&painter, mask, 2, 2);
|
||||
|
||||
COMPARE4X(0x808080, 0x800000, 0xFFFF00, 0xFF0000,
|
||||
0x008000, 0x000080, 0x00FF00, 0x000000,
|
||||
0x00FFFF, 0x000000, 0xFF00FF, 0xFF0000,
|
||||
0x00FF00, 0x0000FF, 0x000000, 0x0000FF);
|
||||
|
||||
painter.fillColor = 0xFFFFFFFF;
|
||||
mPainterDrawMask(&painter, mask, -1, -1);
|
||||
mPainterDrawMask(&painter, mask, 3, 3);
|
||||
assert_int_equal(0xFF0000FF, mImageGetPixel(image, 0, 0));
|
||||
assert_int_equal(0xFFFFFFFF, mImageGetPixel(image, 3, 3));
|
||||
|
||||
mImageDestroy(image);
|
||||
mImageDestroy(mask);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(painterDrawMaskBlend) {
|
||||
struct mImage* image;
|
||||
struct mImage* mask;
|
||||
struct mPainter painter;
|
||||
const uint8_t lut[4] = { 0x00, 0x55, 0xAA, 0xFF };
|
||||
int x, y;
|
||||
|
||||
image = mImageCreate(4, 4, mCOLOR_XRGB8);
|
||||
mPainterInit(&painter, image);
|
||||
painter.blend = true;
|
||||
painter.fill = true;
|
||||
painter.fillColor = 0xFFFF8000;
|
||||
|
||||
for (y = 0; y < 4; ++y) {
|
||||
for (x = 0; x < 4; ++x) {
|
||||
mImageSetPixel(image, x, y, 0xFF808080);
|
||||
}
|
||||
}
|
||||
|
||||
mask = mImageCreate(4, 4, mCOLOR_ARGB8);
|
||||
for (y = 0; y < 4; ++y) {
|
||||
for (x = 0; x < 4; ++x) {
|
||||
mImageSetPixel(mask, x, y, (lut[x] << 24) | (lut[y] * 0x010101));
|
||||
}
|
||||
}
|
||||
|
||||
mPainterDrawMask(&painter, mask, 0, 0);
|
||||
|
||||
COMPARE4X(0x808080, 0x555555, 0x2A2A2A, 0x000000,
|
||||
0x808080, 0x716355, 0x63462A, 0x552A00,
|
||||
0x808080, 0x8E7155, 0x9C632A, 0xAA5500,
|
||||
0x808080, 0xAA8055, 0xD4802A, 0xFF8000);
|
||||
|
||||
mImageDestroy(image);
|
||||
mImageDestroy(mask);
|
||||
}
|
||||
|
||||
#undef COMPARE3X
|
||||
#undef COMPARE3
|
||||
#undef COMPARE4X
|
||||
|
@ -2083,4 +2174,6 @@ M_TEST_SUITE_DEFINE(Image,
|
|||
cmocka_unit_test(painterDrawCircleOffset),
|
||||
cmocka_unit_test(painterDrawCircleBlend),
|
||||
cmocka_unit_test(painterDrawCircleInvalid),
|
||||
cmocka_unit_test(painterDrawMask),
|
||||
cmocka_unit_test(painterDrawMaskBlend),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue