I am creating a pixel art editor for Android, and recently I implemented an ellipse tool using the midpoint ellipse algorithm.
I would just like to emphasize that this tool works perfectly!
The only problem is, if I draw a large ellipse on a larger bitmap (of around 1000x1000 or more), the sides of the ellipse get cut, and there is some strange behaviour going on:
Why is this happening?
Code for the midpoint ellipse algorithm:
package com.therealbluepandabear.pixapencil.algorithms
import com.therealbluepandabear.pixapencil.activities.canvas.canvascommands.overrideSetPixel
import com.therealbluepandabear.pixapencil.models.Coordinates
class MidpointEllipseAlgorithm(private val algorithmInfo: AlgorithmInfoParameter, private val xDEC: Boolean = false, private val yDEC: Boolean = false, private val filledMode: Boolean = false) {
private val shouldLineIgnoreBrush = true
private fun putPixel(p1: Coordinates, p2: Coordinates) {
val xc = p1.x
val yc = p1.y
val x = p2.x
val y = p2.y
if (!xDEC && !yDEC) {
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc + x, yc + y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc + x, yc - y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc - x, yc - y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc - x, yc + y),
algorithmInfo.color
)
if (filledMode) {
val lineAlgorithmInstance = LineAlgorithm(algorithmInfo, shouldLineIgnoreBrush)
lineAlgorithmInstance.compute(
Coordinates(xc + x, yc + y),
Coordinates(xc + x, yc - y),
)
lineAlgorithmInstance.compute(
Coordinates(xc - x, yc - y),
Coordinates(xc - x, yc + y),
)
}
} else if (xDEC && !yDEC) {
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates((xc + x) + 1, yc + y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates((xc + x) + 1, yc - y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc - x, yc - y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc - x, yc + y),
algorithmInfo.color
)
if (filledMode) {
val lineAlgorithmInstance = LineAlgorithm(algorithmInfo, shouldLineIgnoreBrush)
lineAlgorithmInstance.compute(
Coordinates((xc + x) + 1, yc + y),
Coordinates((xc + x) + 1, yc - y),
)
lineAlgorithmInstance.compute(
Coordinates(xc - x, yc - y),
Coordinates(xc - x, yc + y),
)
}
} else if (!xDEC && yDEC) {
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc + x, (yc + y) + 1),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc + x, yc - y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc - x, yc - y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc - x, (yc + y) + 1),
algorithmInfo.color
)
if (filledMode) {
val lineAlgorithmInstance = LineAlgorithm(algorithmInfo, shouldLineIgnoreBrush)
lineAlgorithmInstance.compute(
Coordinates(xc + x, (yc + y) + 1),
Coordinates(xc + x, yc - y),
)
lineAlgorithmInstance.compute(
Coordinates(xc - x, yc - y),
Coordinates(xc - x, (yc + y) + 1),
)
}
} else {
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates((xc + x) + 1, (yc + y) + 1),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates((xc + x) + 1, yc - y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc - x, yc - y),
algorithmInfo.color
)
algorithmInfo.canvasCommandsHelperInstance.overrideSetPixel(
Coordinates(xc - x, (yc + y) + 1),
algorithmInfo.color
)
if (filledMode) {
val lineAlgorithmInstance = LineAlgorithm(algorithmInfo, shouldLineIgnoreBrush)
lineAlgorithmInstance.compute(
Coordinates((xc + x) + 1, (yc + y) + 1),
Coordinates((xc + x) + 1, yc - y),
)
lineAlgorithmInstance.compute(
Coordinates(xc - x, yc - y),
Coordinates(xc - x, (yc + y) + 1),
)
}
}
}
fun compute(p1: Coordinates, rx: Int, ry: Int) {
val idp = Coordinates(0, ry)
var xkp1 = idp.x
var ykp1 = idp.y
var lxkp1: Int
var lykp1: Int
var p1k = (ry * ry) + ((rx * rx) / 4) - (ry * (rx * rx))
val incy = p1.y
val incx = p1.x
putPixel(Coordinates(incx, incy), Coordinates(xkp1, ykp1))
while (
(2 * (xkp1 + 1) * (ry * ry))
<
(2 * ykp1 * (rx * rx))
) {
p1k += if (p1k >= 0) {
xkp1++
ykp1--
lxkp1 = xkp1 - 1
lykp1 = ykp1 + 1
(ry * ry) + (2 * (lxkp1 + 1)) * (ry * ry) + (rx * rx) * ((ykp1 * ykp1) - (lykp1 * lykp1)) - (rx * rx) * (ykp1 - lykp1)
} else {
xkp1++
lxkp1 = xkp1 - 1
lykp1 = ykp1
(ry * ry) + (2 * (lxkp1 + 1)) * (ry * ry) + (rx * rx) * ((ykp1 * ykp1) - (lykp1 * lykp1))
}
putPixel(Coordinates(incx, incy), Coordinates(xkp1, ykp1))
}
var p2k = (ry * ry) * ((xkp1 + 0.5) * (xkp1 + 0.5)) + (rx * rx) * ((ykp1 - 1) * (ykp1 - 1)) - ((rx * rx) * (ry * ry))
while (
ykp1 > 0
) {
if (p2k >= 0) {
ykp1--
lykp1 = ykp1 + 1
lxkp1 = xkp1
p2k += (rx * rx) - 2 * (rx * rx) * (lykp1 - 1) + (ry * ry) * ((xkp1 * xkp1) - (lxkp1 * lxkp1))
} else {
xkp1++
lxkp1 = xkp1 - 1
ykp1--
lykp1 = ykp1 + 1
p2k += (rx * rx) - 2 * (rx * rx) * (lykp1 - 1) + (ry * ry) * ((xkp1 * xkp1) - (lxkp1 * lxkp1)) + (ry * ry) * (xkp1 - lxkp1)
}
putPixel(Coordinates(incx, incy), Coordinates(xkp1, ykp1))
}
}
}
The problem only exists in the MidpointEllipseAlgorithm
, so it's been isolated. The overrideSetPixel
function is not causing this behaviour because when I replace it with a simple Bitmap.setPixel
the problem still exists. But, although it's been isolated, I am confused why this is happening.
If you draw the ellipse on smaller canvases this behaviour isn't getting demonstrated, it works perfectly. It's only if you draw an ellipse of a sufficient size on a large bitmap where this behaviour is demonstrated.
Why is it being 'cut' like this on large bitmaps? It's completely nonsensical as the algorithm itself has been reviewed by my peers, and it is to a high standard. I have tried to fix it by adjusting variables and inequalities but I have no clue where it's coming from, I even compared my algorithm code to the ones online and still, even if I replace it I get the issue.
Fixed by converting the following to double
:
var p2k = (ry.toDouble() * ry.toDouble()) * ((xkp1.toDouble() + 0.5) * (xkp1.toDouble() + 0.5)) + (rx.toDouble() * rx.toDouble()) * ((ykp1.toDouble() - 1) * (ykp1.toDouble() - 1)) - ((rx.toDouble() * rx.toDouble()) * (ry.toDouble() * ry.toDouble()))