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 to about 5000 PNG files and I knew about Zach Beane’s ZPNG package for writing 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"))
Comments
patrickwonders
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
Xach
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