Photo: somes issues and thoughts
Greetings I am developing a site where people editing content will not directly access the Zope interface but will use several simplified forms adequate for their work. Since they will occasionally need to add some images, I am using the Photo product as the basis for the image management tool they will be using (images will be Photo objects in a hierarchy of Photo Folders, and most people will not be writing DTML but referring to original images or derived displays explicitly writing their URLs in plain HTML). While preparing this, I noticed a few minor issues. On-the-fly resizing could be interesting: Ron Bickers seems to be considering adding support for on-the-fly image resizing, that is, arbitrary image resizing with no need for predefining named display sizes. This could be convenient for situations like my current one. There will be a few cases where the "photo album" metaphor will be used (like in the Photo Folder sample views), but the majority of the other pictures will be added one by one to be used in separate contexts, each in one or two sizes without any foreseeable "size standards". For many pictures, display sizes will have to be defined individually, therefore with less interest in having to "invent" or reuse a name for each new size or even to have to visit an administration interface just for resizing; maybe the nicest solution from the point of view of a content editor (when not working with the "tag" method but only writing pure HTML) would be simply to write <img src="http:/somesite/img/.../picture.jpg?width=300" ... /> when one needs to show picture.jpg in a new size with width of 300 pixels or .../picture.jpg?width=480&height=480 to limite the larger size to 480 pixels. This would end up producing "unnamed" cached displays for each picture. Of course HTML authors would still have to take care of checking the final dimensions in order to write 'height' and 'width' attributes... or those could be supplied by a size testing interface on the site (possibly listing already cached sizes). It could also be nice to allow rescaling (relatively to the original size) with something like .../picture.jpg?scale=1.56 (or a "scale" cookie perhaps of use for automagically resizing all images (big and small) by the same factor on a set of pages via a "scale" cookie). One could then wish for several other image transformations like those available with \includegraphics in LaTeX package graphicx: cropping with "bounding boxes", rotation, ... :-) About the cookie name: It could also be useful to allow customization of the cookie name (now hardcoded as 'display') to solve possible cases of "collision" with other cookies or even to allow separate display customization for different image sets in the same site. Likewise for any other cookies which may someday be used by the Photo product. The `tag' method: The `tag' method sets the display cookie only when the supplied display name corresponds to an existing one for the current image. An interesting exception would be to set the display cookie ALSO when display=='', thus activating "natural size" viewing (unless one has defined a display size with id=='', which is currently possible...). Ideally, the `tag' method should respect alt=='' (since sometimes one really wants that): if alt is None: alt = getattr(self, 'title', None) if alt is None: alt = self.getId() Image size calculation: In my "view" method (replacing the sample "view") I was going to show the dimensions the current pic would have at each defined display size, near to the link to the corresponding page. It would be interesting to have public methods providing that information (height() and width() only provide it for the original image; displayMap() only provides dimensions for displays which have already been generated, and I chose not to pregenerate them all). I simply reproduced the calculations of _getAspectRatioSize, but then noticed that some results were wrong. For instance, with an original image of 272×281 pixels, the result of rezising to a maximum of 480×480 is not an image with 464×480 as expected, but 464×479. This happens because I opted for using ImageMagick and: - _getAspectRatioSize, using integer division, rounds down 464.626334... (the "ideal" new width) to 464; - _resize calls ImageMagick's `convert' with option -geometry 464x480; - called like this, `convert' also tries to mantain the aspect ratio of the input image and uses the -geometry parameters as limits: constrained to 464×480, the best solution it finds to maintain the aspect ratio is creating an image of 464×479. It would be better in this case to call `convert' with option -geometry 480x480 and let it calculate the ideal new size. When I do that manually, I get a 465×480 image. Unfortunately, I had no time to determine the precise equation used by ImageMagick to calculate new sizes maintaining aspect ratio. Having it, you could use that instead of the calculation at _getAspectRatioSize when resizing with PIL (since PIL's `resize', unlike ImageMagick's `convert', doesn't try to maintain aspect ratio and needs to be given precise final sizes). Image quality with PIL: I noticed that in the version of PIL I have here, 1.1.2, the resize method has an optional filter argument which can be "NEAREST, BILINEAR, or BICUBIC. If omitted, it defaults to NEAREST." I have no time for careful testing now (and Image Magick is working very well), but noticed that `resize' is being without specifying the filter, thus using NEAREST and not taking full advantage of PIL capabilities. Best regards J Esteves
-----Original Message----- From: zope-admin@zope.org [mailto:zope-admin@zope.org]On Behalf Of J M Cerqueira Esteves
their URLs in plain HTML). While preparing this, I noticed a few minor issues.
Wow! Lots of suggestions. I'll make note of them and consider them for fixes, updates, and features for future releases. At first glance, much of it looks like good ideas. Thanks for your input! _______________________ Ron Bickers Logic Etc, Inc.
* J M Cerqueira Esteves <jmce@artenumerica.com> [2002-01-03 19:40 +0000]:
Unfortunately, I had no time to determine the precise equation used by ImageMagick to calculate new sizes maintaining aspect ratio. Having it, you could use that instead of the calculation at _getAspectRatioSize when resizing with PIL (since PIL's `resize', unlike ImageMagick's `convert', doesn't try to maintain aspect ratio and needs to be given precise final sizes).
I have just found what ImageMagick uses, in magick/image.c, function ParseImageGeometry. If (w0, h0) are the original width and height and (wM, hM) are the maximum width and height allowed (specified with -geometry wMxhM), ImageMagick defines the final width and height as follows (in Python): scale = min (float (wM) / w0, float (hM) / h0) width = max (int (scale * w0 + 0.5), 1) height = max (int (scale * h0 + 0.5), 1) while Photo uses (with integer division, therefore with truncation everywhere): if hM > h0 * wM / w0: height = h0 * wM / w0 width = wM else: height = hM width = w0 * hM / w0
From: J M Cerqueira Esteves <jmce@artenumerica.com>
while Photo uses (with integer division, therefore with truncation everywhere):
if hM > h0 * wM / w0: height = h0 * wM / w0 width = wM else: height = hM width = w0 * hM / w0
Look at the order of operations - the truncation doesn't hurt anything.
* marc lindahl <marc@bowery.com> [2002-01-04 01:54 +0000]:
From: J M Cerqueira Esteves <jmce@artenumerica.com>
while Photo uses (with integer division, therefore with truncation everywhere):
if hM > h0 * wM / w0: height = h0 * wM / w0 width = wM else: height = hM width = w0 * hM / h0 [I had a mistake here, /w0 instead of /h0]
Look at the order of operations - the truncation doesn't hurt anything.
I believe you are mentioning that h0*wM/w0 == (h0*wM)/w0 == int (float(h0*wM) / w0) instead of the (much worse) h0 * (wM/w0)... I was aware of that. The main difference is that Photo defines new dimensions with integer truncation, while Image Magick rounds each to the nearest integer (when results of both calculations are different, the aspect ratio is closer to the original with Image Magick).
From: J M Cerqueira Esteves <jmce@artenumerica.com>
The main difference is that Photo defines new dimensions with integer truncation, while Image Magick rounds each to the nearest integer (when results of both calculations are different, the aspect ratio is closer to the original with Image Magick).
But then you can go 1 pixel outside your 'bounding box' - is that more desirable?
* marc lindahl <marc@bowery.com> [2002-01-04 03:17 +0000]:
From: J M Cerqueira Esteves <jmce@artenumerica.com>
The main difference is that Photo defines new dimensions with integer truncation, while Image Magick rounds each to the nearest integer (when results of both calculations are different, the aspect ratio is closer to the original with Image Magick).
But then you can go 1 pixel outside your 'bounding box' - is that more desirable?
It never goes outside the hM×wM 'bounding box'... unless you request hM == 0 or wM == 0. The algorithm is scale = min (float (wM) / w0, float (hM) / h0) width = max (int (scale * w0 + 0.5), 1) height = max (int (scale * h0 + 0.5), 1) Suppose hM >= 1, wM >= 1, and float (wM) / w0 <= float (hM) / h0. Then: scale == float (wM) / w0 width == max (int (float (wM) / w0 * w0 +0.5), 1) Unless the result of float (wM) / w0 * w0 falls outside of the interval ] wM-0.5, wM+0.5 [ (can you think of a case where arithmetic would cause such an error?) we then have width == max (wM, 1) == wM and height == max (int (float(wM) / w0 * h0 + 0.5), 1). Since we are assuming float (wM) / w0 <= float (hM) / h0, then height <= max (int (float(hM) / h0 * h0 + 0.5), 1) and unless arithmetic errors make float (hM) / h0 * h0 >= hM+0.5 we can conclude that height <= max (hM, 1), i.e., in our case, height <= hM. In the case float (wM) / w0 >= float (hM) / h0 one can show in the same way that width <= wM, height == hM . If one is still worried about the possibility of errors above 0.5 when calculating float (wM) / w0 * w0 or float (hM) / h0 * h0, one can simply change the algorithm above into sw = float (wM) / w0 sh = float (hM) / h0 if sw <= sh: width = wM height = int (sw * h0 + 0.5) else: width = int (sh * w0 + 0.5) height = hM (now respecting zero values for wM and hM).
J M Cerqueira Esteves wrote:
* marc lindahl <marc@bowery.com> [2002-01-04 01:54 +0000]:
From: J M Cerqueira Esteves <jmce@artenumerica.com>
while Photo uses (with integer division, therefore with truncation everywhere):
if hM > h0 * wM / w0: height = h0 * wM / w0 width = wM else: height = hM width = w0 * hM / h0 [I had a mistake here, /w0 instead of /h0]
Look at the order of operations - the truncation doesn't hurt anything.
I believe you are mentioning that
h0*wM/w0 == (h0*wM)/w0 == int (float(h0*wM) / w0)
instead of the (much worse) h0 * (wM/w0)... I was aware of that.
The main difference is that Photo defines new dimensions with integer truncation, while Image Magick rounds each to the nearest integer (when results of both calculations are different, the aspect ratio is closer to the original with Image Magick).
Sometimes, truncation (floor) gives better aspect ratio than round. I would even not be surprised if, statistically, none of the methods were better. Two examples: 1- Initial size: 51x49 Scale: 1/10 Final size (method=round): 5x5 Final size (method=floor): 5x4 Best method: round 2- Initial size: 54x56 Scale: 1/10 Final size (method=round): 5x6 Final size (method=floor): 5x5 Best method: floor If you are *really* concerned about aspect ratio (I mean, if half a pixel does *really* matter) and have not enough time to prove mathematical theorems on integer arithmetics, I would suggest you to use brute force and ignorance: Given a scale factor, choose between the four combinations [(x, y) either scaled with method (ceil, floor)] which one gives better aspect ratio (paying attention to boundary conditions such as none of the dimentions being lesser than 1 (zero height/width is no good at all!) or bigger than some limit -- eventually these boundary conditions will make the size of your code increase quite a bit, but hey!, life is hard... :^) ). But if you decide that a difference of half a pixel in the aspect ratio is not such a big thing for your problem, maybe the better is to obey to the scale factor, so you should always use the round method (paying attention to boundary conditions as before). Sebrosa
* Jose' Sebrosa <sebrosa@artenumerica.com> [2002-01-04 13:29 +0000]:
Sometimes, truncation (floor) gives better aspect ratio than round. I would even not be surprised if, statistically, none of the methods were better.
Two examples:
1- Initial size: 51x49 Scale: 1/10 Final size (method=round): 5x5 Final size (method=floor): 5x4 Best method: round
2- Initial size: 54x56 Scale: 1/10 Final size (method=round): 5x6 Final size (method=floor): 5x5 Best method: floor
Those methods are not used by Photo and ImageMagick. With Photo and IM you start by choosing new maximum integer dimensions, not the scale, and one of the sides of the resulting rectangle gets one of those exact integer lengths. THEN you calculate the scale factor and apply it to the other side; it is here that IM rounds and Photo truncates. But it is interesting to use these examples and see what we get for a few very small maximum width (wM) and maximum height (hM) values: 1 - h0×w0 = 51×49 width × height wM hM Photo ImageMagick 6 6 6×5 6×6 6 5 5×5 5×5 5 6 5×4 5×5 5 5 5×4 5×5 1 1 1×0 1×1 2 - h0×w0 = 54×56 width × height wM hM Photo ImageMagick 6 6 5×6 6×6 6 5 4×5 5×5 5 6 5×5 5×5 5 5 4×5 5×5 1 1 0×1 1×1 You may test all of this by calling functions below with arguments (w0,h0,wM,hM): def scaleP (w0, h0, w, h): if h > h0 * w / w0: h = h0 * w / w0 else: w = w0 * h / h0 return (w, h) def scaleIM (w0, h0, w, h): scale = min (float (w) / w0, float (h) / h0) w = int (scale * w0 + 0.5) h = int (scale * h0 + 0.5) return (w, h)
* J M Cerqueira Esteves <jmce@artenumerica.com> [2002-01-04 15:07 +0000]:
* Jose' Sebrosa <sebrosa@artenumerica.com> [2002-01-04 13:29 +0000]:
Sometimes, truncation (floor) gives better aspect ratio than round. I would even not be surprised if, statistically, none of the methods were better.
Two examples:
1- Initial size: 51x49 Scale: 1/10 Final size (method=round): 5x5 Final size (method=floor): 5x4 Best method: round
2- Initial size: 54x56 Scale: 1/10 Final size (method=round): 5x6 Final size (method=floor): 5x5 Best method: floor
Those methods are not used by Photo and ImageMagick.
Correction: ImageMagick uses the "round" method as above in some cases, for instance when one uses convert --geometry 10%x10% original.png scaled.png or even convert --geometry 10% original.png scaled.png In this case it seems that both directions are treated separately and we find the problem above. In any case I have been talking of `convert' as it is run by Photo, with final max sizes instead of percent scales: convert --geometry 5x6 original.png scaled.png In this case `convert' tries to respect the aspect ratio, unless one follows the geometry argument with '!', in which case the image is distorted as needed. So, if some day Photo objects accepts a scale argument as in image.tag (..., scale=0.1, ..) or http://..../image?scale=0.1 it could be best NOT to call convert with the corresponding percentage argument but to precalculate new dimensions (W,H) in Photo.py and use convert -geometry WxH. Oh well, too many one-pixel thoughts for one day... :-)
J M Cerqueira Esteves wrote:
* Jose' Sebrosa <sebrosa@artenumerica.com> [2002-01-04 13:29 +0000]:
Sometimes, truncation (floor) gives better aspect ratio than round. I would even not be surprised if, statistically, none of the methods were better.
Two examples:
1- Initial size: 51x49 Scale: 1/10 Final size (method=round): 5x5 Final size (method=floor): 5x4 Best method: round
2- Initial size: 54x56 Scale: 1/10 Final size (method=round): 5x6 Final size (method=floor): 5x5 Best method: floor
Those methods are not used by Photo and ImageMagick.
Sorry then.
With Photo and IM you start by choosing new maximum integer dimensions, not the scale, and one of the sides of the resulting rectangle gets one of those exact integer lengths. THEN you calculate the scale factor and apply it to the other side; it is here that IM rounds and Photo truncates.
Well, I suppose that the new maximum integer dimensions are choosen from an initial scaling factor. So you say that the Photo and IM method are to 1) start with an initial scale, 2) choose new maximum integer dimensions (through round(initial_scale*dimension) in IM and floor() in Photo, I guess...), 3) choose a dimension to have the new value (which dimension? let's say it's always the width, or that it is always the smaller...), 4) *redefine* the scaling factor to fit the new dimension (which becomes priviledged), and 5) set the other dimension using the *redefined* scale (with round() in IM and floor() in Photo). I think that this is almost the same as my brute force suggestion, with the single difference that I do not priviledge any of the dimentions in my choices -- my criterium is just the final aspect ratio. To arrange the code the way IM/Photo does while keeping the symmetry in handling the two dimensions, I think it would be necessary to do the calculations twice: one choosing width in 3) above, and other choosing height. Then the final aspect ratios could be compared, and the better approximation could be choosed. Eventually, if the priviledged dimension choosed in 4 is the smaller (it is?), there is no need to perform the second calculation, but this isn't obvious to me. Anyway, for the interested reader, it should be a simple exercice on math to prove that this is, or isn't, true. On the other hand we can forget about math and do it brute force -- it's easier! :^) Sebrosa
Oops! Sorry! Confusion here! Of course if your input data is not a scaling factor, but two separate dimensions, all I wrote is garbage! I was always thinking in terms of a single scaling factor as input. Sebrosa
From: "Jose' Sebrosa" <sebrosa@artenumerica.com>
Sometimes, truncation (floor) gives better aspect ratio than round. I would even not be surprised if, statistically, none of the methods were better.
In fact, statistically, they differ only in the small 'bias' of the rounding factor of 0.5 pixel.
* J M Cerqueira Esteves <jmce@artenumerica.com> [2002-01-04 06:40 +0000]:
while Photo uses (with integer division, therefore with truncation everywhere):
if hM > h0 * wM / w0: height = h0 * wM / w0 width = wM else: height = hM width = w0 * hM / w0
... which by the way has idempotency problems. In my example, a 272×281 pic allowed to have wM=480 and hM=480, this gives 464×480 as dimensions respecting the original aspect ratio. But if one specifies (wM, hM)=(464,480) and starts again with 272×281, the final result is then (464, 479) instead of the expected (464,480)...
From: J M Cerqueira Esteves <jmce@artenumerica.com> Organization: Arte Numerica Date: Fri, 4 Jan 2002 07:21:39 +0000 To: zope@zope.org Cc: sebrosa@artenumerica.com Subject: Re: [Zope] Photo: somes issues and thoughts
* J M Cerqueira Esteves <jmce@artenumerica.com> [2002-01-04 06:40 +0000]:
while Photo uses (with integer division, therefore with truncation everywhere):
if hM > h0 * wM / w0: height = h0 * wM / w0 width = wM else: height = hM width = w0 * hM / w0
... which by the way has idempotency problems. In my example, a 272×281 pic allowed to have wM=480 and hM=480, this gives 464×480 as dimensions respecting the original aspect ratio. But if one specifies (wM, hM)=(464,480) and starts again with 272×281, the final result is then (464, 479) instead of the expected (464,480)...
The last line is wrong: width = h0 * hM / w0 But as for float vs fix, I think if you do the same calculation with float you'll still get the same result after you int.
* marc lindahl <marc@bowery.com> [2002-01-04 03:16 +0000]:
if hM > h0 * wM / w0: height = h0 * wM / w0 width = wM else: height = hM width = w0 * hM / w0
The last line is wrong:
width = h0 * hM / w0
Yes, it was wrong, but it should be width = w0 * hM / h0 ...
But as for float vs fix, I think if you do the same calculation with float you'll still get the same result after you int.
Not always.
From: J M Cerqueira Esteves <jmce@artenumerica.com>
Yes, it was wrong, but it should be width = w0 * hM / h0 ...
I knew I would get that upside down! I was staring at it...
But as for float vs fix, I think if you do the same calculation with float you'll still get the same result after you int.
Not always.
you're starting with int's, so the worst you can get is one roundoff from the divide. unless you add a 0.5 to round instead of floor, then it'll be the same.
A follow up to many notes and suggestions from J Esteves:
On-the-fly resizing could be interesting: ... <img src="http:/somesite/img/.../picture.jpg?width=300" ... /> ... when one needs to show picture.jpg in a new size with width of 300 pixels or .../picture.jpg?width=480&height=480 to limite the larger size to 480 pixels. ... It could also be nice to allow rescaling (relatively to the original size) with something like
.../picture.jpg?scale=1.56
I like these ideas. All of these are now on my todo list.
It could also be useful to allow customization of the cookie name
Added to my todo list.
An interesting exception would be to set the display cookie ALSO when display=='', thus activating "natural size" viewing
Added to my list.
Ideally, the `tag' method should respect alt=='' (since sometimes one really wants that):
True. Also, added to my list.
It would be interesting to have public methods providing that information (height() and width()
On my todo list.
Unfortunately, I had no time to determine the precise equation used by ImageMagick to calculate new sizes maintaining aspect ratio.
Based on the lengthy discussion of this issue, and since I didn't get any negative responses, I'll incorporate the algorithm you provided. Thanks!
Image quality with PIL:
I noticed that in the version of PIL I have here, 1.1.2, the resize method has an optional filter argument which can be "NEAREST, BILINEAR, or BICUBIC. If omitted, it defaults to NEAREST." I have no time for careful testing now (and Image Magick is working very well), but noticed that `resize' is being without specifying the filter, thus using NEAREST and not taking full advantage of PIL capabilities.
In a nutshell, resized image quality with PIL stinks. I and many others have done countless tests with all three settings of PIL. Not one of my tests produced a noticeable difference in any photo, so I didn't bother adding anything fancy in Photo to try to make the images better. I've tried dozens of things suggested on the PIL forums, and nothing produced images better than the plain ol' resize. Clearly, IM is much better than PIL at this task, so I recommend it be used when available. If you haven't already, take a look at http://www.bickersfamily.org/Photos/PILvsIM/ Thanks again for your input! _______________________ Ron Bickers Logic Etc, Inc.
participants (5)
-
J M Cerqueira Esteves -
Jose' Sebrosa -
marc lindahl -
Ron Bickers -
Ron Bickers