Drawing precisely to pixels using draw-line

Hello, this is my first time engaging with the Racket community. Thanks for being here. Let me know if I should do anything better to get your help on this.

I’m using draw-line to draw (pen width = 1) lines in the hope they align to pixels on a Windows machine.

Depending on where I insert such a line (as a snip in a pasteboard), I noticed that it may or may not end-up being precisely where I expect it to on a grid I'm keeping track of. I read about scaling and experimented with pen widths, smoothing, and alignment scale to no avail.

I boiled down the issue to the code below that I hope you can understand upon reading, or just run it to see my point. I know I might appear like I'm quibbling over pixels, but with many lines it looks very bad.

My question to you is: how could I get my line drawings to fall onto pixel locations reliably in this setting? What understanding or settings am I missing?

#lang racket/gui
;simple line to draw, with size and direction (horiz/vert) and snip-class wiring
(define line-snip%
  (class snip% (super-new)
    (init-field size horiz/vert? snip-class)
    (send this set-snipclass snip-class)
      
    (define/override (draw dc x y left top right bottom dx dy caret)
      (send* dc
        (set-pen "Gray" 1 'solid)
        (set-smoothing 'unsmoothed)
        (set-alignment-scale 2))
      (if horiz/vert?
          (send dc draw-line   (+ x 0) y (+ x size) y)
          (send dc draw-line x (+ y 0) x (+ y size))))))

;windowing
(let* ([frame      (new frame% [label "Imprecise Alignment Test"] 
                               [width 400] [height 400])]
       [board      (new pasteboard%)]
       [canvas     (new editor-canvas% [parent frame] [editor board])]
       [snip-class (make-object snip-class%)])
  ;wiring
  (send (get-the-snip-class-list) add snip-class)

  ;tests
  (for* ([pair        (in-list '([50 "Lines touch. Good."] 
                                 [100 "Corner has gap. How to fix?"]))]
         [horiz/vert? (in-list '(#t #f))])
    
    (let* ([placement (first  pair)]
           [line      (make-object line-snip% 50 horiz/vert? snip-class)]
           [label     (make-object string-snip% (second pair))])
      
      (send board insert line placement placement)
      (when horiz/vert?
        (send board insert label (+ placement 10) (+ placement 10)))

      (send frame show #t))))

Output

Please note that changing the alignment scale above from 2 to 1 fixes this issue as it appears here, but does not fix it when I try to build a spreadsheet-like grid, where I place horizontal and vertical lines at regular intervals, or try to place things in between them, or do more complex pixel-precise actions or placements.

I appreciate your time and expertise for any direction you may offer.

3 Likes
  1. Set the pen cap to projecting.

  1. If you use the drawing mode smoothed you need to add 0.5 to both x and y in order to get the point inside the pixel.

3 Likes

In reviewing your feedback, I checked an underlying assumption in my question, and was able to minimize this issue accordingly. Thanks for your time and attention.

2 Likes

The "just add 0.5" needs a little elaboration. In smoothed and unaligned mode the coordinates refer to the center of the pixel. If integer coordinates is used the pen is drawing between two columns of pixels and the result is a 2 pixel wide line (if the pen width is 1 (or 0 which means hair line)).

If there are no (coordinate) transformations active then the advice "add 0.5" to both x and y works.
If a transformation is active, then it becomes trickier to compute the center of the pixel.
To make it easier to draw lines without thinking about this, the mode aligned attempts to handle this automatically.

2 Likes

@alexh I saw your post here before it was removed. I would like to sincerely thank you for your blog posts on the subject. Without your detailed tutorials and great walkthroughs I would not have the moral support I feel I have now on this journey with racket/gui. A lot of the confidence I have now was directly ignited by you showing us the ropes, and all the pretty pictures and lucid explanations.

3 Likes

Hi Liam,

Thanks for the kind words. I am glad you found my blog posts useful, I think Racket can be used for a lot of cool things, and I wanted to show some of those through the blog posts.

Alex.

2 Likes

Please note that changing the alignment scale above from 2 to 1 fixes this issue as it appears here, but does not fix it when I try to build a spreadsheet-like grid, where I place horizontal and vertical lines at regular intervals, or try to place things in between them, or do more complex pixel-precise actions or placements.

Instead of drawing each line individually, try draw-lines or draw-path.

Thanks for keeping me in mind @soegaard. I'm grateful that you are my first exposure to the Racket community. Can I tell you, the mere existence of people like yourself willing to help, and the mind-shift necessary to prepare a question properly, by itself helps, in addition to the care you're offering. So, thanks!

As for grouping the lines together, I'm opting to draw them as separate snips so that I can move them later when reacting to the user. Maybe I'll fuse them when I'm ready to optimize. But your advise helps in a different way. I found that if I fuse the functions (not the objects) that do the placement of the lines, my prior issue becomes very unnoticeable. That and setting the alignment scale to 2, pen width to 1, and smoothing to unsmoothed, gets me pixel-precise crispness.

Moved

It is nice to know that others exist that share our enjoyment of Racket. Thanks again!

1 Like