Enhanced photo image copy command

Duoas 2008-11-06

I've been looking through tkImgPhoto.c lately, and the more I look at it the greater my horror. I was just beginning to consider how to refactor it to use proper sampling filters (at minimum bilinear, bicubic, and lanczos) [in addition to some significant organizational improvements] when I came across this page. I would love to see this in the core, in addition to a revised version of TIP#166 (to modify the copy/data/get/put photo option commands to be able to handle individual color channels -- rgb, argb, rgba, a, r, g, or b). What needs to be done to make this happen? Write a TIP and reference implementation? Is someone actually working on this still?

I am balking now because it already seems that the photo command has too many fingers in it. What it really needs is a good near-rewrite.


This is the outcome of an old project of mine, in fact I have been sitting on it just over three years now. Although I do not consider it completed I have only finished working on it when it proved itself to be quite usable.

If you are not the impatient type, you might consider reading the brief history I appended as a new section to my personal page. (NJG)

In short, you will find here a description of the functionality I have added to the <photo image> copy ... command (don't skip it, this is the only place I have it in writing); and a pointer to the package (not in the Tcl sense) I have to offer.

The package contains a patch file against tkImgPhoto.c of the tk8.4.6 release and the merged version of the file as well. For Windows users also included is xphoto.dll that after executing the load {< ... directory path ... >\xphoto.dll} command provides the same functionality.

For the enhanced functionality new options have been introduced. If only the old options appear in the <photo image> copy ... command the same code is executed as in the original version. However, if after the call of the expanded ParseSubcommandOptions any of the new options found to be present then the completely new Tk_PhotoPutResizedRotatedBlock is called to do the processing and old options get silently ignored.

Now the options

  • -mirror ? x | y ?

As a preprocessing step mirroring can be applied to either or both of the coordinate axes. No value given means both axes.

  • -rotate degree
  • -scale x-fact ?y-fact?

All parameters are floating values, if y-fact is missing it is taken equal to x-fact. Internally these two options are used in a single computing step.

The principle of the algorithm is simple. The origin of the coordinate system is moved (from the upper left corner) to the center of the source image and the bounding box of the image is scaled and rotated by the amount of the respective option values. We then iterate over the pixels lying within or on the boundary of the resulting box. At each step the pixel position is rotated/scaled/translated back to its originating position within the source image. Finally the pixel's color is computed as the bi-linear weighted average of the color values of the four pixels that surround the resulting position.

Important! There is a bug in the control logic. Do not specify simultaneous shrinking in one direction and expansion in the other.

  • -filter ?Mitchell | Lanczos | BlackmanSinc?
  • -blur blur-fact

These two are also used in a single processing step.

The simple bi-linear averaging above is principally incorrect. When you downscale an image and display it on the same pixel grid, a pixel in the downscaled image represents a proportionally larger part of the image than in the original one. Averaging then should be carried out over the pixels that fall into the corresponding area in the original image. If you dig further you will find that it is a two dimensional filtering problem and averaging should be done according to the characteristics of a suitably chosen low-pass filter.

Three such filters are included and can be selected by their name. I found that (to me at least) Mitchell presents the most pleasing result; thus the -filter option alone assumes Mitchell filtering.

If we average over a larger area than required blurring occurs. The level of which can be controlled by the value (which multiplies the linear dimension of the averaging area) given to the -blur option. Blurring implies filtering thus if the latter is not given simultaneously Mitchell is assumed. Internally the blurring level is not permitted to go below 1.

When filtering is specified first filtering and scaling is done in a single step, and rotation (if requested) is applied to the result.

Important! There is a bug in the control logic. Filtering is not done when image expansion (scale > 1) is specified.

  • -smoothedge 1 | 2
  • -background ?color name or RGB value?

These can be applied for eliminating the ragged edges (~anti-aliasing) that occur as a result of rotation. For the time being it works only against a homogeneous background the color of which can be given by the -background option. If value is not given the background assumes the "SystemButtonFace" color. -smoothedge defines the degree of smoothing. What happens is that before scaling/filtering we add (programmatically, not physically) a border of background color to the source image the pixel width of which is the value given in the -smoothedge option multiplied by the reciprocal of the scale factor. If no value is given 2 is assumed. The default is no smoothing.

A few concluding remarks are in order here.

  • I did not care much about sorting out special cases and writing separate code for them. So the code would need some »beautifying« before it can be considered professional quality.
  • Image sharpening is not implemented. I have not tried to figure out whether -blur with a fractional parameter does this.
  • When a rectangular object with horizontal and vertical edges is rotated the result will contain a great amount of transparent pixels. Further rotations will further increase their relative size. So a filtering step (which is not present yet) would be needed to stop this avalanche effect.
  • Edge smoothing against arbitrary background is not implemented. When a photo image is copied into another solving it is an internal affair. I was postponing the implementation at the time because I had started to look for a way to do it against the canvas itself.

The package can be downloaded from here [L1 ] MHo: Deadlink as of 2012/01/16.

Package archived at: [L2 ]

Here are some examples

http://cpluscsystems.axelero.net/cgi-bin/download.php?category=programming&collection=tcltk&item=test_1Z.png

http://cpluscsystems.axelero.net/cgi-bin/download.php?category=programming&collection=tcltk&item=test_1Y.png

http://cpluscsystems.axelero.net/cgi-bin/download.php?category=programming&collection=tcltk&item=test_1X.png


DKF: Wow! Very cool stuff indeed!

It turns out that the correct thing to do when working with image rotation, etc, is to remember that you are also dealing with an alpha channel, and that all pixels outside the image should be considered to be black and fully transparent. Using that information, you can drop the -smoothedge and -background options.

NB: At the time of writing (8-Jul-2004) the photo display compositing seems to be not quite right. Please assume that this is going to get fixed. :^)

RT: Many thanks! I've put the code into use already (windows DLL). I do use TclMagick successfully, but sometimes a quick and easy scaling is needed and this enhancement fits the bill nicely. I hope it makes it into the core in some form for 8.5. Regards, Roy Terry 8July2004

US: Coooool! I found two minor bugs:

 Line 102:
  Change "SystemButtonFace" to NORMAL_BG

You also need to include default.h (I'm sure Unix users will love this change)

 Line 185:
  Change scaleX to scaleY

Unfortunately I don't have a chance to compile it for windows, but nevertheless need to have it for this platform. Would you mind to mail me a new xphoto.dll ?


Is there any chance in hell of getting this code into the core? It's already done, after all.

Of course, you just have to file a TIP and follow the process involved. With a bit of persistence, good ideas quickly get accepted into the core.

2006-03-26 ARR: Great job, I like this tool very much! I used the windows dll and I think I found a heavy bug: if "image create photo" is used in a namespace or even if there is :: in the image name my app crashes. A workaround is to rename the image command and overwrite with a small proc to eval at global level and avoid image names like ::my_image_name. Could anyone fix the bug in the C code please?

catch { rename image image_org }

proc image { args } {
    catch { uplevel #0 image_org $args } err
}

NJG 2006-04-01:

I have replaced the Windows dll in the package (download reference just before the example images) with a new one that interprets namespace qualifiers as the unmodified image code would do. However, note the following anomaly I had to mirror in my code (which hijacks only the <image> copy ... commands and let control pass through in all other cases):

namespace export image
namespace eval zri {
    namespace import ::image
    image create photo blahblah::PHOTO1        ;# creates ::zri::blahblah::PHOTO1 expected
    image create photo PHOTO2                ;# creates ::PHOTO2 rather than ::zri::PHOTO2!
}
image create photo ::zri::PHOTO3        ;# creates ::zri::PHOTO3 expected
namespace eval zri {
    PHOTO1 copy ::zri::PHOTO3                 ;# it works only in this form
}

That is, if there are no namespace qualifiers in the name the image is created in the global namespace not in the current one!

Lars H: As far as image creation is concerned, this is an obscure "feature" of Tcl_CreateObjCommand, and affects also many other commands that create new commands. The most annoying aspect of it is probably that one cannot use them to create a specific command (in this case, an image) in the current namespace unless one gives a fully qualified name for it. Fortunately, [namespace current]::somename isn't too hard to write.


ARR 2006-04-07: Thanks a lot for your very quick help. All works great now in my application. I hope this will go into the core.

Note: 'hoping' this will go in the core won't get it there. It just takes someone (doesn't even need to be the author of the package) to write up a TIP and submit it, and then persevere a bit with discussion, questions, etc.


PWE 2006-05-09: When doing an asymmetrical downscaling with

 $nimg copy $img -filter Mitchell -scale 1.0 0.1

the height remains the same, whilst the width is shrunk to one tenth. From the documentation above, I would have expected the width remain the same, and the height to have shrunk.

US: Scroll up a few lines to see why!

RT: I scrolled and looked at source and it seems that if 2 scaling factors are given they are reversed. So if you supply the values in reverse order it works as expected. (Of course when you get a corrected version your code will be wrong :*( Roy Terry, 10May2006 - looking at NJG's original code.


MiR 2012-04-04 13:33:52:

<I've been searching for a simple scaling possibility with floating point factors. Has this code ever been opted for integration into TK Core? Should I put it onto the TK 9.0 Wishlist? Or can anyone correct me if I'm wrong and this code is whether incorporated already or disposable due to common alternatives. I try to code a crossplattform script with seamless scaling and I know there are lots of extensions or evem Imagemagick but its hard to keep things compatible with this kind of solution...>