» Monday, 27 September A.D. 2010

more pixel conversion

A couple of thoughts on the previous post.

First, thanks to everyone who pointed out that I should have been using multiple-value-prog1 instead of prog1.

Second, one person commented that they didn't really understand the point of the post. I should have explained the context behind the post.

If you look at graphics code--or at least the open-source graphics code I've happened to look at in the past week or so, maybe there's better stuff out there--there's a lot of repeated code. Cut-and-paste programming, sure, but also just a lack of abstraction in general. Maybe that's a result of using C; maybe it's a result of a lack of imagination. Maybe using C causes a lack of imagination. wink

I wanted to see if I could be more abstract. If I have to write pixel conversion routines from grayscale to RGBA and CMYK to RGBA, I think it'd be a clever thing to only write the “stuff pixels in RGBA format here” once. Likewise, if I have to write CMYK to grayscale, I shouldn't have to rewrite the “grab pixels in CMYK format” bits again. Writing out the core conversion loops gets tedious after a while. We have first-class functions; we ought to be able to separate out the looping logic from the “do this inside the loop” logic. And so on.

Part of the subtext also is that for me is that when I sit down to write Lisp, I often get distracted by the One True Way to do it, the way that will yield the fastest code out of the compiler the first time. I might not always succeed at doing it, but I get distracted by it.

For whatever reason, I don't have this problem when writing Python or even C, only in Lisp. I don't write prototyping code very well in Lisp. So whipping out thirty lines of code that looks reasonably useful and that might not be screaming performance-wise is a good exercise for me.

From all that came the sketch of the ideas yesterday: write the “read a pixel in this format” once, write the “write a pixel in this format” once, write the core conversion loop once, etc. As a bonus, a function like:

(defmethod pixel-reader ((format rgba) buffer start)
  #'(lambda ()
      (multiple-value-prog1 (values (aref buffer start)
                                    (aref buffer (+ start 1))
                                    (aref buffer (+ start 2))
                                    (aref buffer (+ start 3)))
        (incf start 4))))

would work regardless of the particular datatype of the RGBA components, assuming the inheritance hierarchy is written sanely. 8-bit bytes, 16-bit words, single-precision floats, all of these and more are taken care of with one function. Granted, you'd probably want to write separate methods for each datatype if you were being serious about taking this route.

I felt pretty good about my codesketch. I prototyped, it looked reasonable, and it didn't have to cons 0 bytes and run 20x faster than the naive method. I wanted to share. That's the explanation that should have gone in yesterday's post.

posted by Nate @ 9:01PM