I'm working on Google App Engine for Java (GAEJ) now.
Very excited about working on it because can develop Java Web App quite easliy & quickly!
However I faced a limitation of image processing on GAEJ. On GAEJ, java.awt pakage.* is not supported. That means we cannot use BufferedImage etc!!
As you know, Google provides com.google.appengine.api.images.* for image processing on GAEJ, something like below:
It can read jpg, png, gif images accroding to the official document. And if transformation is applied, the output binary data becomes png format. So pure java png library should be very helpful.
I read the wiki and understood how to use PNGJ - actually the usage is quite simple.
Okay now let's come to the point. What I wanted to do is creating indexed png image on GAEJ. ImageService of GAEJ always creates 24bit color + 8 bit alpha png from gif image. I was unhappy that the result png image file size increased after GAEJ converted the image :( I read this this page and tried it myself :)
Here is the code for converting 32 bit image to indexed image. The usage is something like this:
The actual code is as below:
Following images are the example result of my program - the left image is the original png, the right image is the indexed png converted by my program.
No difference in image quality but the file size becomes smaller. 6,809 bytes -> 1,944 bytes :D
Very excited about working on it because can develop Java Web App quite easliy & quickly!
However I faced a limitation of image processing on GAEJ. On GAEJ, java.awt pakage.* is not supported. That means we cannot use BufferedImage etc!!
As you know, Google provides com.google.appengine.api.images.* for image processing on GAEJ, something like below:
ImagesService imagesService = ImagesServiceFactory.getImagesService(); Image srcImage = ImagesServiceFactory.makeImage(srcImageData); // some transformations. Transform crop = ImagesServiceFactory.makeCrop(0.3, 0, 1, 0.70); OutputSettings settings = new OutputSettings(OutputEncoding.PNG); // apply transform Image newImage = imagesService.applyTransform(crop, srcImage, settings); byte[] newImageData = newImage.getImageData();
It can read jpg, png, gif images accroding to the official document. And if transformation is applied, the output binary data becomes png format. So pure java png library should be very helpful.
PNGJ
I googled a bit and found Java library for png - PNGJ.I read the wiki and understood how to use PNGJ - actually the usage is quite simple.
Okay now let's come to the point. What I wanted to do is creating indexed png image on GAEJ. ImageService of GAEJ always creates 24bit color + 8 bit alpha png from gif image. I was unhappy that the result png image file size increased after GAEJ converted the image :( I read this this page and tried it myself :)
- Update: 2014/08/31 - if you are trying to create indexed png only using Java standard Image API, please see Create Indexed PNG Image Using Standard Java Image API
- Update: 2014/09/15 - I have used pngj 0.96 in this post. If you are looking the sample code for pngj 2.X.X, please see Java: Create Index PNG Using PNGJ 2.1.1
Here is the code for converting 32 bit image to indexed image. The usage is something like this:
InputStream is = new FileInputStream("c:/temp/source.png"); OutputStream os = new FileOutputStream("c:/temp/indexed256.png"); PngUtils.toIndexed256Colors(is, os);
The actual code is as below:
package com.dukesoftware.image; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import ar.com.hjg.pngj.ImageInfo; import ar.com.hjg.pngj.ImageLine; import ar.com.hjg.pngj.PngReader; import ar.com.hjg.pngj.PngWriter; import ar.com.hjg.pngj.chunks.ChunkCopyBehaviour; public final class PngUtils { private static final int _1K_BYTES = 1024; private static final int _8BIT_INT = 256; private PngUtils(){} public static void toIndexed256Colors(InputStream is, OutputStream os) throws IOException { PngReader png1 = new PngReader(is, ""); ImageInfo pnginfo1 = png1.imgInfo; if(pnginfo1.indexed){ // already indexed copy(is, os); } // 1st step: create color palette from source png and also create indexed color pixel lines used by the palette final ColorPaletteBuilder builder = new ColorPaletteBuilder(); int[][] indexedLine = new int[pnginfo1.rows][pnginfo1.cols]; final int channel = pnginfo1.channels; for (int row = 0, rows = pnginfo1.rows; row < rows; row++) { final int[] scaleline = png1.readRow(row).scanline; for(int col = 0, len = scaleline.length / channel, offset = 0; col < len; col++, offset = col * channel){ indexedLine[row][col] = builder.getIndex(scaleline[offset], scaleline[offset+1], scaleline[offset+2]); } // we can check number of colors when each pixel is written but this is not efficient if(builder.getNumberOfColors() > _8BIT_INT){ // maybe better to define Custom Exception throw new IllegalStateException("Number of colors reached more than 256! Reduce number of colors of original image first"); } } png1.end(); // 2nd step: write the palette and the indexed color pixel lines to output png ImageInfo pnginfo2 = new ImageInfo(png1.imgInfo.cols, png1.imgInfo.rows, 8, false,false,true); PngWriter png2 = new PngWriter(os, pnginfo2); png2.setCompLevel(9); png2.getMetadata().queueChunk(builder.buildPngChunkPaletteFromCurrentMap(pnginfo2)); png2.copyChunksFirst(png1, ChunkCopyBehaviour.COPY_ALL); ImageLine l2 = new ImageLine(pnginfo2); for (int row = 0, rows = png1.imgInfo.rows; row < rows; row++) { l2.pack(indexedLine[row], false); png2.writeRow(l2, row); } png2.copyChunksLast(png1, ChunkCopyBehaviour.COPY_ALL); png2.end(); } // utility methods: not important private final static void copy(InputStream is, OutputStream os) throws IOException { copy(is, os, new byte[_1K_BYTES]); } private final static void copy(InputStream is, OutputStream os, byte[] buffer)throws IOException { try{ for (int bytes = 0 ;(bytes = is.read(buffer)) != -1; ) { os.write(buffer, 0, bytes); } os.flush(); }finally{ if(is != null){ try{ is.close(); } catch(IOException e){ // quietly close anyway } } } } }
package com.dukesoftware.image; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import ar.com.hjg.pngj.ImageInfo; import ar.com.hjg.pngj.chunks.PngChunkPLTE; public final class ColorPaletteBuilder { private final Mapmap = new HashMap (); private int index = -1; public int getIndex(int r, int g, int b) { Integer key = (r << 16) | (g << 8) | b; Integer indexInMap = map.get(key); if(indexInMap != null){ return indexInMap; } index++; map.put(key, index); return index; } public PngChunkPLTE buildPngChunkPaletteFromCurrentMap(ImageInfo pnginfo) { PngChunkPLTE palette = new PngChunkPLTE(pnginfo); palette.setNentries(map.size()); for(Entry entry : map.entrySet()){ int key = entry.getKey(); palette.setEntry( entry.getValue(), key >> 16, (key & 0xFF00) >> 8, key & 0x00FF ); } return palette; } public int getNumberOfColors() { return map.size(); } }
Result
No difference in image quality but the file size becomes smaller. 6,809 bytes -> 1,944 bytes :D
コメント