1 /* 2 * SmartSprites Project 3 * 4 * Copyright (C) 2007-2009, Stanisław Osiński. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without modification, 8 * are permitted provided that the following conditions are met: 9 * 10 * - Redistributions of source code must retain the above copyright notice, this 11 * list of conditions and the following disclaimer. 12 * 13 * - Redistributions in binary form must reproduce the above copyright notice, this 14 * list of conditions and the following disclaimer in the documentation and/or 15 * other materials provided with the distribution. 16 * 17 * - Neither the name of the SmartSprites Project nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * - We kindly request that you include in the end-user documentation provided with 22 * the redistribution and/or in the software itself an acknowledgement equivalent 23 * to the following: "This product includes software developed by the SmartSprites 24 * Project." 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 28 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 30 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 33 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 */ 37 package org.carrot2.util; 38 39 import java.awt.Color; 40 import java.awt.image.BufferedImage; 41 import java.awt.image.DataBuffer; 42 import java.awt.image.IndexColorModel; 43 import java.awt.image.WritableRaster; 44 45 import amd.Quantize; 46 47 /** 48 * A simple utility wrapping the {@link Quantize} class to work on {@link BufferedImage}s and handle transparency. 49 */ 50 public class ColorQuantizer { 51 52 /** Maximum number of colors in an indexed image, leaving one for transparency. */ 53 public static final int MAX_INDEXED_COLORS = 255; 54 55 /** 56 * Instantiates a new color quantizer. 57 */ 58 private ColorQuantizer() { 59 // Prevent Instantiation 60 } 61 62 /** 63 * Quantizes the image to {@link #MAX_INDEXED_COLORS} with white matte for areas with partial transparency (full 64 * transparency will be preserved). 65 * 66 * @param source 67 * the source 68 * 69 * @return {@link BufferedImage} with type {@link BufferedImage#TYPE_BYTE_INDEXED} and quantized colors 70 */ 71 public static BufferedImage quantize(BufferedImage source) { 72 return quantize(source, Color.WHITE); 73 } 74 75 /** 76 * Quantizes the image to {@link #MAX_INDEXED_COLORS} with the provided matte {@link Color} for areas with partial 77 * transparency (full transparency will be preserved). 78 * 79 * @param source 80 * the source 81 * @param matteColor 82 * the matte color 83 * 84 * @return {@link BufferedImage} with type {@link BufferedImage#TYPE_BYTE_INDEXED} and quantized colors 85 */ 86 public static BufferedImage quantize(BufferedImage source, Color matteColor) { 87 return quantize(source, matteColor, MAX_INDEXED_COLORS); 88 } 89 90 /** 91 * Quantizes the image to the provided number of colors with the provided matte {@link Color} for areas with partial 92 * transparency (full transparency will be preserved). 93 * 94 * @param source 95 * the source 96 * @param matteColor 97 * the matte color 98 * @param maxColors 99 * the max colors 100 * 101 * @return {@link BufferedImage} with type {@link BufferedImage#TYPE_BYTE_INDEXED} and quantized colors 102 */ 103 public static BufferedImage quantize(BufferedImage source, Color matteColor, int maxColors) { 104 final int width = source.getWidth(); 105 final int height = source.getHeight(); 106 107 // First put the matte color so that we have a sensible result 108 // for images with full alpha transparencies 109 final BufferedImage mattedSource = BufferedImageUtils.matte(source, matteColor); 110 111 // Get two copies of RGB data (quantization will overwrite one) 112 final int[][] bitmap = BufferedImageUtils.getRgb(mattedSource); 113 114 // Quantize colors and shift palette by one for transparency color 115 // We'll keep transparency color black for now. 116 final int[] colors = Quantize.quantizeImage(bitmap, maxColors); 117 final int[] colorsWithAlpha = new int[colors.length + 1]; 118 System.arraycopy(colors, 0, colorsWithAlpha, 1, colors.length); 119 colorsWithAlpha[0] = matteColor.getRGB(); 120 final IndexColorModel colorModel = new IndexColorModel(8, colorsWithAlpha.length, colorsWithAlpha, 0, false, 0, 121 DataBuffer.TYPE_BYTE); 122 123 // Write the results to an indexed image, skipping the fully transparent bits 124 final BufferedImage quantized = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, colorModel); 125 final WritableRaster raster = quantized.getRaster(); 126 final int[][] rgb = BufferedImageUtils.getRgb(source); 127 for (int x = 0; x < width; x++) { 128 for (int y = 0; y < height; y++) { 129 final int value = (rgb[x][y] & 0xff000000) != 0x00000000 ? bitmap[x][y] + 1 : 0; 130 raster.setPixel(x, y, new int[] { value }); 131 } 132 } 133 134 return quantized; 135 } 136 137 /** 138 * Reduces a direct color buffered image to an indexed color one without quality loss. To make sure no quality loss 139 * will occur, check the results of the {@link #getColorReductionInfo(BufferedImage)} method call. 140 * 141 * @param source 142 * the source 143 * 144 * @return the buffered image 145 * 146 * @throws IllegalArgumentException 147 * if the application of this method would result in image quality loss 148 */ 149 public static BufferedImage reduce(BufferedImage source) { 150 final int width = source.getWidth(); 151 final int height = source.getHeight(); 152 153 if (BufferedImageUtils.hasPartialTransparency(source)) { 154 throw new IllegalArgumentException("The source image cannot contain translucent areas"); 155 } 156 157 final int[] colorsWithAlpha = BufferedImageUtils.getDistinctColors(source, 1); 158 if (colorsWithAlpha.length - 1 > MAX_INDEXED_COLORS) { 159 throw new IllegalArgumentException( 160 "The source image cannot contain more than " + MAX_INDEXED_COLORS + " colors"); 161 } 162 163 final IndexColorModel colorModel = new IndexColorModel(8, colorsWithAlpha.length, colorsWithAlpha, 0, false, 0, 164 DataBuffer.TYPE_BYTE); 165 166 // Write the results to an indexed image, skipping the fully transparent bits 167 final BufferedImage quantized = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, colorModel); 168 final int[][] rgb = BufferedImageUtils.getRgb(source); 169 170 for (int x = 0; x < width; x++) { 171 for (int y = 0; y < height; y++) { 172 if ((rgb[x][y] & 0xff000000) != 0x00000000) { 173 quantized.setRGB(x, y, source.getRGB(x, y)); 174 } 175 } 176 } 177 178 return quantized; 179 } 180 181 /** 182 * Returns a {@link ColorReductionInfo} for the provided image. 183 * 184 * @param source 185 * the source 186 * 187 * @return the color reduction info 188 */ 189 public static ColorReductionInfo getColorReductionInfo(BufferedImage source) { 190 return new ColorReductionInfo(BufferedImageUtils.hasPartialTransparency(source), 191 BufferedImageUtils.countDistinctColors(source)); 192 } 193 194 /** 195 * Indicates how many distinct colors an image has, whether it has partial transparency (alpha channel). 196 */ 197 public static class ColorReductionInfo { 198 199 /** Number of distinct colors in the image. */ 200 public int distinctColors; 201 202 /** True if the image has partially transparent areas (alpha channel). */ 203 public boolean hasPartialTransparency; 204 205 /** 206 * Instantiates a new color reduction info. 207 * 208 * @param hasPartialTransparency 209 * the has partial transparency 210 * @param distinctColors 211 * the distinct colors 212 */ 213 public ColorReductionInfo(boolean hasPartialTransparency, int distinctColors) { 214 this.hasPartialTransparency = hasPartialTransparency; 215 this.distinctColors = distinctColors; 216 } 217 218 /** 219 * Returns true if the image can be saved in a 8-bit indexed color format with 1-bit transparency without 220 * quality loss. 221 * 222 * @return true, if successful 223 */ 224 public boolean canReduceWithoutQualityLoss() { 225 return !hasPartialTransparency && distinctColors <= MAX_INDEXED_COLORS; 226 } 227 } 228 }