001/* 002 * $RCSfile: PaletteBuilder.java,v $ 003 * 004 * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or without 007 * modification, are permitted provided that the following conditions 008 * are met: 009 * 010 * - Redistribution of source code must retain the above copyright 011 * notice, this list of conditions and the following disclaimer. 012 * 013 * - Redistribution in binary form must reproduce the above copyright 014 * notice, this list of conditions and the following disclaimer in 015 * the documentation and/or other materials provided with the 016 * distribution. 017 * 018 * Neither the name of Sun Microsystems, Inc. or the names of 019 * contributors may be used to endorse or promote products derived 020 * from this software without specific prior written permission. 021 * 022 * This software is provided "AS IS," without a warranty of any 023 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 024 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 025 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 026 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 027 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 028 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS 029 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 030 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, 031 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND 032 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR 033 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 034 * POSSIBILITY OF SUCH DAMAGES. 035 * 036 * You acknowledge that this software is not designed or intended for 037 * use in the design, construction, operation or maintenance of any 038 * nuclear facility. 039 * 040 * $Revision: 1.3 $ 041 * $Date: 2007/08/31 00:06:00 $ 042 * $State: Exp $ 043 */ 044 045 046 047package com.github.jaiimageio.impl.common; 048 049import java.awt.Color; 050import java.awt.Transparency; 051import java.awt.image.BufferedImage; 052import java.awt.image.ColorModel; 053import java.awt.image.IndexColorModel; 054import java.awt.image.Raster; 055import java.awt.image.RenderedImage; 056import java.awt.image.WritableRaster; 057 058import javax.imageio.ImageTypeSpecifier; 059 060 061/** 062 * This class implements the octree quantization method 063 * as it is described in the "Graphics Gems" 064 * (ISBN 0-12-286166-3, Chapter 4, pages 297-293) 065 */ 066public class PaletteBuilder { 067 068 /** 069 * maximum of tree depth 070 */ 071 protected static final int MAXLEVEL = 8; 072 073 protected RenderedImage src; 074 protected ColorModel srcColorModel; 075 protected Raster srcRaster; 076 077 protected int requiredSize; 078 079 protected ColorNode root; 080 081 protected int numNodes; 082 protected int maxNodes; 083 protected int currLevel; 084 protected int currSize; 085 086 protected ColorNode[] reduceList; 087 protected ColorNode[] palette; 088 089 protected int transparency; 090 protected ColorNode transColor; 091 092 093 /** 094 * Creates an image representing given image 095 * <code>src</code> using <code>IndexColorModel<code>. 096 * 097 * Lossless conversion is not always possible (e.g. if number 098 * of colors in the given image exceeds maximum palette size). 099 * Result image then is an approximation constructed by octree 100 * quantization method. 101 * 102 * @exception IllegalArgumentException if <code>src</code> is 103 * <code>null</code>. 104 * 105 * @exception UnsupportedOperationException if implemented method 106 * is unable to create approximation of <code>src</code> 107 * and <code>canCreatePalette</code> returns <code>false</code>. 108 * 109 * @see #createIndexColorModel(RenderedImage) 110 * 111 * @see #canCreatePalette(RenderedImage) 112 * 113 */ 114 public static RenderedImage createIndexedImage(RenderedImage src) { 115 PaletteBuilder pb = new PaletteBuilder(src); 116 pb.buildPalette(); 117 return pb.getIndexedImage(); 118 } 119 120 /** 121 * Creates an palette representing colors from given image 122 * <code>img</code>. If number of colors in the given image exceeds 123 * maximum palette size closest colors would be merged. 124 * 125 * @exception IllegalArgumentException if <code>img</code> is 126 * <code>null</code>. 127 * 128 * @exception UnsupportedOperationException if implemented method 129 * is unable to create approximation of <code>img</code> 130 * and <code>canCreatePalette</code> returns <code>false</code>. 131 * 132 * @see #createIndexedImage(RenderedImage) 133 * 134 * @see #canCreatePalette(RenderedImage) 135 * 136 */ 137 public static IndexColorModel createIndexColorModel(RenderedImage img) { 138 PaletteBuilder pb = new PaletteBuilder(img); 139 pb.buildPalette(); 140 return pb.getIndexColorModel(); 141 } 142 143 /** 144 * Returns <code>true</code> if PaletteBuilder is able to create 145 * palette for given image type. 146 * 147 * @param type an instance of <code>ImageTypeSpecifier</code> to be 148 * indexed. 149 * 150 * @return <code>true</code> if the <code>PaletteBuilder</code> 151 * is likely to be able to create palette for this image type. 152 * 153 * @exception IllegalArgumentException if <code>type</code> 154 * is <code>null</code>. 155 */ 156 public static boolean canCreatePalette(ImageTypeSpecifier type) { 157 if (type == null) { 158 throw new IllegalArgumentException("type == null"); 159 } 160 return true; 161 } 162 163 /** 164 * Returns <code>true</code> if PaletteBuilder is able to create 165 * palette for given rendered image. 166 * 167 * @param image an instance of <code>RenderedImage</code> to be 168 * indexed. 169 * 170 * @return <code>true</code> if the <code>PaletteBuilder</code> 171 * is likely to be able to create palette for this image type. 172 * 173 * @exception IllegalArgumentException if <code>image</code> 174 * is <code>null</code>. 175 */ 176 public static boolean canCreatePalette(RenderedImage image) { 177 if (image == null) { 178 throw new IllegalArgumentException("image == null"); 179 } 180 ImageTypeSpecifier type = new ImageTypeSpecifier(image); 181 return canCreatePalette(type); 182 } 183 184 protected RenderedImage getIndexedImage() { 185 IndexColorModel icm = getIndexColorModel(); 186 187 BufferedImage dst = 188 new BufferedImage(src.getWidth(), src.getHeight(), 189 BufferedImage.TYPE_BYTE_INDEXED, icm); 190 191 WritableRaster wr = dst.getRaster(); 192 int minX = src.getMinX(); 193 int minY = src.getMinY(); 194 for (int y =0; y < dst.getHeight(); y++) { 195 for (int x = 0; x < dst.getWidth(); x++) { 196 Color aColor = getSrcColor(x + minX, y + minY); 197 wr.setSample(x, y, 0, findColorIndex(root, aColor)); 198 } 199 } 200 201 return dst; 202 } 203 204 205 protected PaletteBuilder(RenderedImage src) { 206 this(src, 256); 207 } 208 209 protected PaletteBuilder(RenderedImage src, int size) { 210 this.src = src; 211 this.srcColorModel = src.getColorModel(); 212 this.srcRaster = src.getData(); 213 214 this.transparency = 215 srcColorModel.getTransparency(); 216 217 if (transparency != Transparency.OPAQUE) { 218 this.requiredSize = size - 1; 219 transColor = new ColorNode(); 220 transColor.isLeaf = true; 221 } else { 222 this.requiredSize = size; 223 } 224 } 225 226 private Color getSrcColor(int x, int y) { 227 int argb = srcColorModel.getRGB(srcRaster.getDataElements(x, y, null)); 228 return new Color(argb, transparency != Transparency.OPAQUE); 229 } 230 231 protected int findColorIndex(ColorNode aNode, Color aColor) { 232 if (transparency != Transparency.OPAQUE && 233 aColor.getAlpha() != 0xff) 234 { 235 return 0; // default transparnt pixel 236 } 237 238 if (aNode.isLeaf) { 239 return aNode.paletteIndex; 240 } else { 241 int childIndex = getBranchIndex(aColor, aNode.level); 242 243 return findColorIndex(aNode.children[childIndex], aColor); 244 } 245 } 246 247 protected void buildPalette() { 248 reduceList = new ColorNode[MAXLEVEL + 1]; 249 for (int i = 0; i < reduceList.length; i++) { 250 reduceList[i] = null; 251 } 252 253 numNodes = 0; 254 maxNodes = 0; 255 root = null; 256 currSize = 0; 257 currLevel = MAXLEVEL; 258 259 /* 260 from the book 261 262 */ 263 264 int w = src.getWidth(); 265 int h = src.getHeight(); 266 int minX = src.getMinX(); 267 int minY = src.getMinY(); 268 for (int y = 0; y < h; y++) { 269 for (int x = 0; x < w; x++) { 270 271 Color aColor = getSrcColor(w - x + minX - 1, h - y + minY - 1); 272 /* 273 * If transparency of given image is not opaque we assume all 274 * colors with alpha less than 1.0 as fully transparent. 275 */ 276 if (transparency != Transparency.OPAQUE && 277 aColor.getAlpha() != 0xff) 278 { 279 transColor = insertNode(transColor, aColor, 0); 280 } else { 281 root = insertNode(root, aColor, 0); 282 } 283 if (currSize > requiredSize) { 284 reduceTree(); 285 } 286 } 287 } 288 } 289 290 protected ColorNode insertNode(ColorNode aNode, Color aColor, int aLevel) { 291 292 if (aNode == null) { 293 aNode = new ColorNode(); 294 numNodes++; 295 if (numNodes > maxNodes) { 296 maxNodes = numNodes; 297 } 298 aNode.level = aLevel; 299 aNode.isLeaf = (aLevel > MAXLEVEL); 300 if (aNode.isLeaf) { 301 currSize++; 302 } 303 } 304 aNode.colorCount++; 305 aNode.red += aColor.getRed(); 306 aNode.green += aColor.getGreen(); 307 aNode.blue += aColor.getBlue(); 308 309 if (!aNode.isLeaf) { 310 int branchIndex = getBranchIndex(aColor, aLevel); 311 if (aNode.children[branchIndex] == null) { 312 aNode.childCount++; 313 if (aNode.childCount == 2) { 314 aNode.nextReducible = reduceList[aLevel]; 315 reduceList[aLevel] = aNode; 316 } 317 } 318 aNode.children[branchIndex] = 319 insertNode(aNode.children[branchIndex], aColor, aLevel + 1); 320 } 321 return aNode; 322 } 323 324 protected IndexColorModel getIndexColorModel() { 325 int size = currSize; 326 if (transparency != Transparency.OPAQUE) { 327 size ++; // we need place for transparent color; 328 } 329 330 byte[] red = new byte[size]; 331 byte[] green = new byte[size]; 332 byte[] blue = new byte[size]; 333 334 int index = 0; 335 palette = new ColorNode[size]; 336 if (transparency != Transparency.OPAQUE) { 337 index ++; 338 } 339 340 int lastIndex = findPaletteEntry(root, index, red, green, blue); 341 342 IndexColorModel icm = null; 343 if (transparency != Transparency.OPAQUE) { 344 icm = new IndexColorModel(8, size, red, green, blue, 0); 345 } else { 346 icm = new IndexColorModel(8, currSize, red, green, blue); 347 } 348 return icm; 349 } 350 351 protected int findPaletteEntry(ColorNode aNode, int index, 352 byte[] red, byte[] green, byte[] blue) 353 { 354 if (aNode.isLeaf) { 355 red[index] = (byte)(aNode.red/aNode.colorCount); 356 green[index] = (byte)(aNode.green/aNode.colorCount); 357 blue[index] = (byte)(aNode.blue/aNode.colorCount); 358 aNode.paletteIndex = index; 359 360 palette[index] = aNode; 361 362 index++; 363 } else { 364 for (int i = 0; i < 8; i++) { 365 if (aNode.children[i] != null) { 366 index = findPaletteEntry(aNode.children[i], index, 367 red, green, blue); 368 } 369 } 370 } 371 return index; 372 } 373 374 protected int getBranchIndex(Color aColor, int aLevel) { 375 if (aLevel > MAXLEVEL || aLevel < 0) { 376 throw new IllegalArgumentException("Invalid octree node depth: " + 377 aLevel); 378 } 379 380 int shift = MAXLEVEL - aLevel; 381 int red_index = 0x1 & ((0xff & aColor.getRed()) >> shift); 382 int green_index = 0x1 & ((0xff & aColor.getGreen()) >> shift); 383 int blue_index = 0x1 & ((0xff & aColor.getBlue()) >> shift); 384 int index = (red_index << 2) | (green_index << 1) | blue_index; 385 return index; 386 } 387 388 protected void reduceTree() { 389 int level = reduceList.length - 1; 390 while (reduceList[level] == null && level >= 0) { 391 level--; 392 } 393 394 ColorNode thisNode = reduceList[level]; 395 if (thisNode == null) { 396 // nothing to reduce 397 return; 398 } 399 400 // look for element with lower color count 401 ColorNode pList = thisNode; 402 int minColorCount = pList.colorCount; 403 404 int cnt = 1; 405 while (pList.nextReducible != null) { 406 if (minColorCount > pList.nextReducible.colorCount) { 407 thisNode = pList; 408 minColorCount = pList.colorCount; 409 } 410 pList = pList.nextReducible; 411 cnt++; 412 } 413 414 // save pointer to first reducible node 415 // NB: current color count for node could be changed in future 416 if (thisNode == reduceList[level]) { 417 reduceList[level] = thisNode.nextReducible; 418 } else { 419 pList = thisNode.nextReducible; // we need to process it 420 thisNode.nextReducible = pList.nextReducible; 421 thisNode = pList; 422 } 423 424 if (thisNode.isLeaf) { 425 return; 426 } 427 428 // reduce node 429 int leafChildCount = thisNode.getLeafChildCount(); 430 thisNode.isLeaf = true; 431 currSize -= (leafChildCount - 1); 432 int aDepth = thisNode.level; 433 for (int i = 0; i < 8; i++) { 434 thisNode.children[i] = freeTree(thisNode.children[i]); 435 } 436 thisNode.childCount = 0; 437 } 438 439 protected ColorNode freeTree(ColorNode aNode) { 440 if (aNode == null) { 441 return null; 442 } 443 for (int i = 0; i < 8; i++) { 444 aNode.children[i] = freeTree(aNode.children[i]); 445 } 446 447 numNodes--; 448 return null; 449 } 450 451 /** 452 * The node of color tree. 453 */ 454 protected class ColorNode { 455 public boolean isLeaf; 456 public int childCount; 457 ColorNode[] children; 458 459 public int colorCount; 460 public long red; 461 public long blue; 462 public long green; 463 464 public int paletteIndex; 465 466 public int level; 467 ColorNode nextReducible; 468 469 public ColorNode() { 470 isLeaf = false; 471 level = 0; 472 childCount = 0; 473 children = new ColorNode[8]; 474 for (int i = 0; i < 8; i++) { 475 children[i] = null; 476 } 477 478 colorCount = 0; 479 red = green = blue = 0; 480 481 paletteIndex = 0; 482 } 483 484 public int getLeafChildCount() { 485 if (isLeaf) { 486 return 0; 487 } 488 int cnt = 0; 489 for (int i = 0; i < children.length; i++) { 490 if (children[i] != null) { 491 if (children[i].isLeaf) { 492 cnt ++; 493 } else { 494 cnt += children[i].getLeafChildCount(); 495 } 496 } 497 } 498 return cnt; 499 } 500 501 public int getRGB() { 502 int r = (int)red/colorCount; 503 int g = (int)green/colorCount; 504 int b = (int)blue/colorCount; 505 506 int c = 0xff << 24 | (0xff&r) << 16 | (0xff&g) << 8 | (0xff&b); 507 return c; 508 } 509 } 510}