This has bothered me, too, and in the past I’ve tried tinkering with the code. You might think this is an easy thing to do, but the details can be a nuisance.
When you rotate the brush to a new angle, the coordinates of a square’s 4 corners and its center are promoted from integers to real numbers. For example,
3.0. This matters because, in software math,
3 / 2 is
3.0 / 2 is
1.5. New corners are calculated relative to the center with trigonometry functions. Afterward, those coordinates are demoted to integers and a new polygon is drawn.
You’ve got about 4 options when demoting: truncate, floor, ceil and round. For the rectangle’s center, there’s no
0.5 of a pixel, so the center of the brush could be off to the top-left or to the bottom-right. If, for example, you round the center and round each corner, then translate each integer corner by the integer center, you can easily introduce an extra pixel.
Exception cases can be caught, such as when the rotation is divisible by 90 degrees (-180, -90, 0, 90, 180) with floor modulo. But there are also nearby degrees (-182 to -178, etc.). For smaller brushes, the difference between 89 and 90 degrees isn’t significant in terms of display, but for larger brushes, say 64px, it is. Below are examples for a 20px brush.
And so on, and so on. Tweak how you center the brush, and you introduce problems with its size. Tweak how you estimate the brush’s new axis-aligned bounding box (AABB), then find problems with its centering. Privilege certain sizes and you can miss problems with others. Privilege making certain angles – like 26.565 and 45 degrees – correct and you can miss problems with others.
In general raster image graphics, anti-aliasing is used, but not in pixel art.
If you’re interested, there are similar issues with rotating an image that I brought up in this post. A key part is the calculation of the AABB, so I pasted it above. It’s a bit simpler for a square than a rectangle.