Last Train Out Of Cairo
After recovering from Christmas, and the terrible events of late 2013, it’s time to put my nose back to the grindstone with the printer module. My latest task: make the printer module not be terrible.
What’s so bad about the printer module, you ask? The short answer is all the things. All bad. Every single one of them. It’s slow. It doesn’t print right. It consumes way too much resources. It makes my cat sad.
The Brotherhood Of The Printer Developer
It all started when trying to figure out this whole printing thing. It turns out that printer development is one of those secret development clubs. There are no tutorials, the API documentation leaves something to be desired, and printing in Linux is bad in general. In short, it’d be easier to join the Illuminati than to infiltrate the Dark Cabal of Printer Developers.
Just by reading the CUPS API documentation, it’s not difficult to hash out a simple hello world type printer application. However, as anybody who has ever printed something knows, printing has lots of knobs to fiddle. However, the CUPS API does not seem to have functions corresponding to things like paper size and DPI.
During my research, I managed to turn up all of one StackOverflow post on the topic. The gist of it being “you set that up yourself using PostScript and send that to CUPS.” It also provides a sample implementation using Cairo.
I decided to give it a shot. If nothing else, it would be a good introduction to the Cairo library for me. In my youth, I was fond of using Java’s Graphics2D library to make all sorts of fancy UI elements. In slightly oversimplified terms: Cairo is the GTK equivilent to Graphics2D. This isn’t entirely accurate: Cairo is a vector graphics library that GTK just happens to have adopted. Cairo is very usable outside the context of GTK; it can author a variety of file types including pdf and postscript.
I decided I’d use Cairo to author postscript within the printer module.
cairo_surface_t * base = cairo_ps_surface_create( "[temp_file].ps", [WIDTH], [HEIGHT]); cairo_surface_t * image = cairo_image_surface_create_from_png( "[photo_strip_filename]");
First, I create 2
cairo_surface_t pointers. A Cairo surface is sort of like a canvas that you paint on. For those of you familiar with Java’s Graphics2D, you can think of it like your Graphics2D instance.
cairo_surface_t is the base class of all Cairo surfaces, there are a variety of surface types for things like PostScript, PDF, PNG, X Windows, or whatever else. The first surface is an empty PostScript surface that represents our finished product. The second surface is created using our .png formatted photo strips.
cairo_t * working = cairo_create(base);
cairo_surface_t is your canvas, then
cairo_t is your brush. Think of it like a Java Stroke object. Right here, we are creating a new
cairo_t from our base Cairo surface.
cairo_set_operator(working, CAIRO_OPERATOR_DEST_OVER); cairo_set_source_surface(working, image, 0, 0); cairo_paint(working);
The basic idea is that you apply operations to a
cairo_t, then you apply your
cairo_t to a
cairo_surface_t. Here, we are compositing our PNG surface over our PostScript surface. First we set the operator of our
cairo_t to composite over the top. Next, we set the
cairo_t to have our image Surface as its source surface. Finally, we call
cairo_paint which will apply our
cairo_t to the base surface.
This call saves our PostScript file.
cairo_surface_destroy(base); cairo_surface_destroy(image); cairo_destroy(working);
No C function is complete without a bunch of cleanup at the end. Here we call
cairo_surface_destroy to free our
cairo_surface_t pointers and then we call
cairo_destroy to free our
PS: You’re Doing It Wrong
That all seemed pretty great right? I thought so too. Here’s the problem: go check out that PostScript file you just created. Notice how it is 200 MB? Yeah…
It turns out that enormous PostScript files is a common problem. While we could just make sure to delete this file when we’re done, we’re still creating this gigantic file, then shoving it down our printer’s throat. My printer is on WiFi, so it takes a good 2 minutes to print this file, and brings my computer to a crawl while it’s doing it. No user is going to want to wait 2 minutes for their photo strip to print.
The second problem is actually a “feature” of PostScript. PostScript is a document layout language, and due to this the printer will take your PostScript file’s word for what it wants done. This sounds nice, until you realize that Cairo isn’t actually a PostScript authoring library. Cairo’s ability to tune a PostScript file is pretty limited. Specifically, this is a problem for things like DPI. I’m trying to print high quality images at 600 DPI. However, Cairo can’t set this in the PostScript, so the printer ends up spitting out a massively blown up copy of the image. This will not do…
So, I’m back to square one. PostScript is a dead-end, and CUPS won’t let me customize my job. What to do…
I thought back to my hello world printer application. I was able to print a random image that was blown up to the size of my paper. What if I print an image that’s exactly the correct size for my paper? I gave it a shot, and sure enough there was no scaling issues!
I can set my printer to 600 DPI, then print my (600 * 4) x (600 * 6) image and it just works, just like Some Guy promised it would. All is once again well in the world.
Plus, I got some Cairo experience under my belt. Look forward to fancy curved lines and gradients in future versions of DMP Photo Booth! (Joking, I promise)