From 57ba653bc7991bc8d9641f236758d817eff19543 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 27 Jul 2023 20:26:08 -0700 Subject: [PATCH] Util: Add mPainterDrawCircle --- include/mgba-util/image.h | 1 + src/util/image.c | 144 +++++++++++++++++++++++++++++++++++--- src/util/test/image.c | 92 ++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 8 deletions(-) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 98a03dd0b..31514687d 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -137,6 +137,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); +void mPainterDrawCircle(struct mPainter*, int x, int y, int diameter); 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 100f7a608..d68491718 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -557,6 +557,13 @@ void mPainterInit(struct mPainter* painter, struct mImage* backing) { painter->backing = backing; } +static void mPainterDrawPixel(struct mPainter* painter, unsigned x, unsigned y, uint32_t color) { + if (painter->blend) { + color = mColorMixARGB8(painter->strokeColor, mImageGetPixel(painter->backing, x, y)); + } + mImageSetPixel(painter->backing, x, y, color); +} + static void mPainterFillRectangle(struct mPainter* painter, int x, int y, int width, int height) { FILL_BOUNDS_INIT(x, y, width, height); @@ -651,10 +658,7 @@ void mPainterDrawLine(struct mPainter* painter, int x1, int y1, int x2, int y2) 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); + mPainterDrawPixel(painter, x, y - painter->strokeWidth / 2 + i, color); } if (residual > 0) { y += yi; @@ -667,10 +671,7 @@ void mPainterDrawLine(struct mPainter* painter, int x1, int y1, int x2, int y2) 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); + mPainterDrawPixel(painter, x - painter->strokeWidth / 2 + i, y, color); } if (residual > 0) { x += xi; @@ -683,6 +684,133 @@ void mPainterDrawLine(struct mPainter* painter, int x1, int y1, int x2, int y2) // TODO: Draw endcaps for widths >2 } +static void _drawCircleOctants(struct mPainter* painter, int x, int y, int offx, int offy, int offset, uint32_t color) { + mPainterDrawPixel(painter, x + offy - offset, y + offx - offset, color); + mPainterDrawPixel(painter, x - offy, y + offx - offset, color); + mPainterDrawPixel(painter, x + offy - offset, y - offx, color); + mPainterDrawPixel(painter, x - offy, y - offx, color); + if (offx < offy) { + mPainterDrawPixel(painter, x + offx - offset, y + offy - offset, color); + mPainterDrawPixel(painter, x - offx, y + offy - offset, color); + mPainterDrawPixel(painter, x + offx - offset, y - offy, color); + mPainterDrawPixel(painter, x - offx, y - offy, color); + } +} + +static void _drawCircle2x2(struct mPainter* painter, int x, int y, uint32_t color) { + mPainterDrawPixel(painter, x, y, color); + mPainterDrawPixel(painter, x - 1, y, color); + mPainterDrawPixel(painter, x, y - 1, color); + mPainterDrawPixel(painter, x - 1, y - 1, color); +} + +void mPainterDrawCircle(struct mPainter* painter, int x, int y, int diameter) { + int radius = diameter / 2; + int offset = (diameter ^ 1) & 1; + int stroke = painter->strokeWidth; + int dx = 1; + int residual0 = 1 - radius; + int dy0 = -2 * radius; + int offx = 0; + int offy; + int y0 = radius; + if (stroke > radius) { + // Clamp stroke + stroke = radius; + if (!offset) { + // Draw center dot as stroke + mPainterDrawPixel(painter, x + radius, y + radius, painter->strokeColor); + } + } else if (!offset && painter->fill) { + // Draw center dot as fill + mPainterDrawPixel(painter, x + radius, y + radius, painter->fillColor); + } + + int residual1 = 1 - radius + stroke; + int dy1 = -2 * (radius - stroke); + int y1 = radius - stroke; + int i; + + if (!offset) { + // Draw central axes + for (i = 0; i < stroke; ++i) { + mPainterDrawPixel(painter, x + radius, y + radius * 2 - i, painter->strokeColor); + mPainterDrawPixel(painter, x + radius, y + i, painter->strokeColor); + mPainterDrawPixel(painter, x + radius * 2 - i, y + radius, painter->strokeColor); + mPainterDrawPixel(painter, x + i, y + radius, painter->strokeColor); + } + if (painter->fill) { + for (i = i; i < y1 + 1; ++i) { + mPainterDrawPixel(painter, x + radius, y + radius - i, painter->fillColor); + mPainterDrawPixel(painter, x + radius, y + radius + i, painter->fillColor); + mPainterDrawPixel(painter, x + radius - i, y + radius, painter->fillColor); + mPainterDrawPixel(painter, x + radius + i, y + radius, painter->fillColor); + } + } + } + + while (offx < y0) { + if (residual0 >= 0) { + y0 -= 1; + dy0 += 2; + residual0 += dy0; + } + if (residual1 >= 0) { + y1 -= 1; + dy1 += 2; + residual1 += dy1; + } + offx += 1; + dx += 2; + residual0 += dx; + residual1 += dx; + if (stroke) { + // Fill + if (painter->fill) { + if (offx == 1 && y1 == 0 && offset) { + // Special case for diameter-2 fill + _drawCircle2x2(painter, x + radius, y + radius, painter->fillColor); + } else { + for (offy = 0; offy < y1 + 1; ++offy) { + if (offx > offy) { + continue; + } + _drawCircleOctants(painter, x + radius, y + radius, offx, offy, offset, painter->fillColor); + } + } + } + // Stroke + if (radius == 1 && offset) { + // Special case for diameter-2 stroke + _drawCircle2x2(painter, x + radius, y + radius, painter->strokeColor); + } else { + for (offy = y1 + 1; offy < y0 + 1; ++offy) { + if (offx == 1 && offy == 1 && y1 == 0 && offset) { + // Special case for diameter-2 inner fill + continue; + } + if (offx > offy) { + continue; + } + _drawCircleOctants(painter, x + radius, y + radius, offx, offy, offset, painter->strokeColor); + } + } + } else if (painter->fill) { + if (offx == 1 && y0 == 0 && offset) { + // Special case for diameter-2 fill + _drawCircle2x2(painter, x, y, painter->fillColor); + } else { + for (offy = 0; offy < y0 + 1; ++offy) { + if (offx > offy) { + continue; + } + _drawCircleOctants(painter, x + radius, y + radius, offx, offy, offset, painter->fillColor); + } + } + } + } +} + 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 c136a8318..eed41a913 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -1849,6 +1849,95 @@ M_TEST_DEFINE(painterDrawLineBlend) { mImageDestroy(image); } +M_TEST_DEFINE(painterDrawCircleArea) { + struct mImage* image; + struct mPainter painter; + + int i; + for (i = 4; i < 50; ++i) { + image = mImageCreate(i, i, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawCircle(&painter, 0, 0, i); + + int filled = 0; + + int x, y; + for (y = 0; y < i; ++y) { + for (x = 0; x < i; ++x) { + uint32_t color = mImageGetPixel(image, x, y); + if (color == painter.fillColor) { + ++filled; + } + } + } + float area = i * i; + assert_float_equal(filled / area, M_PI / 4, 0.12); + } +} + +M_TEST_DEFINE(painterDrawCircleCircumference) { + struct mImage* image; + struct mPainter painter; + + int i; + for (i = 25; i < 100; ++i) { + image = mImageCreate(i, i, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawCircle(&painter, 0, 0, i); + + int filled = 0; + + int x, y; + for (y = 0; y < i; ++y) { + for (x = 0; x < i; ++x) { + uint32_t color = mImageGetPixel(image, x, y); + if (color == painter.strokeColor) { + ++filled; + } + } + } + assert_float_equal(filled / (float) i, M_PI, M_PI * 0.11); + } +} + + +M_TEST_DEFINE(painterDrawCircleOffset) { + struct mImage* image; + struct mPainter painter; + + int i; + for (i = 4; i < 20; ++i) { + image = mImageCreate(i * 2, i * 2, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawCircle(&painter, 0, 0, i); + mPainterDrawCircle(&painter, i, 0, i); + mPainterDrawCircle(&painter, 0, i, i); + mPainterDrawCircle(&painter, i, i, i); + + int x, y; + for (y = 0; y < i; ++y) { + for (x = 0; x < i; ++x) { + uint32_t color = mImageGetPixel(image, x, y); + assert_int_equal(color, mImageGetPixel(image, x + i, y)); + assert_int_equal(color, mImageGetPixel(image, x, y + i)); + assert_int_equal(color, mImageGetPixel(image, x + i, y + i)); + } + } + } +} + #undef COMPARE3X #undef COMPARE3 #undef COMPARE4X @@ -1887,4 +1976,7 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(painterDrawLineOctants), cmocka_unit_test(painterDrawLineWidth), cmocka_unit_test(painterDrawLineBlend), + cmocka_unit_test(painterDrawCircleArea), + cmocka_unit_test(painterDrawCircleCircumference), + cmocka_unit_test(painterDrawCircleOffset), )