diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index c91d88518..98a03dd0b 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -136,6 +136,7 @@ void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, 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); 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); diff --git a/src/util/image.c b/src/util/image.c index 781641340..100f7a608 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -614,6 +614,75 @@ void mPainterDrawRectangle(struct mPainter* painter, int x, int y, int width, in } } +void mPainterDrawLine(struct mPainter* painter, int x1, int y1, int x2, int y2) { + if (!painter->strokeWidth) { + return; + } + int dx = x2 - x1; + int dy = y2 - y1; + int x, y; + int xi = 1; + int yi = 1; + int residual; + + int mx = dx; + int my = dy; + if (mx < 0) { + mx = -mx; + } + if (my < 0) { + my = -my; + } + + if (dx < 0) { + xi = -1; + dx = -dx; + } + if (dy < 0) { + yi = -1; + dy = -dy; + } + + unsigned i; + uint32_t color = painter->strokeColor; + + if (mx > my) { + residual = 2 * dy - dx; + y = y1; + for (x = x1; x != x2 + xi; x += xi) { + for (i = 0; i < painter->strokeWidth; ++i) { + if (painter->blend) { + color = mColorMixARGB8(painter->strokeColor, mImageGetPixel(painter->backing, x, y - painter->strokeWidth / 2 + i)); + } + mImageSetPixel(painter->backing, x, y - painter->strokeWidth / 2 + i, color); + } + if (residual > 0) { + y += yi; + residual -= 2 * dx; + } + residual += 2 * dy; + } + } else { + residual = 2 * dx - dy; + x = x1; + for (y = y1; y != y2 + yi; y += yi) { + for (i = 0; i < painter->strokeWidth; ++i) { + if (painter->blend) { + color = mColorMixARGB8(painter->strokeColor, mImageGetPixel(painter->backing, x - painter->strokeWidth / 2 + i, y)); + } + mImageSetPixel(painter->backing, x - painter->strokeWidth / 2 + i, y, color); + } + if (residual > 0) { + x += xi; + residual -= 2 * dy; + } + residual += 2 * dx; + } + } + + // TODO: Draw endcaps for widths >2 +} + uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { if (from == to) { return color; diff --git a/src/util/test/image.c b/src/util/test/image.c index 80cb7332b..c136a8318 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -1568,7 +1568,7 @@ M_TEST_DEFINE(painterStrokeRectangleBlend) { } M_TEST_DEFINE(painterDrawRectangle) { - struct mImage* image; + struct mImage* image; struct mPainter painter; image = mImageCreate(3, 3, mCOLOR_ARGB8); @@ -1628,6 +1628,227 @@ M_TEST_DEFINE(painterDrawRectangle) { mImageDestroy(image); } +M_TEST_DEFINE(painterDrawLineOctants) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 2, 2); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 2, 2, 0, 0); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 2, 0, 0, 2); + COMPARE3X(0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 2, 2, 0); + COMPARE3X(0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 2, 1); + COMPARE3X(0xFF, 0xFF, 0x00, + 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 2, 1, 0, 0); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 1, 2); + COMPARE3X(0xFF, 0x00, 0x00, + 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 1, 2, 0, 0); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterDrawLineWidth) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 3, 3); + COMPARE4X(0xFF, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0xFF, 0xFF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 3, 3); + COMPARE4X(0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 3, 0, 0, 3); + COMPARE4X(0x00, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 3, 0, 0, 3); + COMPARE4X(0x00, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 1, 0, 1, 2); + COMPARE3X(0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 1, 0, 1, 2); + COMPARE3X(0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 1, 2, 1); + COMPARE3X(0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 1, 2, 1); + COMPARE3X(0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterDrawLineBlend) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawLine(&painter, 0, 0, 2, 2); + painter.strokeColor = 0x4000FF00; + mPainterDrawLine(&painter, 0, 2, 2, 0); + COMPARE3(0x400000FF, 0x00000000, 0x4000FF00, + 0x00000000, 0x4000FF00, 0x00000000, + 0x4000FF00, 0x00000000, 0x400000FF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawLine(&painter, 0, 0, 2, 2); + painter.strokeColor = 0x4000FF00; + mPainterDrawLine(&painter, 0, 2, 2, 0); + COMPARE3(0x400000FF, 0x00000000, 0x4000FF00, + 0x00000000, 0x6F00916D, 0x00000000, + 0x4000FF00, 0x00000000, 0x400000FF); + mImageDestroy(image); +} + #undef COMPARE3X #undef COMPARE3 #undef COMPARE4X @@ -1663,4 +1884,7 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(painterStrokeRectangleWidth), cmocka_unit_test(painterStrokeRectangleBlend), cmocka_unit_test(painterDrawRectangle), + cmocka_unit_test(painterDrawLineOctants), + cmocka_unit_test(painterDrawLineWidth), + cmocka_unit_test(painterDrawLineBlend), )