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.Transparency; 050import java.awt.image.BufferedImage; 051import java.awt.image.RenderedImage; 052import java.awt.image.ColorModel; 053import java.awt.image.IndexColorModel; 054import java.awt.image.Raster; 055import java.awt.image.WritableRaster; 056import java.awt.Color; 057import javax.imageio.ImageTypeSpecifier; 058 059 060/** 061 * This class implements the octree quantization method 062 * as it is described in the "Graphics Gems" 063 * (ISBN 0-12-286166-3, Chapter 4, pages 297-293) 064 */ 065public class PaletteBuilder { 066 067 /** 068 * maximum of tree depth 069 */ 070 protected static final int MAXLEVEL = 8; 071 072 protected RenderedImage src; 073 protected ColorModel srcColorModel; 074 protected Raster srcRaster; 075 076 protected int requiredSize; 077 078 protected ColorNode root; 079 080 protected int numNodes; 081 protected int maxNodes; 082 protected int currLevel; 083 protected int currSize; 084 085 protected ColorNode[] reduceList; 086 protected ColorNode[] palette; 087 088 protected int transparency; 089 protected ColorNode transColor; 090 091 092 /** 093 * Creates an image representing given image 094 * <code>src</code> using <code>IndexColorModel<code>. 095 * 096 * Lossless conversion is not always possible (e.g. if number 097 * of colors in the given image exceeds maximum palette size). 098 * Result image then is an approximation constructed by octree 099 * quantization method. 100 * 101 * @exception IllegalArgumentException if <code>src</code> is 102 * <code>null</code>. 103 * 104 * @exception UnsupportedOperationException if implemented method 105 * is unable to create approximation of <code>src</code> 106 * and <code>canCreatePalette</code> returns <code>false</code>. 107 * 108 * @see createIndexColorModel 109 * 110 * @see canCreatePalette 111 * 112 */ 113 public static RenderedImage createIndexedImage(RenderedImage src) { 114 PaletteBuilder pb = new PaletteBuilder(src); 115 pb.buildPalette(); 116 return pb.getIndexedImage(); 117 } 118 119 /** 120 * Creates an palette representing colors from given image 121 * <code>img</code>. If number of colors in the given image exceeds 122 * maximum palette size closest colors would be merged. 123 * 124 * @exception IllegalArgumentException if <code>img</code> is 125 * <code>null</code>. 126 * 127 * @exception UnsupportedOperationException if implemented method 128 * is unable to create approximation of <code>img</code> 129 * and <code>canCreatePalette</code> returns <code>false</code>. 130 * 131 * @see createIndexedImage 132 * 133 * @see canCreatePalette 134 * 135 */ 136 public static IndexColorModel createIndexColorModel(RenderedImage img) { 137 PaletteBuilder pb = new PaletteBuilder(img); 138 pb.buildPalette(); 139 return pb.getIndexColorModel(); 140 } 141 142 /** 143 * Returns <code>true</code> if PaletteBuilder is able to create 144 * palette for given image type. 145 * 146 * @param type an instance of <code>ImageTypeSpecifier</code> to be 147 * indexed. 148 * 149 * @return <code>true</code> if the <code>PaletteBuilder</code> 150 * is likely to be able to create palette for this image type. 151 * 152 * @exception IllegalArgumentException if <code>type</code> 153 * is <code>null</code>. 154 */ 155 public static boolean canCreatePalette(ImageTypeSpecifier type) { 156 if (type == null) { 157 throw new IllegalArgumentException("type == null"); 158 } 159 return true; 160 } 161 162 /** 163 * Returns <code>true</code> if PaletteBuilder is able to create 164 * palette for given rendered image. 165 * 166 * @param image an instance of <code>RenderedImage</code> to be 167 * indexed. 168 * 169 * @return <code>true</code> if the <code>PaletteBuilder</code> 170 * is likely to be able to create palette for this image type. 171 * 172 * @exception IllegalArgumentException if <code>image</code> 173 * is <code>null</code>. 174 */ 175 public static boolean canCreatePalette(RenderedImage image) { 176 if (image == null) { 177 throw new IllegalArgumentException("image == null"); 178 } 179 ImageTypeSpecifier type = new ImageTypeSpecifier(image); 180 return canCreatePalette(type); 181 } 182 183 protected RenderedImage getIndexedImage() { 184 IndexColorModel icm = getIndexColorModel(); 185 186 BufferedImage dst = 187 new BufferedImage(src.getWidth(), src.getHeight(), 188 BufferedImage.TYPE_BYTE_INDEXED, icm); 189 190 WritableRaster wr = dst.getRaster(); 191 int minX = src.getMinX(); 192 int minY = src.getMinY(); 193 for (int y =0; y < dst.getHeight(); y++) { 194 for (int x = 0; x < dst.getWidth(); x++) { 195 Color aColor = getSrcColor(x + minX, y + minY); 196 wr.setSample(x, y, 0, findColorIndex(root, aColor)); 197 } 198 } 199 200 return dst; 201 } 202 203 204 protected PaletteBuilder(RenderedImage src) { 205 this(src, 256); 206 } 207 208 protected PaletteBuilder(RenderedImage src, int size) { 209 this.src = src; 210 this.srcColorModel = src.getColorModel(); 211 this.srcRaster = src.getData(); 212 213 this.transparency = 214 srcColorModel.getTransparency(); 215 216 if (transparency != Transparency.OPAQUE) { 217 this.requiredSize = size - 1; 218 transColor = new ColorNode(); 219 transColor.isLeaf = true; 220 } else { 221 this.requiredSize = size; 222 } 223 } 224 225 private Color getSrcColor(int x, int y) { 226 int argb = srcColorModel.getRGB(srcRaster.getDataElements(x, y, null)); 227 return new Color(argb, transparency != Transparency.OPAQUE); 228 } 229 230 protected int findColorIndex(ColorNode aNode, Color aColor) { 231 if (transparency != Transparency.OPAQUE && 232 aColor.getAlpha() != 0xff) 233 { 234 return 0; // default transparnt pixel 235 } 236 237 if (aNode.isLeaf) { 238 return aNode.paletteIndex; 239 } else { 240 int childIndex = getBranchIndex(aColor, aNode.level); 241 242 return findColorIndex(aNode.children[childIndex], aColor); 243 } 244 } 245 246 protected void buildPalette() { 247 reduceList = new ColorNode[MAXLEVEL + 1]; 248 for (int i = 0; i < reduceList.length; i++) { 249 reduceList[i] = null; 250 } 251 252 numNodes = 0; 253 maxNodes = 0; 254 root = null; 255 currSize = 0; 256 currLevel = MAXLEVEL; 257 258 /* 259 from the book 260 261 */ 262 263 int w = src.getWidth(); 264 int h = src.getHeight(); 265 int minX = src.getMinX(); 266 int minY = src.getMinY(); 267 for (int y = 0; y < h; y++) { 268 for (int x = 0; x < w; x++) { 269 270 Color aColor = getSrcColor(w - x + minX - 1, h - y + minY - 1); 271 /* 272 * If transparency of given image is not opaque we assume all 273 * colors with alpha less than 1.0 as fully transparent. 274 */ 275 if (transparency != Transparency.OPAQUE && 276 aColor.getAlpha() != 0xff) 277 { 278 transColor = insertNode(transColor, aColor, 0); 279 } else { 280 root = insertNode(root, aColor, 0); 281 } 282 if (currSize > requiredSize) { 283 reduceTree(); 284 } 285 } 286 } 287 } 288 289 protected ColorNode insertNode(ColorNode aNode, Color aColor, int aLevel) { 290 291 if (aNode == null) { 292 aNode = new ColorNode(); 293 numNodes++; 294 if (numNodes > maxNodes) { 295 maxNodes = numNodes; 296 } 297 aNode.level = aLevel; 298 aNode.isLeaf = (aLevel > MAXLEVEL); 299 if (aNode.isLeaf) { 300 currSize++; 301 } 302 } 303 aNode.colorCount++; 304 aNode.red += aColor.getRed(); 305 aNode.green += aColor.getGreen(); 306 aNode.blue += aColor.getBlue(); 307 308 if (!aNode.isLeaf) { 309 int branchIndex = getBranchIndex(aColor, aLevel); 310 if (aNode.children[branchIndex] == null) { 311 aNode.childCount++; 312 if (aNode.childCount == 2) { 313 aNode.nextReducible = reduceList[aLevel]; 314 reduceList[aLevel] = aNode; 315 } 316 } 317 aNode.children[branchIndex] = 318 insertNode(aNode.children[branchIndex], aColor, aLevel + 1); 319 } 320 return aNode; 321 } 322 323 protected IndexColorModel getIndexColorModel() { 324 int size = currSize; 325 if (transparency != Transparency.OPAQUE) { 326 size ++; // we need place for transparent color; 327 } 328 329 byte[] red = new byte[size]; 330 byte[] green = new byte[size]; 331 byte[] blue = new byte[size]; 332 333 int index = 0; 334 palette = new ColorNode[size]; 335 if (transparency != Transparency.OPAQUE) { 336 index ++; 337 } 338 339 int lastIndex = findPaletteEntry(root, index, red, green, blue); 340 341 IndexColorModel icm = null; 342 if (transparency != Transparency.OPAQUE) { 343 icm = new IndexColorModel(8, size, red, green, blue, 0); 344 } else { 345 icm = new IndexColorModel(8, currSize, red, green, blue); 346 } 347 return icm; 348 } 349 350 protected int findPaletteEntry(ColorNode aNode, int index, 351 byte[] red, byte[] green, byte[] blue) 352 { 353 if (aNode.isLeaf) { 354 red[index] = (byte)(aNode.red/aNode.colorCount); 355 green[index] = (byte)(aNode.green/aNode.colorCount); 356 blue[index] = (byte)(aNode.blue/aNode.colorCount); 357 aNode.paletteIndex = index; 358 359 palette[index] = aNode; 360 361 index++; 362 } else { 363 for (int i = 0; i < 8; i++) { 364 if (aNode.children[i] != null) { 365 index = findPaletteEntry(aNode.children[i], index, 366 red, green, blue); 367 } 368 } 369 } 370 return index; 371 } 372 373 protected int getBranchIndex(Color aColor, int aLevel) { 374 if (aLevel > MAXLEVEL || aLevel < 0) { 375 throw new IllegalArgumentException("Invalid octree node depth: " + 376 aLevel); 377 } 378 379 int shift = MAXLEVEL - aLevel; 380 int red_index = 0x1 & ((0xff & aColor.getRed()) >> shift); 381 int green_index = 0x1 & ((0xff & aColor.getGreen()) >> shift); 382 int blue_index = 0x1 & ((0xff & aColor.getBlue()) >> shift); 383 int index = (red_index << 2) | (green_index << 1) | blue_index; 384 return index; 385 } 386 387 protected void reduceTree() { 388 int level = reduceList.length - 1; 389 while (reduceList[level] == null && level >= 0) { 390 level--; 391 } 392 393 ColorNode thisNode = reduceList[level]; 394 if (thisNode == null) { 395 // nothing to reduce 396 return; 397 } 398 399 // look for element with lower color count 400 ColorNode pList = thisNode; 401 int minColorCount = pList.colorCount; 402 403 int cnt = 1; 404 while (pList.nextReducible != null) { 405 if (minColorCount > pList.nextReducible.colorCount) { 406 thisNode = pList; 407 minColorCount = pList.colorCount; 408 } 409 pList = pList.nextReducible; 410 cnt++; 411 } 412 413 // save pointer to first reducible node 414 // NB: current color count for node could be changed in future 415 if (thisNode == reduceList[level]) { 416 reduceList[level] = thisNode.nextReducible; 417 } else { 418 pList = thisNode.nextReducible; // we need to process it 419 thisNode.nextReducible = pList.nextReducible; 420 thisNode = pList; 421 } 422 423 if (thisNode.isLeaf) { 424 return; 425 } 426 427 // reduce node 428 int leafChildCount = thisNode.getLeafChildCount(); 429 thisNode.isLeaf = true; 430 currSize -= (leafChildCount - 1); 431 int aDepth = thisNode.level; 432 for (int i = 0; i < 8; i++) { 433 thisNode.children[i] = freeTree(thisNode.children[i]); 434 } 435 thisNode.childCount = 0; 436 } 437 438 protected ColorNode freeTree(ColorNode aNode) { 439 if (aNode == null) { 440 return null; 441 } 442 for (int i = 0; i < 8; i++) { 443 aNode.children[i] = freeTree(aNode.children[i]); 444 } 445 446 numNodes--; 447 return null; 448 } 449 450 /** 451 * The node of color tree. 452 */ 453 protected class ColorNode { 454 public boolean isLeaf; 455 public int childCount; 456 ColorNode[] children; 457 458 public int colorCount; 459 public long red; 460 public long blue; 461 public long green; 462 463 public int paletteIndex; 464 465 public int level; 466 ColorNode nextReducible; 467 468 public ColorNode() { 469 isLeaf = false; 470 level = 0; 471 childCount = 0; 472 children = new ColorNode[8]; 473 for (int i = 0; i < 8; i++) { 474 children[i] = null; 475 } 476 477 colorCount = 0; 478 red = green = blue = 0; 479 480 paletteIndex = 0; 481 } 482 483 public int getLeafChildCount() { 484 if (isLeaf) { 485 return 0; 486 } 487 int cnt = 0; 488 for (int i = 0; i < children.length; i++) { 489 if (children[i] != null) { 490 if (children[i].isLeaf) { 491 cnt ++; 492 } else { 493 cnt += children[i].getLeafChildCount(); 494 } 495 } 496 } 497 return cnt; 498 } 499 500 public int getRGB() { 501 int r = (int)red/colorCount; 502 int g = (int)green/colorCount; 503 int b = (int)blue/colorCount; 504 505 int c = 0xff << 24 | (0xff&r) << 16 | (0xff&g) << 8 | (0xff&b); 506 return c; 507 } 508 } 509}