I am working on a pixel art editor for Android, and I noticed that when I create Bitmaps with more obscure width/height ratios, for example 3x300 or 5x90 (or the other way round) the pixels become slightly rectangular.
I've tried for a week or two to find out what exactly I am doing 'wrong' with the sizing calculations, but I have no idea how to fix this issue. This issue is not recreatable when creating Bitmaps with similar width/height ratios, for example a 50x40 or 90x80.
Below is the code that handles the sizing of the Rect in which we draw the Bitmap on.
Since some people had issues understanding the code, I will try to explain it. First of all, the ratio gives us the scaling factor in which we should multiply the base width/height so that our bitmap appears as expected.
For example, let's say the user selected a 5x10 (width is 5, and height is 10) bitmap, the height is larger than the width so the ratio will be 5/10 which is 0.5. now, the width remains the same, all we need to really scale is the height, so we take the height of the container and multiply that by 0.5 to get our desired result, et cetera. This is my best effort at explaining how the view is sized.
private fun setBoundingRect() {
val ratio = if (bitmapWidth > bitmapHeight) {
bitmapHeight.toDouble() / bitmapWidth.toDouble()
} else {
bitmapWidth.toDouble() / bitmapHeight.toDouble()
}
val rectW: Int = if (bitmapWidth > bitmapHeight) {
width
} else if (bitmapHeight > bitmapWidth) {
(height * ratio).toInt()
} else {
width
}
val rectH: Int = if (bitmapWidth > bitmapHeight) {
(width * ratio).toInt()
} else if (bitmapHeight > bitmapWidth) {
height
} else {
width
}
val canvasCenter = Point(width / 2, height / 2)
val left = canvasCenter.x - rectW / 2
val top = canvasCenter.y - rectH / 2
val right = canvasCenter.x rectW / 2
val bottom = canvasCenter.y rectH / 2
boundingRect = Rect(left, top, right, bottom)
}
For the most part, it works well.
onDraw
method:
override fun onDraw(canvas: Canvas) {
if (::drawingViewBitmap.isInitialized) {
canvas.drawRect(boundingRect, PaintData.rectPaint)
canvas.drawBitmap(drawingViewBitmap, null, boundingRect, null)
drawGrid(canvas)
}
}
Below is a demo of a 3x150 project, and as you can see, the pixels are 'rectangular', and it is quite evident:
I've tried to figure out what exactly I am doing wrong in my sizing calculations, which I think is where the issue is stemming from, but I haven't been able to figure it out.
Full code: https://github.com/therealbluepandabear/RevampedPixelGridView
CodePudding user response:
The aspect ratio of your Rect
varies depending the AR of the bitmap, but also on the size and aspect ratio of your View
(runnable example):
calculated rect width as 16 px, actual 16.62
View size: 876 x 831
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.019277109
calculated rect width as 19 px, actual 19.16
View size: 887 x 958
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.018789144
calculated rect width as 19 px, actual 19.96
View size: 888 x 998
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.018036073
calculated rect width as 17 px, actual 17.26
View size: 936 x 863
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.018561484
So your Rect
's aspect ratio can be out by as much as 10% compared to the original bitmap at these View
size - much more if the View
is smaller.
It's happening because you're relying on integer rounding to calculate the short dimension (those calculated rect width
s up there are the result of toInt()
on the actual
float calculations). That introduces some error, and because the values you're working with are so small that becomes significant.
You could improve it just by using roundToInt()
instead of toInt()
, so it can at least hit the closest integer every time:
calculated rect width as 18 px, actual 17.900000000000002
View size: 900 x 895
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.020134227
calculated rect width as 18 px, actual 18.080000000000002
View size: 943 x 904
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.019911505
calculated rect width as 19 px, actual 19.080000000000002
View size: 923 x 954
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.018867925
calculated rect width as 20 px, actual 19.68
View size: 981 x 984
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.020325202
Or use floating-point calculations and create a RectF
instead (there are drawBitmap
and drawRect
calls that use them):
fun setBoundingRect() {
val rectW =
if (bitmapWidth > bitmapHeight) width.toFloat()
else (bitmapWidth.toFloat() / bitmapHeight) * height
val rectH =
if (bitmapHeight > bitmapWidth) height.toFloat()
else (bitmapHeight.toFloat() / bitmapWidth) * width
val canvasCenter = Point(width / 2, height / 2)
val left = canvasCenter.x - rectW / 2
val top = canvasCenter.y - rectH / 2
val right = canvasCenter.x rectW / 2
val bottom = canvasCenter.y rectH / 2
boundingRect = RectF(left, top, right, bottom)
}
View size: 819 x 897
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.020000003
View size: 987 x 996
Bitmap W/H ratio: 0.02
Rect W/H ratio: 0.019999983
Or you could calculate a more appropriate height/width instead of just using the full height
or width
of the View
, so that when you multiply it by ratio
you get a round number (or close to it) for the other dimension. Right now the accuracy is just down to coincidence, how well the numbers match up