Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pixel perfect image drawing? #22

Open
liamoc opened this issue Dec 3, 2015 · 2 comments
Open

Pixel perfect image drawing? #22

liamoc opened this issue Dec 3, 2015 · 2 comments

Comments

@liamoc
Copy link

liamoc commented Dec 3, 2015

Hi,

I was hoping to use this library to render some pixel art for a game I'm making by composing various pre-drawn images and shapes together. The issue I've encountered is that if you use an image it isn't rendered pixel-perfect, even when it is not scaled. Here's an example, where I'm trying to render each letter of a bitmap font, next to each other. Two of the letters are shown below the output of my little program. You can see how the output is "fuzzy", whereas the input is crisp.

example

@Twinside
Copy link
Owner

Twinside commented Dec 3, 2015

Hi, do you have some code?

I already encountered the problem in #15 and the fix were to shift the position by 0.5px (as can be seen in the commit

@hsyl20
Copy link

hsyl20 commented Jan 28, 2021

Hi @Twinside

This topic came up again a few weeks ago in https://groups.google.com/g/diagrams-discuss/c/TRtnvrQaT-k/m/LPq6iqmTBAAJ?pli=1

The following code demonstrates the issue.

import Codec.Picture
import Graphics.Rasterific

main :: IO ()
main = do
    i <- readPng "tiny-rectangle.png"
    case i of
        Left err -> error err
        Right img -> do
            let d = drawImage (convertRGBA8 img) 0.0 (V2 0 0)
            let img2 = renderDrawing 4 5 (PixelRGBA8 0 0 0 0) d
            writePng "tiny-rectangle-out.png" img2

With the following "tiny-rectangle.png" image: tiny-rectangle (a 4x5 image with a red box and a black line through it, it only appears blurry in the browser)

The following patch seems to fix the issue:

diff --git src/Graphics/Rasterific.hs src/Graphics/Rasterific.hs
index 088248d..e60e2ac 100644
--- src/Graphics/Rasterific.hs
+++ src/Graphics/Rasterific.hs
@@ -823,8 +823,8 @@ drawImageAtSize img@Image { imageWidth = w, imageHeight = h } borderSize ip
         stroke (borderSize / 2) (JoinMiter 0)
                (CapStraight 0, CapStraight 0) rect'
         where
-          p = ip ^-^ V2 0.5 0.5
-          rect = rectangle (V2 0 0) rw rh
+          p = ip ^+^ V2 0.5 0.5
+          rect = rectangle (V2 (-0.5) (-0.5)) rw rh
           rect' = rectangle p reqWidth reqHeight

My analysis was:

With the current code, in Graphics.Rasterific.Immediate.fillWithTexture, if we print "els" (the shape of the image we want to render) we get:

[LinePrim Line (V2 (-0.5) (-0.5)) (V2 3.5 (-0.5))
,LinePrim Line (V2 3.5 (-0.5)) (V2 3.5 4.5)
,LinePrim Line (V2 3.5 4.5) (V2 (-0.5) 4.5)
,LinePrim Line (V2 (-0.5) 4.5) (V2 (-0.5) (-0.5))]

Notice the (-0.5,-0.5) translation.

It then gets clipped to:

[LinePrim Line (V2 0.0 0.0) (V2 3.5 0.0)
,LinePrim Line (V2 3.5 0.0) (V2 3.5 0.0)
,LinePrim Line (V2 3.5 0.0) (V2 3.5 0.0)
,LinePrim Line (V2 3.5 0.0) (V2 3.5 0.125)
,LinePrim Line (V2 3.5 0.125) (V2 3.5 0.75)
,LinePrim Line (V2 3.5 0.75) (V2 3.5 2.0)
,LinePrim Line (V2 3.5 2.0) (V2 3.5 4.5)
,LinePrim Line (V2 3.5 4.5) (V2 1.5 4.5)
,LinePrim Line (V2 1.5 4.5) (V2 0.5 4.5)
,LinePrim Line (V2 0.5 4.5) (V2 0.0 4.5)
,LinePrim Line (V2 0.0 4.5) (V2 0.0 4.5)
,LinePrim Line (V2 0.0 4.5) (V2 0.0 0.0)]

My guess is that ^-^ V2 0.5 0.5 in drawImageAtSize was added to compensate for line sampling (hence rectangle sampling) which adds (0.5,0.5) to every sample coordinate (probably to get at the center of the pixels).

Texture sampling seems to use final image coordinates according to the comment in Graphics.Rasterific.Texture. So if we remove the correction:

-          p = ip ^-^ V2 0.5 0.5
+          p = ip

We get the correct shape after clipping:

[LinePrim Line (V2 0.0 0.0) (V2 4.0 0.0)
,LinePrim Line (V2 4.0 0.0) (V2 4.0 5.0)
,LinePrim Line (V2 4.0 5.0) (V2 0.0 5.0)
,LinePrim Line (V2 0.0 5.0) (V2 0.0 0.0)]

But we don't compensate the (0.5,0.5) translation in samples anymore so the output is still bad.

We can also change:

-          rect = rectangle (V2 0 0) rw rh
+          rect = rectangle (V2 (-0.5) (-0.5)) rw rh

to compensate, but then the coordinates are (-0.5,-0.5) off. So finally we also need to do:

-          p = ip ^-^ V2 0.5 0.5
+          p = ip ^+^ V2 0.5 0.5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants