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"))
2 Comments:
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).
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 !
Post a Comment
<< Home