0063: Cairo VII – Draw and Save Images
So, this time we’re going to draw some images and then save them.
Warning: These images are not high art and may or may not cause harm to your artistic sensibilities. (You’ve now been officially cautioned.)
In fact, because all these images are just a rectangle with text overlaid, I’m not even going to go over how they’re drawn. I’ll just say this one thing…
If you want a draw operation to be on top of another—like text overlaid on a rectangle, for instance—foreground draw operations need to be done after background draw operations. You probably sorted that out on your own, but there it is anyway.
Saving Images – Basic Procedure
Every time we save an image, we carry out the same set of operations:
- find the size of the
DrawingArea
, - grab the full expanse of the
DrawingArea
’sSurface
that we want to save and stuff it into a buffer, - set up the save options and option values for the image (this is different for each format), and
- save the image.
But since each has their own save options, we’ll look at each individually starting with…
Saving a JPeg
Here’s the initialization section:
GtkAllocation size;
Pixbuf pixbuf;
string[] jpegOptions, jpegOptionValues;
int xOffset = 0, yOffset = 0;
We used a GtkAllocation
object before, the main purpose of which is to get the size allocated to a Widget
—in this case, the DrawingArea
—so we don’t have to guess.
The Pixbuf
is the buffer we’re going to stuff the image into as we prepare to save and the rest are pretty obvious:
jpegOptions
: an array of save options,jpegOptionValues
: an array of values for each of the save options,xOrigin
,yOrigin
: the offset from the top-left corner of theDrawingArea
.
Note: If xOffset
and yOffset
are non-zero, we won’t be saving the entire image.
Note 2: The last two arguments to the getFromSurface()
call (see the onDraw()
snippet below), if less than the width and height of the Surface
, will also save less than the entire image. Taking these two notes together, I’m sure you arrived at the conclusion that you can save any rectangular area of the Surface you choose.
I haven’t bothered with width and height variables here because these examples all save the entire DrawingArea
and so we use the width
and height
fields from the GtkAllocation
as we’ll see in a moment.
The constructor is so mundane as to warrant skipping in this discussion… so we shall.
As for the callback, we’ll leave out the drawing bit and go right for the meat:
bool onDraw(Scoped!Context context, Widget w)
{
// set up and draw a rectangle
// (see code file)
// set up and draw text
// (see code file)
getAllocation(size); // grab the widget's size as allocated by its parent
pixbuf = getFromSurface(context.getTarget(), xOffset, yOffset, size.width, size.height); // the contents of the surface go into the buffer
// prep and write JPEG file
jpegOptions = ["quality"];
jpegOptionValues = ["100"];
if(pixbuf.savev("./rectangle_hw.jpg", "jpeg", jpegOptions, jpegOptionValues))
{
writeln("JPEG was successfully saved.");
}
return(true);
} // onDraw()
And here’s what’s happening:
- get the size of the
DrawingArea
withgetAllocation(size)
, - grab either the entire
Surface
or a subsurface (by supplying a non-zero offset and/or a width and height less than theSurface
’swidth
andheight
) and stuff it into a buffer (that’s thePixbuf
), - set up the JPeg save options and their values, and
- call
Pixbuf.savev()
to save the image.
Save Options for JPeg
- “icc-profile” - the complete ICC profile encoded into base64 (which, you’ll note, I didn’t bother with)
- “quality” - 0 … 100
- “x-dpi” - dots per inch (reasonable values: 50 to 300)
- “y-dpi” - dots per inch (same as x-dpi: 50 to 300)
And that’s all there is to it.
Saving a PNG
Since everything else is the same, let’s skip right to the part of the callback that does the saving:
// prep and write to a PNG
pngOptions = ["x-dpi", "y-dpi", "compression"];
pngOptionValues = ["150", "150", "1"];
if(pixbuf.savev("./rectangle_hw.png", "png", pngOptions, pngOptionValues))
{
writeln("PNG was successfully saved.");
}
So the differences here (compared to our first example) are:
- set up the PNG save options,
- the values for each, and
- save with the
.png
file extension.
Save Options for PNG
- “x-dpi” dots per inch (reasonable range is 50 to 300)
- “y-dpi” dots per inch (same as x-dpi: 50 to 300)
- “tEXt::key” - an ASCII string of length 1 – 79, UTF-8 encoded strings
- “compression” - 0 … 9
- “icc-profile” - the complete ICC profile encoded into base64
Saving a TIFF
As with the others, just set up the options and save:
tiffOptions = ["bits-per-sample", "compression"];
tiffOptionValues = ["8", "1"];
if(pixbuf.savev("./rectangle_hw.tiff", "tiff", tiffOptions, tiffOptionValues))
{
writeln("TIFF was successfully saved.");
}
Save Options for TIFF
- “bits-per-sample”
- “1” for saving bi-level CCITTFAX4 images
- “8” for saving 8-bits per sample
- “compression”
- “1” for no compression
- “2” for Huffman
- “5” for LZW
- “7” for JPEG
- “8” for DEFLATE (see the libtiff documentation and tiff.h for all supported codec values)
- “icc-profile” - (zero-terminated string) containing a base64 encoded ICC color profile.
Saving a BMP
Everything’s the same, but there are no listed options so you have to pass in two empty arrays like this:
if(pixbuf.savev("./rectangle_hw.bmp", "bmp", [], []))
{
writeln("BMP was successfully saved.");
}
Conclusion
And that’s most of what there is to saving images using Cairo. Next time, we’ll start digging into animation.
Comments? Questions? Observations?
Did we miss a tidbit of information that would make this post even more informative? Let's talk about it in the comments.
- come on over to the D Language Forum and look for one of the gtkDcoding announcement posts,
- drop by the GtkD Forum,
- follow the link below to email me, or
- go to the gtkDcoding Facebook page.
You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.
© Copyright 2024 Ron Tarrant