rotate() creates huge working canvases

Post any defects you find in the released or beta versions of the ImageMagick software here. Include the ImageMagick version, OS, and any command-line required to reproduce the problem. Got a patch for a bug? Post it here.
Post Reply
Bob-O-Rama
Posts: 31
Joined: 2007-11-23T15:34:51-07:00

rotate() creates huge working canvases

Post by Bob-O-Rama »

Hi,

I'm using IM 6.7.1 Q16 MT dll distribution, but the issue was observed in 6.5.2, both on MSVS 2005 and 2010. I have a 10MP input image and on calling rotate() it would occasionally start swapping out to disk and rotate() never completed. This is with your pre-built binary distribution. So on building the IM libraries ( 32-bit platform, DLL, MT, Q16 ) from source, I had the same issue. Then built the debug version of everything and traced execution through to see what was happening. I found that when rotate() expands the size of the image to accommodate rotation, it creates an enormous border region, even for very small rotation angles. For example a 1500 pixel border would be added when perhaps only 50 pixels was needed. Here is what I saw walking through the code:

Input image is 3840x2880 PNM 16-bit / channel. Walking through the IM code, we get to shear.c

Code: Select all

/*
    Compute image size.
  */
  width=image->columns;
  height=image->rows;
  if ((rotations == 1) || (rotations == 3))
    {
      width=image->rows;
      height=image->columns;
    }

At this point:

width =3840
height=2880

matching the actual image dimensions in the source PNM file. So far so good.

Code: Select all

  y_width=width+(ssize_t) floor(fabs(shear.x)*height+0.5);
  x_offset=(ssize_t) ceil((double) width+((fabs(shear.y)*height)-width)/2.0-
    0.5);
  y_offset=(ssize_t) ceil((double) height+((fabs(shear.y)*y_width)-height)/2.0-
    0.5);
OK, so

y_width = 3941

so this means the shear.x factor is being computed properly. It also makes sense. As the trig works out for the -4.00 degree rotation we are doing. So far so good. However, look at the offset values which directly dictate the border placed around the original...

x_offset = 2020
y_offset = 1577

Code: Select all

  /*
    Surround image with a border.
  */
  integral_image->border_color=integral_image->background_color;
  integral_image->compose=CopyCompositeOp;
  border_info.width=(size_t) x_offset;
  border_info.height=(size_t) y_offset;
So we add a 2020 x 1577 border to the image? We only rotated it by 4 degrees, the needed border to fit the new image might be perhaps 100 pixels.

Code: Select all

  rotate_image=BorderImage(integral_image,&border_info,exception);
On return,

rotate_image.columns = 7880 ( i.e. 2020 + 3840 + 2020 )
rotate_image.rows = 6034 ( i.e. 1577 + 2880 + 1577 )

So we end up using a 48MP intermediate image to rotate() a 10MP image. This is why I was having issues with rotate, it would ask for a 300MB allocation on top of the couple copies of the image already in RAM ( each 10MP x 6 bytes ), and if Windows could not oblige, it would swap. BorderImage() does complete. However, when the actual rotation is performed in the next few lines, since its rotating a 48MP image held in disk swap, it never completes in our lifetime. You see Windows flail between periods of intense disk I/O and intense CPU, and rotate() never returns.

So is this WAD? Or can the intermediate canvas be trimmed down a bit? If it can, it would reduce the memory footprint of rotate() by 80%

-- Bob
Bob-O-Rama
Posts: 31
Joined: 2007-11-23T15:34:51-07:00

Re: rotate() creates huge working canvases

Post by Bob-O-Rama »

OK, so on reading the recent rotate() bug report, it just seems as if the rotation via shears is just a memory pig, and has the disadvantage of poor interpolation. Apparently this is working as designed. There does appear to have been some work on this method which manages memory a bit better. Anyway, I can't get blood from a stone, so I need an alternative to rotate()...

Can you give me an example of how I can replace image.rotate( x ) with image.distort( ...something... ) I've been looking over the docs and tried a few ( apparently wrong ) ways to do it. I assume its something like

image.distort( MagickCore::ScaleRotateTransformDistory, then a miracle occurs... )

so any help would be appreciated.

-- Bob
Post Reply