Hello guys! I have written Java: How to Create Indexed PNG Using PNGJ Library a few years ago.
Recently I investigated how to create indexed png by standard Java image API.
I have googled and tested various methods, finally I figureed out how to create indexed png image.
Still my program is not perfect (of course most of the case it worked well), I believe that this program gives you the hint to creating indexed png and image manipulation on standard Java API.
Code
Actually the method is quite straigt forward. Please see the code below :)
The advantedge of this code is the code doesn't require any library. The code below contains all of the stuffs needed for creating indexed png image!
package com.dukesoftware.util.image; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorConvertOp; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.imageio.ImageIO; public class PngOptimizer { public static void main(String[] args) throws FileNotFoundException, IOException { final String inputPath = "c:/temp/p.png"; final String outputPath = "c:/temp/indexed256.png"; InputStream is = new FileInputStream(inputPath); BufferedImage image = ImageIO.read(is); if (image.getType() == BufferedImage.TYPE_CUSTOM) { Files.copy(new File(inputPath).toPath(), new FileOutputStream(outputPath)); return; } BufferedImage outImage = toIndexed(image); ImageIO.write(outImage, "png", new FileOutputStream(outputPath)); } /** * Core method for creating indexed image. */ public static BufferedImage toIndexed(BufferedImage image) throws IOException { final boolean hasAlpha = image.getColorModel().hasAlpha(); final int w = image.getWidth(); final int h = image.getHeight(); if (image.getType() == BufferedImage.TYPE_BYTE_INDEXED) { // Already indexed, but try resampling it. // because sometimes the image size becomes smaller than original one. final ColorModel originalColorModel = image.getColorModel(); int[] iArray = image.getRaster().getPixels(0, 0, image.getWidth(), image.getHeight(), (int[]) null); Set<Integer> colorSet = new HashSet<>(); for (int i = 0; i < iArray.length; i++) { colorSet.add(originalColorModel.getRGB(iArray[i])); } int[] colorMap = new int[colorSet.size()]; Map<Integer, Integer> transMap = new HashMap<>(colorMap.length); populateToMap(colorSet, colorMap, transMap); BufferedImage outImage = createIndexedBufferedImage(w, h, colorMap, hasAlpha); for (int i = 0, width = image.getWidth(); i < iArray.length; i++) { outImage.getRaster().setSample(i % width, i / width, 0, transMap.get(originalColorModel.getRGB(iArray[i]))); } return outImage; } else if (image.getType() == BufferedImage.TYPE_CUSTOM) { // if we cannot determine the image type, then do nothing return image; } // create indexed png from non-indexed png. final int bits = hasAlpha ? 4 : 3; final int[] iArray = image.getRaster().getPixels(0, 0, w, h, (int[]) null); Set<Integer> colorSet = new HashSet<>(); if (hasAlpha) { for (int i = 0; i < iArray.length; i += bits) { int r = iArray[i]; int g = iArray[i+1]; int b = iArray[i+2]; int a = iArray[i+3]; colorSet.add((a << 24) | (r << 16) | (g << 8) | (b)); } } else { for (int i = 0; i < iArray.length; i += bits) { int r = iArray[i]; int g = iArray[i + 1]; int b = iArray[i + 2]; colorSet.add((r << 16) | (g << 8) | (b)); } } if(colorSet.size() > 256) { throw new IllegalArgumentException("the image file contains more than 256 colors. could not convert to indexed image."); } int[] colorMap = new int[colorSet.size()]; Map<Integer, Integer> transMap = new HashMap<>(colorMap.length); populateToMap(colorSet, colorMap, transMap); BufferedImage outImage = createIndexedBufferedImage(w, h, colorMap, hasAlpha); if (hasAlpha) { for (int i = 0, width = image.getWidth(), k = 0; i < iArray.length; i += bits, k = i / bits) { int r = iArray[i]; int g = iArray[i+1]; int b = iArray[i+2]; int a = iArray[i+3]; outImage.getRaster().setSample(k % width, k / width, 0, transMap.get((a << 24) | (r << 16) | (g << 8) | (b))); } } else { for (int i = 0, width = image.getWidth(), k = 0; i < iArray.length; i += bits, k = i / bits) { int r = iArray[i]; int g = iArray[i + 1]; int b = iArray[i + 2]; outImage.getRaster().setSample(k % width, k / width, 0, transMap.get((r << 16) | (g << 8) | (b))); } } return outImage; } private static BufferedImage createIndexedBufferedImage(int w, int h, int[] colorMap, final boolean hasAlpha) { IndexColorModel colorModel = new IndexColorModel(determineBitDepth(colorMap.length), colorMap.length, colorMap, 0, hasAlpha, colorMap[0], DataBuffer.TYPE_BYTE); return new BufferedImage(w, h, BufferedImage.TYPE_BYTE_INDEXED, colorModel); } private static int determineBitDepth(int numberOfColors) { if (numberOfColors <= 1) return 1; else if(numberOfColors <= 16) return 4; else if(numberOfColors <= 256) return 8; else throw new IllegalStateException("Number of colors should be less than 256"); } private static void populateToMap(Set<Integer> colorSet, int[] colorMap, Map<Integer, Integer> transMap) { int j = 0; for (Integer color : colorSet) { colorMap[j] = color; transMap.put(color, j); j++; } } }
Result
I have tested my program by passing non-indexed image.
The left side image is an original image and the right side image is the indexed png image generated by my program.
The size is reduced 6809 byte -> 2551 byte :D
コメント