2011-09-25

Adding Text to PNGs

(I'm putting this out here since Google queries for this situation didn't turn up anything useful for me.)

Last night I had to add some text ± 5000 PNG files and I knew about Zach Beane's ZPNG package for reading PNGs but I didn't know of a package that would read them.

A quicklisp:system-apropos turned up png-read written by Ramarren which looked simple enough to use so I went with that. After initially failing to get ZPNG to use png-read's image-data slot I gave up and wrote a little loop to copy the values over to zpng:data-array. This was succesful after one or two tries:

(asdf :png-read)
(asdf :zpng)

;; http://imgur.com/qtriH.png
(defparameter png (png-read:read-png-file "qtriH.png"))
(defparameter zpng (make-instance 'zpng:png :color-type :truecolor
                                  :width (png-read:width png)
                                  :height (png-read:height png)))

(loop for y from 0 below (zpng:height zpng)
      do (loop for x from 0 below (zpng:width zpng)
               do (loop for rgb from 0 below 3
                        do (setf (aref (zpng:data-array zpng) y x rgb)
                                 (aref (png-read:image-data png) x y rgb)))))

(zpng:write-png zpng "tmp.png")

I'm sure Zach had a good reason to reverse the x and y in ZPNG but I can't deduce it.

So, reading and writing the files was working but now I realized ZPNG didn't have any functionality for generating text. (Why should it? It shouldn't.) I started thinking of Vecto since I had used that before and also recalled that it actually used ZPNG for saving PNGs. Now all I needed to do was getting the original image into Vecto so text could be written over it.

Going through Vecto's source showed me I could get at the ZPNG object through the (shadowed) *GRAPHICS-STATE* global. It wasn't exported from the Vecto package but since I was in hack mode anyway it didn't really matter. I used the (slightly adapted) code above, drew some text, did a VECTO:SAVE-PNG and got an empty image.

In Vecto's source I noticed the ZPNG object had four channels instead of the three I was using so I set the fourth channel (alpha) to opaque and got my input PNG back with the custom text generated by Vecto. Done!

(asdf :png-read)
(asdf :vecto)

(use-package :vecto)

;; http://imgur.com/qtriH.png
(defparameter png (png-read:read-png-file "qtriH.png"))
(defparameter zpng (make-instance 'zpng:png :color-type :truecolor
                                  :width (png-read:width png)
                                  :height (png-read:height png)))

(with-canvas (:width (zpng:width zpng) :height (zpng:height zpng))
  (loop for y from 0 below (zpng:height zpng)
        do (loop for x from 0 below (zpng:width zpng)
                 do (loop for rgb from 0 below 4
                          do (if (< rgb 3)
                                 (setf (aref (zpng:data-array (vecto::image vecto::*graphics-state*)) y x rgb)
                                       (aref (png-read:image-data png) x y rgb))
                                 (setf (aref (zpng:data-array (vecto::image vecto::*graphics-state*)) y x rgb)
                                       255)))))
  (set-font (get-font "font.ttf") 32)
  (draw-centered-string (floor (/ (zpng:width zpng) 2))
                        (- (zpng:height zpng) 80)
                        "Hello, World!")
  (save-png "tmp.png"))

Labels: , , , , ,

2 Comments:

OpenID patrickwonders said...

Indexing with (aref img y x rgb) means that the image as seen through (row-major-aref) is just as it would come out of the PNG file (and most other image file formats).

Monday, 26 September, 2011  
Blogger Xach said...

What patrickwonders wrote. The underlying array is the octet array returned by zpng:image-data and zpng:data-array returns a CL array displaced to it. The rationale is also included right in the manual at http://xach.com/lisp/zpng/#data-array !

Monday, 26 September, 2011  

Post a Comment

<< Home