1j01 / jspaint

🎨 Classic MS Paint, REVIVED + ✨Extras
https://jspaint.app/about
MIT License
7.18k stars 560 forks source link

fix large square brush #231

Closed gschweden closed 2 years ago

gschweden commented 3 years ago

fixes #163

Hey, I hope I'm not stepping on your toes here. No worries if you have a better fix in mind for this (re your comments on the issue). I learned a lot about your code just fiddling with this so it won't feel like wasted effort if the fix doesn't work for you.

I will squash the commits before merging but I thought I'd keep them for the review as the progression kinda shows where I'm coming from with this fix.

Problem

The larges (8pxl) square brush appears hollow when quickly moving horizontally. Other brush shapes and sizes are not affected. Eraser is not affected.

screen shot ![image](https://user-images.githubusercontent.com/17363771/122699880-1b4f9c80-d1ff-11eb-85d1-51dec0d5aa7b.png)

Cause

The bitmap array can not differentiate between 0-1,2 and 7,1. So is identifies the left edge of the square as being adjacent to the right edge of the square.

visualization because this hurt my brain haha ![image](https://user-images.githubusercontent.com/17363771/122703597-d3347800-d206-11eb-862a-034364b1a509.png)

So the conditional in the function for finding the circumference (outline) of the brush shape can not be met for values at the left and right edges of the shape.

https://github.com/1j01/jspaint/blob/f7fea8b12d7900d4aaffab3a5875849cb499dbf6/src/image-manipulation.js#L164-L169

screen shot of square brush circumference rendered without brush stamp ![image](https://user-images.githubusercontent.com/17363771/122700178-a3ce3d00-d1ff-11eb-8c46-bc482b943801.png)

This leaves the observable gap in between the filled shape stamps at either end of the line segment.

Solution

If the canvas is 1 bigger than the brush shape get_circumference_points_for_brush can always find the edge of the shape on the canvas.

The best place for increasing the canvas shape seemed to be the function responsible for calculating canvas size based on shape. This increases the canvass shape on both the x and y axis, even though only x would be necessary. I think the minor performance impact is worth the separation of concerns in the code. (Alternative would be to increase the canvas size in the get_circumference_points_for_brush function.)

Conveniently this also fixes the issue with rendering odd sized circles. I also tested with a custom circle of size 8 and it had the same gap issue as the size 8 square. Handling both sqares and circles the same seems reasonable.

Interrupted circle outline ![image](https://user-images.githubusercontent.com/17363771/122704176-ff042d80-d207-11eb-8246-06e3d58a6f29.png)

QA

Tested all brush shapes and sizes at different speeds and directions. There no longer is a gap.

image

Performance and Testing

My local set up could be better. I don't have cypress set up yet but will figure that out if the change is desirable and tests are needed. Performance seems to be same as before change, but my set up is pretty slow so it's hard to tell.

1j01 commented 3 years ago

Nice investigation, and nice graphics! I didn't know the circumference points calculation was failing. I think a simpler solution would be to add bounds checking to the at function.

1j01 commented 2 years ago

I've fixed this by changing the at function. Thanks for your work on this though!

    const at = (x, y) => (
        // coordinate checking is important so it doesn't wrap (if the brush abuts the edge of the canvas)
        x >= 0 && y >= 0 &&
        x < image_data.width && y < image_data.height &&
        image_data.data[(y * image_data.width + x) * 4 + 3] > 127
    );