[cl-gd-devel] with-transformation off-by-one bug?
Hi Edi, I'm using CL-GD with both LispWorks 5.0 on OS X and CMUCL 19b on FreeBSD to plot some graphs, and I'm very grateful for your work here. CL-GD is slick to use and more than fast enough for generating sparklines on an interactive web-based dashboard. I'm using WITH-TRANSFORMATION to simplify the graph plotting, and it's not behaving the way I would expect. As I read it in the documentation, this... (with-transformation (:x1 0 :width width :y1 0 :height height) ...) ... should transform the coordinate system from this... 0,0 -----> width - 1 | | | V height - 1 ... to this... height - 1 ^ | | | 0,0 -----> width - 1 Instead, I get this... height ^ | | | 0,1 -----> width - 1 The y-axis transformation isn't consistent with that of the x-axis. What's even more confusing, is that if the x-axis transformation does work then the following should only complain about y1, y2, and height, and not about x1, x2, and width, but that's not the case: (let ((height 100) (width 100)) (with-transformation (:x1 0 :x2 (1- width) :width width :y1 0 :y2 (1- height) :height height) ...)) Indeed, when I checked the macroexpansion, I see... (UNLESS #:X154021 (SETQ #:X154021 (- #:X254022 #:WIDTH54023))) (UNLESS #:X254022 (SETQ #:X254022 (+ #:X154021 #:WIDTH54023))) (UNLESS #:WIDTH54023 (SETQ #:WIDTH54023 (- #:X254022 #:X154021))) (UNLESS #:Y154024 (SETQ #:Y154024 (- #:Y254025 #:HEIGHT54026))) (UNLESS #:Y254025 (SETQ #:Y254025 (+ #:Y154024 #:HEIGHT54026))) (UNLESS #:HEIGHT54026 (SETQ #:HEIGHT54026 (- #:Y254025 #:Y154024))) Again, I would have thought, instead, that... width = (x2 - x1) + 1 height = (y2 - y1) + 1 So, the y-axis transformation isn't what I expected, but the macroexpansion suggests that the x-axis transformation should be off by one as well, yet it isn't. Am I nuts? Below is the snippet I was running to grok WITH-TRANSFORMATION. I can mail you the images I got, if you like. Thanks in advance, Mike (let* ((height 100) (width 100) (origin+1 (list 1 1)) (x-axis+1 (list 10 1 20 1 30 1 40 1 50 1 60 1 70 1 80 1 90 1 width 1)) (y-axis+1 (list 1 10 1 20 1 30 1 40 1 50 1 60 1 70 1 80 1 90 1 height)) (x-axis-end+1 (list (1- width) 1)) (y-axis-end+1 (list 1 (1- height)))) (cl-gd:with-image* (width height) (allocate-color 255 255 255) (let ((black (allocate-color 0 0 0)) (red (allocate-color 255 0 0)) (green (allocate-color 0 255 0)) (blue (allocate-color 0 0 255)) (yellow (allocate-color 255 255 0))) (draw-rectangle* 0 (1- height) ; or 0 and ... (1- width) 0 ; (1- height), doesn't matter :color yellow) (draw-rectangle* 0 height ; or 0 and ... width 0 ; ... height, doesn't matter :color black) (set-pixels origin+1 :color red) (set-pixels x-axis+1 :color green) (set-pixels x-axis-end+1 :color blue) (set-pixels y-axis+1 :color green) (set-pixels y-axis-end+1 :color blue)) (write-image-to-file "/tmp/test1.png" :if-exists :supersede)) (cl-gd:with-image* (width height) (allocate-color 255 255 255) (let ((black (allocate-color 0 0 0)) (red (allocate-color 255 0 0)) (green (allocate-color 0 255 0)) (blue (allocate-color 0 0 255)) (yellow (allocate-color 255 255 0))) (with-transformation (:x1 0 ;;:x2 99 :width width :y1 0 ;;:y2 99 :height height ) (draw-rectangle* 0 (1- height) ; or 0 and ... (1- width) 0 ; (1- height), doesn't matter :color yellow) (draw-rectangle* 0 height ; or 0 and ... width 0 ; ... height, doesn't matter :color black) (set-pixels origin+1 :color red) (set-pixels x-axis+1 :color green) (set-pixels x-axis-end+1 :color blue) (set-pixels y-axis+1 :color green) (set-pixels y-axis-end+1 :color blue)) (write-image-to-file "/tmp/test2.png" :if-exists :supersede)))) -- Michael J. Forster <mike@sharedlogic.ca>
On Tue, 11 Sep 2007 11:13:24 -0500, "Michael J. Forster" <mike@sharedlogic.ca> wrote:
I'm using WITH-TRANSFORMATION to simplify the graph plotting, and it's not behaving the way I would expect. As I read it in the documentation, this...
(with-transformation (:x1 0 :width width :y1 0 :height height) ...)
... should transform the coordinate system from this...
0,0 -----> width - 1 | | | V height - 1
... to this...
height - 1 ^ | | | 0,0 -----> width - 1
Hmm, why do you think that? I would expect a transformation from 0,0 -----> image-width | | | V image-height to height ^ | | | 0,0 -----> width Why do you want to subtract 1? What do you expect to happen if WIDTH or HEIGHT /are/ 1?
Below is the snippet I was running to grok WITH-TRANSFORMATION. I can mail you the images I got, if you like.
An easy way to experiment with CL-GD's transformations would probably be something like this: (in-package :cl-gd) (defun foo (x1 y1 x2 y2 &optional (image *default-image*)) (with-transformed-alternative ((x1 x-transformer) (y1 y-transformer) (x2 x-transformer) (y2 y-transformer)) (print (list x1 y1 x2 y2)))) (with-image* (... ...) (with-transformation (:x1 ... :width ... :y1 ... :height ...) (foo ... ... ... ...))) Cheers, Edi.
On 13-Sep-07, at 4:14 AM, Edi Weitz wrote:
On Tue, 11 Sep 2007 11:13:24 -0500, "Michael J. Forster" <mike@sharedlogic.ca> wrote:
I'm using WITH-TRANSFORMATION to simplify the graph plotting, and it's not behaving the way I would expect. As I read it in the documentation, this...
(with-transformation (:x1 0 :width width :y1 0 :height height) ...)
... should transform the coordinate system from this...
0,0 -----> width - 1 | | | V height - 1
... to this...
height - 1 ^ | | | 0,0 -----> width - 1
Hmm, why do you think that? I would expect a transformation from
0,0 -----> image-width | | | V image-height
to
height ^ | | | 0,0 -----> width
Why do you want to subtract 1? What do you expect to happen if WIDTH or HEIGHT /are/ 1?
I was thinking of lines on a graph as zero-based vectors. Thus, a vector of length (or axis of width or height) n is indexed from 0 to n-1. And, indeed, with the following... (let ((height 100) (width 100) (x-axis-width (list width 1)) (y-axis-height (list 1 height)) (x-axis-width-1 (list (1- width) 1)) (y-axis-height-1 (list 1 (1- height)))) (cl-gd:with-image* (width height) (set-pixels x-axis-width) (set-pixels y-axis-height) (set-pixels x-axis-width-1) (set-pixels y-axis-height-1) ...)) ... only the last two pixels are displayed on my 100x100 pixel image. The axes are indexed from 0 to n-1. If WIDTH and HEIGHT are 1, I only expect to see the pixel generated by... (set-pixels '(0 0)) Is my brain off by one? Thanks and best regards, Mike -- Michael J. Forster <mike@sharedlogic.ca>
On Thu, 13 Sep 2007 08:29:56 -0500, "Michael J. Forster" <mike@sharedlogic.ca> wrote:
I was thinking of lines on a graph as zero-based vectors. Thus, a vector of length (or axis of width or height) n is indexed from 0 to n-1. And, indeed, with the following...
(let ((height 100) (width 100) (x-axis-width (list width 1)) (y-axis-height (list 1 height)) (x-axis-width-1 (list (1- width) 1)) (y-axis-height-1 (list 1 (1- height)))) (cl-gd:with-image* (width height) (set-pixels x-axis-width) (set-pixels y-axis-height) (set-pixels x-axis-width-1) (set-pixels y-axis-height-1) ...))
... only the last two pixels are displayed on my 100x100 pixel image. The axes are indexed from 0 to n-1. If WIDTH and HEIGHT are 1, I only expect to see the pixel generated by...
(set-pixels '(0 0))
I think where CL-GD and you disagree is that currently the outermost "virtual" point (X2 or (+ X1 WIDTH)) is mapped to the first point which is /not/ on the real image (and similar for the other axis), i.e. the transformation arguments kind of describe a half-open interval. I can see how this is inconvenient and I wouldn't mind changing that unless someone else provides important reasons not to do it. Could you please check if the attached patch does what you want? As for your complaints about the x-axis transformation not being consistent with the y-axis transformation, are you sure you're not seeing rounding errors? Or maybe this happened because of the "one off" stuff plus the fact that the "virtual" y-axis usually points into the opposite direction? Cheers, Edi.
On 14-Sep-07, at 2:19 AM, Edi Weitz wrote:
On Thu, 13 Sep 2007 08:29:56 -0500, "Michael J. Forster" <mike@sharedlogic.ca> wrote:
I was thinking of lines on a graph as zero-based vectors. Thus, a vector of length (or axis of width or height) n is indexed from 0 to n-1. And, indeed, with the following...
(let ((height 100) (width 100) (x-axis-width (list width 1)) (y-axis-height (list 1 height)) (x-axis-width-1 (list (1- width) 1)) (y-axis-height-1 (list 1 (1- height)))) (cl-gd:with-image* (width height) (set-pixels x-axis-width) (set-pixels y-axis-height) (set-pixels x-axis-width-1) (set-pixels y-axis-height-1) ...))
... only the last two pixels are displayed on my 100x100 pixel image. The axes are indexed from 0 to n-1. If WIDTH and HEIGHT are 1, I only expect to see the pixel generated by...
(set-pixels '(0 0))
I think where CL-GD and you disagree is that currently the outermost "virtual" point (X2 or (+ X1 WIDTH)) is mapped to the first point which is /not/ on the real image (and similar for the other axis), i.e. the transformation arguments kind of describe a half-open interval. I can see how this is inconvenient and I wouldn't mind changing that unless someone else provides important reasons not to do it. Could you please check if the attached patch does what you want?
As for your complaints about the x-axis transformation not being consistent with the y-axis transformation, are you sure you're not seeing rounding errors? Or maybe this happened because of the "one off" stuff plus the fact that the "virtual" y-axis usually points into the opposite direction?
Hi Edi, Thanks for getting back to me so quickly. I will be away on business for a couple of days, so if you'll let me ponder your comments and play with the patch, I'll get back to you early next week. And I hope you didn't take any of this as a complaint. It's really a matter of me wrapping my brain around CL-GD's behaviour in these corner cases (no pun intended). Regardless, WITH-TRANSFORMATION is much nicer than the translation and scaling hacks required to use, say, PHP's GD bindings. Thanks again, Mike -- Michael J. Forster <mike@sharedlogic.ca>
On Fri, 14 Sep 2007 05:17:16 -0500, "Michael J. Forster" <mike@sharedlogic.ca> wrote:
Thanks for getting back to me so quickly. I will be away on business for a couple of days, so if you'll let me ponder your comments and play with the patch, I'll get back to you early next week.
OK, no problem.
And I hope you didn't take any of this as a complaint.
Nah, that's OK. My reaction basically showed that I myself never used these transformations in earnest... :)
participants (2)
-
Edi Weitz -
Michael J. Forster