0

Background

I have an app in the Google Play store built in Kotlin.

It currently displays a grid that the user draws her password on. Here's a snapshot of the grid as it was previously drawn with the previous default paint.strokeWidth.

cyapass grid default

The grey lines between the (red) posts are drawn with the following method:

  private fun DrawGridLines() {
        val paint = Paint()
        for (y in 0..numOfCells) {
            xCanvas!!.drawLine(
                (0 + leftOffset).toFloat(), (y * cellSize + topOffset).toFloat(),
                (numOfCells * cellSize + leftOffset).toFloat(),
                (y * cellSize + topOffset).toFloat(), paint
            )
        }

        for (x in 0..numOfCells) {
            xCanvas!!.drawLine(
                (x * cellSize + leftOffset).toFloat(), (0 + topOffset).toFloat(),
                (x * cellSize + leftOffset).toFloat(), (numOfCells * cellSize + topOffset).toFloat(),
                paint
            )
        }

    }

The Problem

While working on updates to the app I ran it on the emulator and saw the following:

grid lines are not there

As you can see the gridlines are drawn properly. Very odd since it seems to be drawing partial grid lines. NOTE: I ran this on numerous API versions and they all draw the grid lines this way now.

paint.strokeWidth = 0.0

I added some code to examine the value of paint.strokeWidth but that is additionally odd. It shows that the value of strokeWidth is always 0.0.

You can see that in my logcat output:

logcat shows always 0.0

The Fix

Yes, I can simply fix this by explicitly setting the value myself.

I added the following line of code to the routine above:

paint.strokeWidth = 5F;

Now it looks like the following: grid is drawn properly

However, I'd like to know why this has suddenly occurred??

I'd also like to know how it seems to draw "some" of the lines since the value of the strokeWidth is actually 0.0???

raddevus
  • 8,142
  • 7
  • 66
  • 87

2 Answers2

1

The first thing I see in your code is that nowhere the Paint gets configured or its strokeWidth assigned a value. You need to set specific values and not use defaults, as defaults don't take into consideration display densities neither may have a valid usable value at all.

In the next sniped of your code you instantiate a new Paint instance and use it straight away without setting any properties to it:

private fun DrawGridLines() {
        val paint = Paint()
        for (y in 0..numOfCells) {
             xCanvas!!.drawLine(....
            "Here using already the new paint??? where did you configure it?"

Secondly, notice that Paint.strokeWidth units are in pixels, therefore you need to take into account the device display density and adjust to it.

for example:

val DEFAULT_SIZE_PX = 5.0f
    
val scaledWidth = DEFAULT_SIZE_PX * context
        .resources
        .displayMetrics
        .density
    
paint.strokeWidth = scaledWidth

Or, which is the same as:

val DEFAULT_SIZE_PX = 5.0f
val displayMetrics = context.resources.displayMetrics
val scaledWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_SIZE_PX, displayMetrics)
paint.strokeWidth = scaledWidth
PerracoLabs
  • 16,449
  • 15
  • 74
  • 127
  • Yes, that is a good point. And that helps to get the output to look more like the original. I'm calculating density now and setting paint.color to LTGRAY and that makes it look like it previously did. paint.strokeWidth = 1.2F * density; paint.color = Color.LTGRAY; Here's what it looks like now (more like original): https://i.stack.imgur.com/jHDOh.png Thanks for your answer. However, it is still interesting that suddenly I had to explicitly set these two items & in the past I didn't. I wonder why. – raddevus Jan 20 '22 at 22:02
  • @raddevus The default value of the paint strokeWidth is just a default with no density calculated at all. Depending on the device you may test it may appear or not at all. The default value should never be used, and should be always configured to consider the display density. This will ensure that it looks the same in all devices. – PerracoLabs Jan 20 '22 at 22:12
0

The official docs on setStrokeWidth provides a very interesting statement:

strokeWidth uses hairline

"Hairlines always draw a single pixel..."

I suppose the way that is handled is now probably handled differently and has this type of effect on output now. Or it is related to the density issue. Either way, it is odd that it has changed. And interesting/odd that it states that you can set it to 0 for hairline output.

raddevus
  • 8,142
  • 7
  • 66
  • 87
  • Note that a single pixel width may not be visible at all depending on the display density. So if using 0 for hairlines, yes you have 1 pixel, but in devices with high density you may not be able to see it at all as it may be too thin, while in devices with low density it will be more visible. This is why it should be always configured with a density, so it looks the same regardless the display density. – PerracoLabs Jan 20 '22 at 22:16
  • Yes, but the interesting thing is that in the past (until very recently) it worked with no problem. Also, consider that if the _hairline_ didn't draw properly (with 0.0 value) then it wouldn't make sense that the docs would say you could use it. Because you cannot calculate the proper density of _hairline_ because 0.0 * any_density = 0.0. That would mean that the hairline would only draw on certain devices and then why wouldn't docs mention that also? I agree that you are correct that setting the value with density is a must, but it is odd that it worked & suddenly doesn't. – raddevus Jan 20 '22 at 22:19
  • Is not that it works or not, and instead is that all devices have different display densities, and in some will be more visible than others. 1 pixel does not look the same depending on the display density. In top devices with very high density 1 pixel will be nearly invisible, while in average devices will be more visible. – PerracoLabs Jan 20 '22 at 22:23
  • I understand that you're saying that the width of one pixel will vary depending upon the device density. But, there is definitely more to this problem than that also. Look at the 2nd image in the original post. For some reason, some _hairlines_ show on the display & others are not rendered. There are verticals & horizontals so that display is capable of displaying the _hairline_ but for some reason it doesn't in certain locations. The device can either display one pixel widths or not -- and it definitely is in some locations, but not in others. That's interesting, right? – raddevus Jan 20 '22 at 22:34
  • Did you clear the canvas before each redraw? if not, then it may be re-drawing on top of the previous rendered drawing. In such a case you need to consider the Float fractions calculated in drawLine, which in some cases may fall into the same spot as a previously draw 1px line, and in others depending on how the CPU fractionates the Float may fall a pixel to either side of a previously drawn line. This would make some lines more visible than others. – PerracoLabs Jan 20 '22 at 22:48
  • Well, first of all, keep in mind that this *changed* without any code change. Suddenly these grid lines are not showing up on the same emulator where they were showing up before. Also, I removed all other code and am only drawing the grid lines now. Here's the image that shows that they still don't display properly: https://i.stack.imgur.com/IiTDg.png Just the canvas and the grid lines but they don't all show up. You have some interesting ideas, but this looks like it is further down in the stack -- (underlying api). And you see the function that all gridlines are drawn at the same time. – raddevus Jan 20 '22 at 23:03