GIMP Engrave plugin (script-fu)
This project was inspired by a beautiful photo (see fragment right) that my parents have.
It's about 2x3 ft, and shows two ballet dansers (from the Royal Winnipeg Ballet). I have always been fascinated by
the way the image was represented by lines of varying widths, and wanted to achieve
a similar effect. I couldn't find what I wanted under GIMP, the existing plugin that does a similar thing was too ragged to my liking.
This plugin is an alternative to the existing Engrave plugin supplied with GIMP and found under
Filters -> Distorts -> Engrave.
This version registers itself under
Filters -> Artistic -> Engrave
to avoid collision with the existing one, and also because it seems a more appropriate place to me.
The original plugin has a different approach which is perhaps more suited to smaller final images
(more on this later).
This plugin (I feel) works best on rather large final images (with 100 or more lines of varying width).
This script is still somewhat work-in-progress, but I believe it is now usable.
Basically, the plugin transforms a grayscale image into a series of horizontal lines of varying widths.
To get other orientations, just work on a rotated image, and rotate back at the end. Or edit the script...
Section 1 below explains how to use the plugin and the meaning of its parameters.
Section 2 describes the process for those interested (with a bit on development history).
Version history:
- Version 1.0, 2009-Apr-02: initial version on the web.
- Version 1.1, 2009-Apr-09: minor improvements, registered in GIMP plugin registry.
- More pre-processing options,
- Accepts images in any mode (converts to grayscale as a first step).
- 2009-Apr-13: fix for non-English environments.
1. Using the plugin
This section describes how to use the plugin. It works for me under GIMP 2.6.6, Windows binary.
The plugin can be downloaded here (engrave.scm);
it goes in your user script directory. Note that there is a bug under GIMP 2.6.1 which requires a hack (see the script).
I don't know what the situation is for versions in between, or prior to 2.6.1.
Note also that the plugin overwrites the clipboard.
In the following, plugin parameters are normally quoted.
1.1 Checking that plugin works properly
Since changes to GIMP (esp. around scaling) could break this script, here's an easy
way to check it:
open the 5x5 test image [
],
process it with the script using all default parms, except that "Pre-processing -> Pre-scaled"
needs to be selected, the output image should be close to the first below
(five black cone-like patterns pointing in alternating directions):
 Good
|
|  Bad (need hack)
|
If you see something like the second image, there is still some hope.
You are possibly seeing the
same problem I was seeing during initial development (under 2.6.1) and for which
I included a hack in the script.
Find the line of code that contains "(set! hack 0)"
in engrave.scm and comment it out by putting a semicolon (";") at the
beginning of the line.
Then do Filters -> Script-Fu -> Refresh scripts. Try the test again:
you won't see exactly the "Good" image above, but if you see four
black cone-like patterns pointing in alternating directions, you should be fine.
1.2 Preparing the initial image
Personally, I prefer to start from a small image, where each row of pixels
will become a line of varying width (within a group of "Line width" rows in the final image).
When working this way, the final image will scaled up by the chosen "Line width".
So for example, if the initial image
is 10x20 pixels, and the "Line width" is 15, the final image will be 150x300.
However, by default, the plugin will work
within the original image size
(rounding to a multiple of "Line width" -- internally, it first reduces it, then expands it again).
As it works, the plugin will create a mask in addition to the final image, so it will need memory for two grayscale
images of the final size. For example, a 300x300 image, with "Pre-processing -> Pre-scaled" selected and a "Line width" 25, would result in a final image of 7500x7500, about 57 Mbyte, so
the plugin would need twice as much during processing.
Note that the image will be converted to grayscale as part of the process, but any image mode is accepted as input, with or without alpha channel.
1.3 Plugin parameters
Here's what the plugin's dialog looks like, with the default parameters.
The starting image I'll be using for the examples is the following
(cropped to 76x59 pixels, grayscale mode):
Wilber is the work of Tuomas Kuosmanen ("Tigert") and is GPL licensed.
In the following discussion, the examples assume the default values
unless otherwise noted
(except that "Line width" will be 15 instead of 25 to keep the examples smaller,
and that "Pre-processing -> Pre-scaled" is always selected).
Note that some adjustments are still being made to gamma,
and hence the examples aren't quite right in this area (most are a bit too dark);
I am working on this.
In the examples below, I show a section of the eye (an interesting area for this script).
The parameters of the plugin are:
- "Pre-processing": This option specifies what is done to the image before starting the main algorithm. The choices are:
- "Downscale both": Select if the image has not been scaled down: in this case, the process will stay within the size of the original image.
This is the default, so users don't accidently create huge images.
- "Downscale vertical": See section "1.5 Pre-processing -> Downscale vertical" below for discussion of this option.
Similar to previous, processing stays within the original image size.
- "Pre-scaled": Select if the image has already been scaled down or is small (as here), and you expect it to be scaled up by the "Line width" (next parm) in both directions.
- "Line width": The width of the horizontal lines. Range 3-127. I find 25 is pretty much a minimum width for nice results.
Here are a few examples:
 Line width = 15
|
|  Line width = 7
|
- "Gamma": Gamma to use. Range: 0.5-3.5. See discussion on gamma later in this section.
Here are two examples:
 Gamma 1.9 (default)
|
|  Gamma 1.1
|
- "Horizontal interpolation": As part of the process (explained in the next section), one step is to upscale the image horizontally.
The choices are:
 Cubic (default)
|
|  Linear
|
|  None
|
Lanczos is not proposed because it seems to spill on neighbouring lines of pixels.
Linear is a bit more angular than Cubic, it's barely visible here.
- "Line type": Specifies how the blacks and whites are placed to get the varying width.
In all cases, the amount of black and white is the same, only the distribution changes.
The choices are:
 Center black (default)
|
|  Center white
|
|  Black on bottom
|
|  Black on top
|
Perhaps the first would be more suitable for a black subject on white background, the second for the opposite case.
From a distance, all four above, and the three of previous bullet, look very similar.
- "Version 2": A slightly different approach (which I think yields better results for most images).
Unchecking "Version 2", with "Blur radius" set to 1, results in full B&W (no grey levels),
is more ragged (pixels visible), but probably tracks gamma better.
See later for what changes if it is unset.
- "Blur radius": As a final step of the process, an horizontal blur is done which improves the results markedly.
This parameter specifies the horizontal blur radius.
Range is 1-9, where 1 means skip this step.
Here are a few examples:
 Blur radius = 3 (default)
|
|  Blur radius = 1 (no blur)
|
|  Blur radius = 9
|
- "Early exit": Checking this (TRUE) causes the process to be stopped a bit early, so one can control the final steps.
See next section for details. Not very useful.
And the final result is (with "Line width" at 15, "Gamma" at 1.6 --
the shadow does not come out nice here):
Fill your screen, stand back 20-30 feet...
1.4 Gamma
Selecting the proper gamma depends on how the resulting image is to be viewed.
If the final image is to be viewed from a distance, such that the lines are barely visible, then a "Gamma" close to the standard 2.2
should be chosen, so that the gray levels are correctly represented. At "Gamma" 2.2, the quantity of black and white should result in
proper gray levels (assuming I got it right, which is still TBD).
For example, 50% gray, corresponding to a pixel value of 185 (x0b9),
would result in a line which is half white, half black.
If the final image is to be viewed from closer, or if graylevel accuracy isn't desirable (eg. for a logo),
then such a high gamma will probably result in too much black. In such cases, a lower gamma should be chosen.
With a "Gamma" of 1.0, the amount of black and white will be proportional to the pixel value (so 128 will be half black, half white).
1.9 should be a good compromise for images to be seen from a distance, but where
the lines are still quite clearly visible (as it was in my parent's photo). Even at 1.9,
it seems like the image (see links below) is too dark from a distance, which means I may have gotten some
things not quite right wrt gamma and end effects.
I think it works better with more lines (15 is few, I think 25 is
a minimum for decent results); fewer lines seem to result in a darker image. The best is to
experiment... (for now, a lower gamma seems necessary, still working on this).
Final guidelines for gamma-correct results in "Version 2" might be roughly something like:
- "Line width" 15, use "Gamma" 1.6
- "Line width" 25, use "Gamma" 1.9
In version 1, gamma was achieved by unequally spacing the values of the mask to get
the desired effect. If "Version 2" is selected (default), inverse gamma is applied to the image at the beginning
of the script such that equally spaced values can be used in the mask later. The advantage of
this second approach is in smoothing when doing the levels and blur at the end:
to do a levels with something else than black=0 and white=1, we need equally spaced values in the mask.
Not yet available:
For comparison purposes, here are four images (you could print them on 5x7 paper,
or view them at max size, one at a time, on your monitor).
In both cases, stand back 20 feet or more:
1.5 Pre-processing -> Downscale vertical
The initial version of this script started from a small image, and expanded it
- vertically (by "Line width") to create the lines of varying widths,
- and horizontally (by same) using the chosen interpolation.
The result is an image scaled up by "Line width" in both directions. This approach is
selected thru "Pre-processing -> Pre-scaled".
Then a pre-processing phase was added to the script so that one could work within the initial
image size. This pre-processing reduces the image in both directions by "Line width"
then the main part of the script does the expansion as described above.
This results in loss of resolution in both
the vertical axis (expected, since a group of rows is summarized in a line of varying
width), but also in the horizontal axis (downscale, then upscale by chosen "Horizontal interpolation").
This is what I prefer, it results in the same resolution in both directions
(both directions get "summarized", vertically by line width,
horizontally thru smooth transitions in line widths).
This approach is selected thru "Pre-processing -> Downscale both", and is the default.
However, the horizontal downscale followed by upscale can be seen as a waste of
resolution. The choice "Pre-processing -> Downscale vertical" leaves the horizontal
axis of the image alone. Processing is still done within the starting image size, but
this results in much more detail in the horizontal axis.
This approach is very close to how the other GIMP Engrave plugin works.
The GIMP 2.6 on-line documentation [from docs.gimp.org] has the following images:
![[image from gimp.org]](http://docs.gimp.org/2.6/en/images/filters/examples/taj_orig.jpg) Original image
|
| ![[image from gimp.org]](http://docs.gimp.org/2.6/en/images/filters/examples/distort-taj-engrave.png) "Engrave" applied
|
With the new Engrave plugin described here, a very similar result can be
obtained. This is what it looks like for this same image, varying "Pre-processing" option,
with the following other parameters:
- "Line width" = 10
- "Gamma" = 1.1
- "Horizontal interpolation" = Cubic (default)
- "Line type" = Black on bottom
- "Version 2" unchecked
- "Blur radius" = 1
 Downscale both
|
|  Downscale vertical
|
Notice how the last one is very similar to the image from the other GIMP Engrave plugin.
1.6 Various usage notes
- Obviously, this filter is useful with simple pictures, where form dominates and color isn't vital; it might not be very interesting on a landscape with lots of details.
- It can allow to print a very small image to a decent size. For example, the image used here (with it's very small size)
wouldn't print well
in 8x10, but the larger image from the filter is quite printable (altho, of course, it won't increase detail!).
- Saturating the blacks and whites (eg. with levels) might yield a better looking result. Or if one does not wish
to see saturated blacks and whites in the final results, restricting the output levels would achieve that.
Or a combination: saturating the whites, but not the blacks.
- With version 1 (and "Blur" = 1), one way to obtain smoother results is to first process for a
"Line width" twice the desired final result, then scale down. Creates huge intermediate image tho.
Not relevant
with "Pre-processing -> Downscale both or vertical".
2. The process implemented by the plugin
This (optional) section provides an overview of the steps of the process implemented by the plugin.
- First, if "Pre-processing -> Pre-scaled" is not selected, the original image is scaled down by the "Line Width" factor. For example, this might give:
- Inverse gamma is applied now (except if "Version 2" not selected). See discussion on gamma above.
- Next, the image is scaled up horizontally by the "Line width" factor, using the selected interpolation.
This step is skipped if "Pre-processing -> Downscale vertical" is selected.
- Then the image is scaled up vertically by the "Line width" factor, using interpolation NONE.
The idea is that each row of the original (pre-scaled) image will be expanded
to a group of "Line width" rows of all the same pixel value in a given column.
The result of these two steps (for the eye again) looks like this
(with "Pre-processing -> Downscale vertical", it would be similar, but with higher frequencies in the horizontal axis):
- The script then prepares a mask, of the same size as the final image.
Horizontally, the same value is repeated on each pixel of a whole row.
Vertically, for each group of "Line width" rows (corresponding to the line of varying width in final result),
the pixel values are distributed from black to white,
in regular steps when "Version 2" is selected,
in gamma-corrected steps otherwise (version 1).
The positions of the different pixels depend on "Line type", eg.
distributed on alternating sides of a center row within the group, or from one edge of the group.
This pixel values will be combined with the scaled image from previous step,
using layer mode 'subtract' (in simple terms, if an image pixel is darker than the mask pixel, the output will be black).
To prepare the mask, we first prepare each pixel in a 1 x "Line width" vertical rectangle,
copy the rectangle to the clipboard, then propagate this with bucket fill of the pattern.
Whitest row of mask is blackmost in final result
(only the whitest pixel values of the original image won't end up being black on this row).
Here are two examples of the layer mask:
 Center black (default)
|
|  Black on bottom
|
- The following steps will not be done if "Early exit" is selected.
- The mask layer is merged down (in subtract mode) into the image, giving:
All pixels that subtracted to less than zero (ie. were darker in the image than in the mask) are now full black.
Pixels above ' 255 / "Line Width" ' pixel value will be made full white in the next step.
- Tools -> Color Tools -> Levels is done, with black being 0, and white equal to
255 divided by "Line Width" (255/15 == 17 in the examples here);
this provides some gradation between black and white.
In version 1, white was set to 1, giving only blacks and whites as output.
The result of this step in "Version 2" looks like this:
- Finally, the horizontal blur is done, if "Blur radius" is greater than 1, giving:
Brief note on development.
I did a first plugin using
gimp-drawable-set-pixel,
but the result was horribly slow (hours for even
a relatively small image). Such a direct approach would have to be done in C, but I
didn't feel like getting into that: writing C is what I do all day, and
my son kept telling me how great Lisp was (I had used Lisp 40 years ago,
in university, now was my chance to get back to it).
Then I read Akkana Peck's excellent book,
Beginning GIMP, From Novice to Professional,
esp. the section on layer modes, and this was the start of this version.
Enjoy!
Feedback, comments, &c., most welcome.
P.S. to get the result in the plugin registry, the steps are approximately:
- Tools -> Color tools -> Levels: 38 - 210
- Scale down to 20x20 (yes, that's very small)
- Filters -> Artistic -> Engrave: pre-scaled, line width at 15, gamma at ~ 1.5, center white
Maintainer: Pierre Lewis
Last updated: Mar 26 2009
Feedback:
leware
at
globetrotter
dot
net