5

I am creating images using ImageNew (and related) in Railo, which uses JAI under the covers.

When I save an image, I'm getting a 24-bit PNG, but I only need 8-bit. (Simply re-saving the file with as 8-bit with a graphics editor results a quarter to half as many bytes.)

ImageWrite doesn't offer any functionality regarding PNG bit depth, and I can't find any details of doing this with JAI itself either (getting a DNS error for http://jai-core.dev.java.net/)

Update:

Using the Quantize ImageFilter I can reduce the number of colours to 256 - this reduces the file size significantly (but still not as far as processing manually does), but still results in a 24-bit/unpaletted PNG file. Unfortunately, it also removes the transparency, which I need preserved (or at least re-applied.)

If I take the file this produces and run it through OptiPNG (a lossless PNG optimiser), it does produce the indexed 8-bit file and shaves off quite a few bytes and gives acceptable filesizes.

So, the remaining step of the puzzle: how do I re-apply transparency after ImageFilter has removed it (or better, prevent it being removed).

I guess I need some way to do Image.replace('white','transparent') either as a Railo/Java-based solution, or a cross-platform command-line tool.

Jeromy French
  • 11,812
  • 19
  • 76
  • 129
Peter Boughton
  • 110,170
  • 32
  • 120
  • 176
  • Have you tried [this](http://docs.oracle.com/cd/E17802_01/products/products/java-media/jai/forDevelopers/jai-apidocs/com/sun/media/jai/codec/PNGEncodeParam.RGB.html#setBitDepth(int)) ? – arunkumar Sep 04 '12 at 01:08
  • The JAI stuff is a bit complex. I could be way off base, but I have seen a few threads suggest you need a Quantizer. IF that is correct, you might try the one in Railo. I believe it uses the JH Labs image library which includes a quantize filter. Another possibility is using [ColorConvertOp](http://grepcode.com/file/repo1.maven.org/maven2/org.jclarion/image4j/0.7/net/sf/image4j/util/ConvertUtil.java?av=f). I cannot vouch for the quality of either, but hopefully that might give you some ideas... – Leigh Sep 04 '12 at 07:04
  • Thanks Leigh. I started looking for CLI tools I could cfexecute, and found [pngquant](http://pngquant.org/) and [pngnq](http://pornel.net/pngnq) which are a couple of quantizers using different algorithms. They both reduce the filesize a fair bit, but are both still a couple of KB behind what I'm getting doing it manually. – Peter Boughton Sep 04 '12 at 16:58
  • I just gave the Railo/JHLabs quantize filter a go (had completely forgot about ImageFilter), and it does better, and once I run it through OptiPNG it does give comparable sizes to my manually produced files - except the filter removes the transparency. :( Don't have more time to investigate now, but guess I'll quickly add this extra info into the question. – Peter Boughton Sep 04 '12 at 16:59
  • Ohh, good point. I thought the JH Labs filter handled that, but I guess not. – Leigh Sep 04 '12 at 17:12
  • Well there might be another filter that can fix it, but couldn't see anything obvious in the list and don't have time to dig deeper at the moment. – Peter Boughton Sep 04 '12 at 17:19
  • I do not have time right now either but there is filter for replacing colors too ie MapColorsFilter. However, my gut says that should be a last resort. Like you mentioned, prevention is the better path. – Leigh Sep 04 '12 at 17:38

3 Answers3

2

Ok, so I have a working solution that produces acceptable results - the final files are slightly smaller than my original manual process but visually indistinguishable.

The solution is not as cross-platform as I'd like (need to go find/build Linux binary for OptiPNG), but it's still a good enough solution.

As Leigh suggested in the question comments, what I've used is a Quantizer to reduce the colours, then using the MapColorsFilter to fix the fact that the quantizer breaks transparency, then finally, using OptiPNG to compress the resulting file to a reasonable size.

Here's the relevant code:

<cfscript>
    var Filename = './filename.png'
    var MyImage = NewImage(Filename)

    ImageFilter(MyImage,'quantize',{numColors:256,dither:false})

    // ImageFilter(MyImage,'MapColors',{oldColor:'white',newColor:'ffffff00'})
    var TransImage = ImageMapColors(MyImage,'white','ffffff00')

    ImageWrite( TransImage , Filename )
</cfscript>

<cfexecute
    name      = "#Variables.OptiPngExecutable#"
    arguments = "-o9 #Filename#"
    timeout   = 30
/>

There's currently a bug in Railo's ImageFilter for the MapColors filter, so I've had to access the filter directly, here's the code I've used to workaround that:

<cffunction name="ImageMapColors" output=false >
    <cfargument name="Image" rtype="Image" required />
    <cfargument name="Old"   type="String" required />
    <cfargument name="New"   type="String" required />

    <cfset var ObjKey = 'ColorReplacer_#Arguments.Old#_#Arguments.New#' />

    <cfif NOT StructKeyExists(Variables,ObjKey)>
        <cfset var Old = createObject('java','railo.commons.color.ColorCaster').toColor(Arguments.Old) />
        <cfset var New = createObject('java','railo.commons.color.ColorCaster').toColor(Arguments.New) />
        <cfset Variables[ObjKey] = createObject("java","railo.runtime.img.filter.MapColorsFilter")
            .init(Old.getRGB(),New.getRGB()) />
    </cfif>

    <cfreturn ImageNew( Variables[ObjKey].filter(ImageGetBufferedImage(Arguments.Image),{}) ) />
</cffunction>
Peter Boughton
  • 110,170
  • 32
  • 120
  • 176
0

pngquant with cfexecute should satisfy your needs.

Henry
  • 32,689
  • 19
  • 120
  • 221
  • Does it handle transparency? Reason for asking is Peter mentioned he already tried pngquant and pngnq in the comments. – Leigh Sep 04 '12 at 19:05
  • 1
    Using optiquant does preserve transparency, but the files it produces don't compress with OptiPNG and are almost twice the size as using `ImageFilter(img,'quantize')`+OptiPNG. (Transparency should be a matter of bytes, not kilobytes). – Peter Boughton Sep 04 '12 at 21:56
-1

Have you tried using an intermediary step saving first as a JPEG using the "quality" attribute of ImageWrite(), then saving as PNG? Just a thought. Guessing use .3 or .4 as a value may work?

Marko
  • 20,385
  • 13
  • 48
  • 64
  • 3
    Eh? Applying JPEG compression simply degrades the image. If the image suited JPEG compression I would be creating a JPEG in the first place. – Peter Boughton Sep 04 '12 at 00:59