Racket: Faster Pixel Access

Written by Dominik Joe Pantůček on February 27, 2020.

As you might recall, Racket provides no way of working with bitmap data both ways: writing and reading. There are only methods that can help you directly access bitmap  ARGB data and those are not designed for any high performance tasks. Read on to learn some new tricks to access the bitmap data faster…

Almost a year ago I published a post[1] about simple computer vision algorithms implemented in Racket and used by our manufacturing tools. The approach is pretty straightforward and the solution is actually easy to make. However, it is not suited for situations where performance matters.

After investigating the issue, we see where the problem is. The bitmap% class[2] can copy the ARGB[3] data from the image or its section to our bytes[4] buffer. So before we actually read anything, the data gets duplicated from one part of the memory to another part.

Also looking inside the implementation of the bitmap%‘s get-argb-pixels method reveals it is definitely not intended for any high performance applications. And the question is – can we do something about that?

Of course, the answer is: yes. Anyway, this is Racket, isn’t it? But instead of going through the hassle of optimizing the get-argb-pixels method or extending the bitmap% or bitmap-dc%[5] classes with faster access methods, I decided to play with it for a while to test some of approaches and question some assumptions that I usually make.

Assuming we are running on a little-endian[6] machine, we know that ARGB data for each pixel fits into 32 bits – that is four bytes – with the alpha value being the most significant, red value after it, then green and last is blue. Which means – on little-endian system – that 0xAARRGGBB is the 32-bit number assigned to all possible colors.

With just a little coding and some bitmap% internals, we can create a very fast getpixel function:

(require racket/draw/unsafe/cairo
         (only-in ffi/unsafe ptr-ref _uint32)
         racket/require
         (filtered-in
          (λ (name)
            (and (regexp-match #rx"^unsafe-fx" name)
                 (regexp-replace #rx"unsafe-" name "")))
          racket/unsafe/ops))

(define (make-fast-getpixel bm)
  (define width (send bm get-width))
  (define height (send bm get-height))
  (define s (send bm get-handle))
  (define data (cairo_image_surface_get_data* s))
  (define stride (cairo_image_surface_get_stride s))
  (λ (x y)
    (if (and (fx>= x 0) (fx< x width)
             (fx>= y 0) (fx< y height))
        (ptr-ref data _uint32 'abs
                 (fx+ (fx* y stride) (fxlshift x 2)))
        #f)))

Using the get-handle method gives us the cairo_image_surface_t struct. Then we get the data pointer from it and the stride size in bytes. A stride is basically a row of pixels but there can be some more bytes to ensure proper memory alignment.

This trick allows you to get the pixel data directly from the underlying cairo image structure without copying any data to a different part of the memory. As a side effect, using unsafe ptr-ref[7] and unsafe fixnums[8], it is really fast!

On a side note - you can write the pixel ARGB values this way too. Just use ptr-set! and be done with that.

 

Happy hacking and see you next Thursday!


References

1. https://trustica.cz/en/2019/02/28/simple-cv-in-racket/

2. https://docs.racket-lang.org/draw/bitmap_.html

3. Wikipedia contributors. (2020, February 8). RGBA color model. In Wikipedia, The Free Encyclopedia. Retrieved 21:37, February 28, 2020, from https://en.wikipedia.org/w/index.php?title=RGBA_color_model&oldid=939753045#ARGB_(word-order)

4. https://docs.racket-lang.org/reference/bytestrings.html

5. https://docs.racket-lang.org/draw/bitmap-dc_.html

6. Wikipedia contributors. (2020, February 11). Endianness. In Wikipedia, The Free Encyclopedia. Retrieved 21:39, February 28, 2020, from https://en.wikipedia.org/w/index.php?title=Endianness&oldid=940330306

7. https://docs.racket-lang.org/foreign/foreign_pointer-funcs.html#(def._((quote._~23~25foreign)._ptr-ref))

8. https://docs.racket-lang.org/reference/fixnums.html