This is already on the main list, I moved it there after Peter mentioned it. 
Sorry, I have a bad habit of starting conversations on lift-committers I'm 
trying to break before David breaks it for me ;-)

GIF resizing was a huge sticking point for the project here at Paytronix that 
this code was originally written for. I'd prefer it if all resize transforms 
could resize into the same format as the source (right now it's GIF -> PNG, and 
most everything else is source type -> dest type), but:
  - Shrunk GIFs are ugly due to the limited color palette and single-bit 
transparency.
  - Calculating an ideal palette and doing any dithering is not trivial, and 
for the application I had it was fine to change the format.

-Ross

On Jan 15, 2010, at 3:47 PM, Jonathan Hoffman wrote:

> Hi Ross,
> 
> That looks good.  It seems you're doing a much better job of handling
> gif than I would have.  Maybe we should move this discussion to the
> main list to get everyone's input on what features would be useful.
> 
> Tim, If you've already created a branch, let me know and I'll get my
> stuff in there too-- My resize code uses the sanselan project to read
> exif orientation information.
> 
> On Fri, Jan 15, 2010 at 10:33 AM, Ross Mellgren <[email protected]> wrote:
>> Well, it has to keep the whole source (packed) in memory and the target 
>> (unpacked / 32 bit RGBA) in memory, so I would assume that as long as 
>> AffineTransformOp is not doing something untoward it's probably around (src 
>> width * src height * src bytes/pixel) + (dest width * dest height * 4 bytes) 
>> plus a little bit extra.
>> 
>> I'm not a Java2D guru or anything, so I'm not sure how that could be 
>> improved offhand.
>> 
>> If you want to wrap it up and put it in, that'd be awesome!
>> 
>> -Ross
>> 
>> On Jan 15, 2010, at 10:17 AM, Timothy Perrett wrote:
>> 
>>> Cool stuff Ross, whats the overhead like in terms of memory etc?
>>> 
>>> I might have a bit of time to put this into a module and stuff it on review 
>>> board.
>>> 
>>> Cheers, Tim
>>> 
>>> On 15 Jan 2010, at 15:03, Ross Mellgren wrote:
>>> 
>>>> According to Jon on the call, he said he was putting together just such a 
>>>> lift module (for image stuff), so I figured I'd toss this over in case it 
>>>> was of use to him in that module.
>>>> 
>>>> If anyone else wants to use it independently, consider it a contribution 
>>>> to lift, and licensed the same way.
>>>> 
>>>> -Ross
>>>> 
>>>> On Jan 15, 2010, at 9:51 AM, Peter Robinett wrote:
>>>> 
>>>>> Ross, this looks nice. Imagine resizing code is something that I've
>>>>> personally had to do many times and is always annoying, so perhaps
>>>>> this would make a good Lift module? Anyway, this is probably best
>>>>> discussed on the main list...
>>>>> 
>>>>> Peter
>>>>> 
>>>>> On Jan 14, 3:01 am, Ross Mellgren <[email protected]> wrote:
>>>>>> Oh I nearly forgot I said on the conference call the other day that I'd 
>>>>>> send the image resize code we built at work, in case it would be 
>>>>>> helpful. Here it is:
>>>>>> 
>>>>>> import java.awt.{Graphics, RenderingHints, Transparency}
>>>>>> import java.awt.geom.AffineTransform
>>>>>> import java.awt.image.{AffineTransformOp, BufferedImage, ColorModel, 
>>>>>> IndexColorModel}
>>>>>> 
>>>>>> /**
>>>>>> * Helpers for manipulating images
>>>>>> */
>>>>>> object ImageHelpers
>>>>>> {
>>>>>> // Some code here omitted -- Ed.
>>>>>> 
>>>>>>   /** Rendering hints set up for the highest quality rendering */
>>>>>>   val highQualityHints = {
>>>>>>       val h = new RenderingHints(null)
>>>>>>       h.put(RenderingHints.KEY_ALPHA_INTERPOLATION, 
>>>>>> RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)
>>>>>>       h.put(RenderingHints.KEY_COLOR_RENDERING, 
>>>>>> RenderingHints.VALUE_COLOR_RENDER_QUALITY)
>>>>>>       h.put(RenderingHints.KEY_INTERPOLATION, 
>>>>>> RenderingHints.VALUE_INTERPOLATION_BICUBIC)
>>>>>>       h.put(RenderingHints.KEY_ANTIALIASING, 
>>>>>> RenderingHints.VALUE_ANTIALIAS_ON)
>>>>>>       h.put(RenderingHints.KEY_RENDERING, 
>>>>>> RenderingHints.VALUE_RENDER_QUALITY)
>>>>>>       h
>>>>>>   }
>>>>>> 
>>>>>> // Some code here omitted -- Ed.
>>>>>> 
>>>>>>   /**
>>>>>>    * Resize an image of the given source type by the given ratios, 
>>>>>> properly handling GIF transparency, giving back the resized
>>>>>>    * image and the new image format type that should be used.
>>>>>>    *
>>>>>>    * The image type might change if the input type is an indexed color 
>>>>>> model, because it is a hard problem to choose an optimized
>>>>>>    * palette, and currently we don't. This function will return "png" as 
>>>>>> the new type in this case.
>>>>>>    *
>>>>>>    * If the input image is not using an indexed color model with 
>>>>>> transparency, then the target format and color model will be
>>>>>>    * identical to the source.
>>>>>>    */
>>>>>>   def resize(source: BufferedImage, inputFormat: String, dx: Double, dy: 
>>>>>> Double): (BufferedImage, String) = {
>>>>>>       var sourceColorModel = source.getColorModel
>>>>>>       val targetColorModel = source.getColorModel
>>>>>>       val standardColorModel = ColorModel.getRGBdefault
>>>>>> 
>>>>>>       val (targetWidth, targetHeight) = (((source.getWidth: Double) * 
>>>>>> dx).asInstanceOf[Int], ((source.getHeight: Double) * 
>>>>>> dy).asInstanceOf[Int])
>>>>>> 
>>>>>>       def resize(src: BufferedImage, dst: BufferedImage) {
>>>>>>           val g = dst.createGraphics
>>>>>>           try {
>>>>>>               g.setRenderingHints(highQualityHints)
>>>>>>               g.drawImage(src, new 
>>>>>> AffineTransformOp(AffineTransform.getScaleInstance(dx, dy), 
>>>>>> AffineTransformOp.TYPE_BICUBIC), 0, 0)
>>>>>>           } finally {
>>>>>>               g.dispose
>>>>>>           }
>>>>>>       }
>>>>>> 
>>>>>>       // GIF support in Java is very ornery. For GIFs we have to 
>>>>>> manually do the masking on input, and then just punt on outputting GIFs 
>>>>>> and instead output PNGs.
>>>>>>       if (sourceColorModel.isInstanceOf[IndexColorModel] &&
>>>>>>           sourceColorModel.hasAlpha &&
>>>>>>           sourceColorModel.getTransparency == Transparency.BITMASK &&
>>>>>>           
>>>>>> sourceColorModel.asInstanceOf[IndexColorModel].getTransparentPixel >= 0) 
>>>>>> {
>>>>>> 
>>>>>>           val indexColorModel = 
>>>>>> sourceColorModel.asInstanceOf[IndexColorModel]
>>>>>>           val transparent = 
>>>>>> indexColorModel.getRGB(indexColorModel.getTransparentPixel)
>>>>>> 
>>>>>>           val masked = new BufferedImage(standardColorModel, 
>>>>>> standardColorModel.createCompatibleWritableRaster(source.getWidth, 
>>>>>> source.getHeight), standardColorModel.isAlphaPremultiplied, null)
>>>>>>           var w = masked.getWidth
>>>>>>           var h = masked.getHeight
>>>>>> 
>>>>>>           val buf  = new Array[Int](w)
>>>>>> 
>>>>>>           var y = 0
>>>>>>           while (y < h) {
>>>>>>               source.getRGB(0, y, w, 1, buf,  0, 1)
>>>>>> 
>>>>>>               var x = 0
>>>>>>               while (x < w) {
>>>>>>                   val c = buf(x)
>>>>>>                   if (c == transparent) {
>>>>>>                       buf(x) = 0
>>>>>>                   }
>>>>>>                   x += 1
>>>>>>               }
>>>>>> 
>>>>>>               masked.setRGB(0, y, w, 1, buf, 0, 1)
>>>>>>               y += 1
>>>>>>           }
>>>>>> 
>>>>>>           val resized = new BufferedImage(standardColorModel, 
>>>>>> standardColorModel.createCompatibleWritableRaster(targetWidth, 
>>>>>> targetHeight), standardColorModel.isAlphaPremultiplied, null)
>>>>>>           resize(masked, resized)
>>>>>>           (resized, "png")
>>>>>>       } else if (sourceColorModel.isInstanceOf[IndexColorModel]) {
>>>>>>           // The input color model is indexed, and we know we won't be 
>>>>>> able to generate a tolerable palette to make another indexed color 
>>>>>> model, so use sRGB and upgrade to PNG.
>>>>>>           val resized = new BufferedImage(standardColorModel, 
>>>>>> standardColorModel.createCompatibleWritableRaster(targetWidth, 
>>>>>> targetHeight), standardColorModel.isAlphaPremultiplied, null)
>>>>>>           resize(source, resized)
>>>>>>           (resized, "png")
>>>>>>       } else {
>>>>>>           val resized = new BufferedImage(targetColorModel, 
>>>>>> targetColorModel.createCompatibleWritableRaster(targetWidth, 
>>>>>> targetHeight), targetColorModel.isAlphaPremultiplied, null)
>>>>>>           resize(source, resized)
>>>>>>           (resized, inputFormat)
>>>>>>       }
>>>>>>   }
>>>>>> 
>>>>>> }
>>>>>> 
>>>>>> It isn't perhaps the ideal implementation, as mentioned in some of the 
>>>>>> comments, but perhaps Jon or others might find it useful in whole or 
>>>>>> part.
>>>>>> 
>>>>>> -Ross
>>>>> --
>>>>> You received this message because you are subscribed to the Google Groups 
>>>>> "Lift-committers" group.
>>>>> To post to this group, send email to [email protected].
>>>>> To unsubscribe from this group, send email to 
>>>>> [email protected].
>>>>> For more options, visit this group at 
>>>>> http://groups.google.com/group/lift-committers?hl=en.
>>>>> 
>>>>> 
>>>> 
>>>> --
>>>> You received this message because you are subscribed to the Google Groups 
>>>> "Lift" group.
>>>> To post to this group, send email to [email protected].
>>>> To unsubscribe from this group, send email to 
>>>> [email protected].
>>>> For more options, visit this group at 
>>>> http://groups.google.com/group/liftweb?hl=en.
>>>> 
>>>> 
>>> 
>>> --
>>> You received this message because you are subscribed to the Google Groups 
>>> "Lift" group.
>>> To post to this group, send email to [email protected].
>>> To unsubscribe from this group, send email to 
>>> [email protected].
>>> For more options, visit this group at 
>>> http://groups.google.com/group/liftweb?hl=en.
>>> 
>>> 
>> 
>> 
>> --
>> You received this message because you are subscribed to the Google Groups 
>> "Lift" group.
>> To post to this group, send email to [email protected].
>> To unsubscribe from this group, send email to 
>> [email protected].
>> For more options, visit this group at 
>> http://groups.google.com/group/liftweb?hl=en.
>> 
>> 
>> 
>> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "Lift" group.
> To post to this group, send email to [email protected].
> To unsubscribe from this group, send email to 
> [email protected].
> For more options, visit this group at 
> http://groups.google.com/group/liftweb?hl=en.
> 
> 

-- 
You received this message because you are subscribed to the Google Groups 
"Lift" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/liftweb?hl=en.


Reply via email to