diff --git a/src/draw/__tests__/drawCircleOnImage.test.ts b/src/draw/__tests__/drawCircleOnImage.test.ts index 5c0bd512a..cc53daba4 100644 --- a/src/draw/__tests__/drawCircleOnImage.test.ts +++ b/src/draw/__tests__/drawCircleOnImage.test.ts @@ -365,3 +365,40 @@ test('default options', () => { [0, 1, 0], ]); }); +test('draw circle image with transparent color', () => { + const image = testUtils.createGreyaImage([ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]); + const center = { row: 5, column: 2 }; + const radius = 2; + const received = image.drawCircle(center, radius, { + fill: [255, 125], + color: [255, 255], + }); + + const expected = testUtils.createGreyaImage([ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0], + [255, 255, 255, 125, 255, 125, 255, 125, 255, 255, 0, 0, 0, 0, 0, 0], + [255, 255, 255, 125, 255, 125, 255, 125, 255, 255, 0, 0, 0, 0, 0, 0], + [255, 255, 255, 125, 255, 125, 255, 125, 255, 255, 0, 0, 0, 0, 0, 0], + [0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]); + + expect(received).toMatchImage(expected); +}); diff --git a/src/draw/drawCircleOnImage.ts b/src/draw/drawCircleOnImage.ts index ed953547b..94cb5be9a 100644 --- a/src/draw/drawCircleOnImage.ts +++ b/src/draw/drawCircleOnImage.ts @@ -73,25 +73,51 @@ export function drawCircleOnImage( if (radius === 1) { setBlendedVisiblePixel(newImage, center.column, center.row, fill); } + //Starting points for the top and bottom row of the circle. + let prevRow = center.row + radius; + + let index = 0; circle(center.column, center.row, radius, (column: number, row: number) => { setBlendedVisiblePixel(newImage, column, row, color); - - //todo: fill is not optimal we can fill symmetrically - if (column - 1 > center.column) { + // Filling the first line of the circle. + if (index === 0) { newImage.drawLine( { row, column: column - 1 }, - { row, column: center.column }, + { + row, + column: center.column - (column - center.column - 1), + }, { strokeColor: fill, out: newImage }, ); - } else if (column + 1 < center.column) { + } + // The algorithm used is Bresenham's circle algorithm (@link https://www.geeksforgeeks.org/bresenhams-circle-drawing-algorithm/) to find points that constitute the circle outline. However, in this algorithm The circle is divided in 4 parts instead of 8: top, right, bottom and left. + // The algorithm draws a point per quadrant until the circle is complete. + // We use bottom (index % 4 === 1, quadrant 2) point of the outline to fill the circle with color. + // Filling half of the circle. + if (index % 4 === 1 && prevRow !== row) { + // For quadrant 2, column < center.column newImage.drawLine( { row, column: column + 1 }, - { row, column: center.column }, + { + row, + column: center.column - (column - center.column + 1), + }, + { strokeColor: fill, out: newImage }, + ); + prevRow = row; + // Filling top half of the circle. + newImage.drawLine( + { row: center.row - (row - center.row), column: column + 1 }, + { + row: center.row - (row - center.row), + column: center.column - (column - center.column + 1), + }, { strokeColor: fill, out: newImage }, ); } + + index++; }); } - return newImage; }