1 /* 2 * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.imageio.plugins.common; 27 28 import java.awt.Transparency; 29 import java.awt.image.BufferedImage; 30 import java.awt.image.RenderedImage; 31 import java.awt.image.ColorModel; 32 import java.awt.image.IndexColorModel; 33 import java.awt.image.Raster; 34 import java.awt.image.WritableRaster; 35 import java.awt.Color; 36 import javax.imageio.ImageTypeSpecifier; 37 38 39 /** 40 * This class implements the octree quantization method 41 * as it is described in the "Graphics Gems" 42 * (ISBN 0-12-286166-3, Chapter 4, pages 297-293) 43 */ 44 public class PaletteBuilder { 45 46 /** 47 * maximum of tree depth 48 */ 49 protected static final int MAXLEVEL = 8; 50 51 protected RenderedImage src; 52 protected ColorModel srcColorModel; 53 protected Raster srcRaster; 54 55 protected int requiredSize; 56 57 protected ColorNode root; 58 59 protected int numNodes; 60 protected int maxNodes; 61 protected int currLevel; 62 protected int currSize; 63 64 protected ColorNode[] reduceList; 65 protected ColorNode[] palette; 66 67 protected int transparency; 68 protected ColorNode transColor; 69 70 71 /** 72 * Creates an image representing given image 73 * {@code src} using {@code IndexColorModel}. 74 * 75 * Lossless conversion is not always possible (e.g. if number 76 * of colors in the given image exceeds maximum palette size). 77 * Result image then is an approximation constructed by octree 78 * quantization method. 79 * 80 * @exception IllegalArgumentException if {@code src} is 81 * {@code null}. 82 * 83 * @exception UnsupportedOperationException if implemented method 84 * is unable to create approximation of {@code src} 85 * and {@code canCreatePalette} returns {@code false}. 86 * 87 * @see createIndexColorModel 88 * 89 * @see canCreatePalette 90 * 91 */ 92 public static RenderedImage createIndexedImage(RenderedImage src) { 93 PaletteBuilder pb = new PaletteBuilder(src); 94 pb.buildPalette(); 95 return pb.getIndexedImage(); 96 } 97 98 /** 99 * Creates an palette representing colors from given image 100 * {@code img}. If number of colors in the given image exceeds 101 * maximum palette size closest colors would be merged. 102 * 103 * @exception IllegalArgumentException if {@code img} is 104 * {@code null}. 105 * 106 * @exception UnsupportedOperationException if implemented method 107 * is unable to create approximation of {@code img} 108 * and {@code canCreatePalette} returns {@code false}. 109 * 110 * @see createIndexedImage 111 * 112 * @see canCreatePalette 113 * 114 */ 115 public static IndexColorModel createIndexColorModel(RenderedImage img) { 116 PaletteBuilder pb = new PaletteBuilder(img); 117 pb.buildPalette(); 118 return pb.getIndexColorModel(); 119 } 120 121 /** 122 * Returns {@code true} if PaletteBuilder is able to create 123 * palette for given image type. 124 * 125 * @param type an instance of {@code ImageTypeSpecifier} to be 126 * indexed. 127 * 128 * @return {@code true} if the {@code PaletteBuilder} 129 * is likely to be able to create palette for this image type. 130 * 131 * @exception IllegalArgumentException if {@code type} 132 * is {@code null}. 133 */ 134 public static boolean canCreatePalette(ImageTypeSpecifier type) { 135 if (type == null) { 136 throw new IllegalArgumentException("type == null"); 137 } 138 return true; 139 } 140 141 /** 142 * Returns {@code true} if PaletteBuilder is able to create 143 * palette for given rendered image. 144 * 145 * @param image an instance of {@code RenderedImage} to be 146 * indexed. 147 * 148 * @return {@code true} if the {@code PaletteBuilder} 149 * is likely to be able to create palette for this image type. 150 * 151 * @exception IllegalArgumentException if {@code image} 152 * is {@code null}. 153 */ 154 public static boolean canCreatePalette(RenderedImage image) { 155 if (image == null) { 156 throw new IllegalArgumentException("image == null"); 157 } 158 ImageTypeSpecifier type = new ImageTypeSpecifier(image); 159 return canCreatePalette(type); 160 } 161 162 protected RenderedImage getIndexedImage() { 163 IndexColorModel icm = getIndexColorModel(); 164 165 BufferedImage dst = 166 new BufferedImage(src.getWidth(), src.getHeight(), 167 BufferedImage.TYPE_BYTE_INDEXED, icm); 168 169 WritableRaster wr = dst.getRaster(); 170 for (int y =0; y < dst.getHeight(); y++) { 171 for (int x = 0; x < dst.getWidth(); x++) { 172 Color aColor = getSrcColor(x,y); 173 wr.setSample(x, y, 0, findColorIndex(root, aColor)); 174 } 175 } 176 177 return dst; 178 } 179 180 181 protected PaletteBuilder(RenderedImage src) { 182 this(src, 256); 183 } 184 185 protected PaletteBuilder(RenderedImage src, int size) { 186 this.src = src; 187 this.srcColorModel = src.getColorModel(); 188 this.srcRaster = src.getData(); 189 190 this.transparency = 191 srcColorModel.getTransparency(); 192 193 this.requiredSize = size; 194 } 195 196 private Color getSrcColor(int x, int y) { 197 int argb = srcColorModel.getRGB(srcRaster.getDataElements(x, y, null)); 198 return new Color(argb, transparency != Transparency.OPAQUE); 199 } 200 201 protected int findColorIndex(ColorNode aNode, Color aColor) { 202 if (transparency != Transparency.OPAQUE && 203 aColor.getAlpha() != 0xff) 204 { 205 return 0; // default transparnt pixel 206 } 207 208 if (aNode.isLeaf) { 209 return aNode.paletteIndex; 210 } else { 211 int childIndex = getBranchIndex(aColor, aNode.level); 212 213 return findColorIndex(aNode.children[childIndex], aColor); 214 } 215 } 216 217 protected void buildPalette() { 218 reduceList = new ColorNode[MAXLEVEL + 1]; 219 for (int i = 0; i < reduceList.length; i++) { 220 reduceList[i] = null; 221 } 222 223 numNodes = 0; 224 maxNodes = 0; 225 root = null; 226 currSize = 0; 227 currLevel = MAXLEVEL; 228 229 /* 230 from the book 231 232 */ 233 234 int w = src.getWidth(); 235 int h = src.getHeight(); 236 for (int y = 0; y < h; y++) { 237 for (int x = 0; x < w; x++) { 238 239 Color aColor = getSrcColor(w - x - 1, h - y - 1); 240 /* 241 * If transparency of given image is not opaque we assume all 242 * colors with alpha less than 1.0 as fully transparent. 243 */ 244 if (transparency != Transparency.OPAQUE && 245 aColor.getAlpha() != 0xff) 246 { 247 if (transColor == null) { 248 this.requiredSize --; // one slot for transparent color 249 250 transColor = new ColorNode(); 251 transColor.isLeaf = true; 252 } 253 transColor = insertNode(transColor, aColor, 0); 254 } else { 255 root = insertNode(root, aColor, 0); 256 } 257 if (currSize > requiredSize) { 258 reduceTree(); 259 } 260 } 261 } 262 } 263 264 protected ColorNode insertNode(ColorNode aNode, Color aColor, int aLevel) { 265 266 if (aNode == null) { 267 aNode = new ColorNode(); 268 numNodes++; 269 if (numNodes > maxNodes) { 270 maxNodes = numNodes; 271 } 272 aNode.level = aLevel; 273 aNode.isLeaf = (aLevel > MAXLEVEL); 274 if (aNode.isLeaf) { 275 currSize++; 276 } 277 } 278 aNode.colorCount++; 279 aNode.red += aColor.getRed(); 280 aNode.green += aColor.getGreen(); 281 aNode.blue += aColor.getBlue(); 282 283 if (!aNode.isLeaf) { 284 int branchIndex = getBranchIndex(aColor, aLevel); 285 if (aNode.children[branchIndex] == null) { 286 aNode.childCount++; 287 if (aNode.childCount == 2) { 288 aNode.nextReducible = reduceList[aLevel]; 289 reduceList[aLevel] = aNode; 290 } 291 } 292 aNode.children[branchIndex] = 293 insertNode(aNode.children[branchIndex], aColor, aLevel + 1); 294 } 295 return aNode; 296 } 297 298 protected IndexColorModel getIndexColorModel() { 299 int size = currSize; 300 if (transColor != null) { 301 size ++; // we need place for transparent color; 302 } 303 304 byte[] red = new byte[size]; 305 byte[] green = new byte[size]; 306 byte[] blue = new byte[size]; 307 308 int index = 0; 309 palette = new ColorNode[size]; 310 if (transColor != null) { 311 index ++; 312 } 313 314 if (root != null) { 315 findPaletteEntry(root, index, red, green, blue); 316 } 317 318 IndexColorModel icm = null; 319 if (transColor != null) { 320 icm = new IndexColorModel(8, size, red, green, blue, 0); 321 } else { 322 icm = new IndexColorModel(8, currSize, red, green, blue); 323 } 324 return icm; 325 } 326 327 protected int findPaletteEntry(ColorNode aNode, int index, 328 byte[] red, byte[] green, byte[] blue) 329 { 330 if (aNode.isLeaf) { 331 red[index] = (byte)(aNode.red/aNode.colorCount); 332 green[index] = (byte)(aNode.green/aNode.colorCount); 333 blue[index] = (byte)(aNode.blue/aNode.colorCount); 334 aNode.paletteIndex = index; 335 336 palette[index] = aNode; 337 338 index++; 339 } else { 340 for (int i = 0; i < 8; i++) { 341 if (aNode.children[i] != null) { 342 index = findPaletteEntry(aNode.children[i], index, 343 red, green, blue); 344 } 345 } 346 } 347 return index; 348 } 349 350 protected int getBranchIndex(Color aColor, int aLevel) { 351 if (aLevel > MAXLEVEL || aLevel < 0) { 352 throw new IllegalArgumentException("Invalid octree node depth: " + 353 aLevel); 354 } 355 356 int shift = MAXLEVEL - aLevel; 357 int red_index = 0x1 & ((0xff & aColor.getRed()) >> shift); 358 int green_index = 0x1 & ((0xff & aColor.getGreen()) >> shift); 359 int blue_index = 0x1 & ((0xff & aColor.getBlue()) >> shift); 360 int index = (red_index << 2) | (green_index << 1) | blue_index; 361 return index; 362 } 363 364 protected void reduceTree() { 365 int level = reduceList.length - 1; 366 while (reduceList[level] == null && level >= 0) { 367 level--; 368 } 369 370 ColorNode thisNode = reduceList[level]; 371 if (thisNode == null) { 372 // nothing to reduce 373 return; 374 } 375 376 // look for element with lower color count 377 ColorNode pList = thisNode; 378 int minColorCount = pList.colorCount; 379 380 int cnt = 1; 381 while (pList.nextReducible != null) { 382 if (minColorCount > pList.nextReducible.colorCount) { 383 thisNode = pList; 384 minColorCount = pList.colorCount; 385 } 386 pList = pList.nextReducible; 387 cnt++; 388 } 389 390 // save pointer to first reducible node 391 // NB: current color count for node could be changed in future 392 if (thisNode == reduceList[level]) { 393 reduceList[level] = thisNode.nextReducible; 394 } else { 395 pList = thisNode.nextReducible; // we need to process it 396 thisNode.nextReducible = pList.nextReducible; 397 thisNode = pList; 398 } 399 400 if (thisNode.isLeaf) { 401 return; 402 } 403 404 // reduce node 405 int leafChildCount = thisNode.getLeafChildCount(); 406 thisNode.isLeaf = true; 407 currSize -= (leafChildCount - 1); 408 int aDepth = thisNode.level; 409 for (int i = 0; i < 8; i++) { 410 thisNode.children[i] = freeTree(thisNode.children[i]); 411 } 412 thisNode.childCount = 0; 413 } 414 415 protected ColorNode freeTree(ColorNode aNode) { 416 if (aNode == null) { 417 return null; 418 } 419 for (int i = 0; i < 8; i++) { 420 aNode.children[i] = freeTree(aNode.children[i]); 421 } 422 423 numNodes--; 424 return null; 425 } 426 427 /** 428 * The node of color tree. 429 */ 430 protected class ColorNode { 431 public boolean isLeaf; 432 public int childCount; 433 ColorNode[] children; 434 435 public int colorCount; 436 public long red; 437 public long blue; 438 public long green; 439 440 public int paletteIndex; 441 442 public int level; 443 ColorNode nextReducible; 444 445 public ColorNode() { 446 isLeaf = false; 447 level = 0; 448 childCount = 0; 449 children = new ColorNode[8]; 450 for (int i = 0; i < 8; i++) { 451 children[i] = null; 452 } 453 454 colorCount = 0; 455 red = green = blue = 0; 456 457 paletteIndex = 0; 458 } 459 460 public int getLeafChildCount() { 461 if (isLeaf) { 462 return 0; 463 } 464 int cnt = 0; 465 for (int i = 0; i < children.length; i++) { 466 if (children[i] != null) { 467 if (children[i].isLeaf) { 468 cnt ++; 469 } else { 470 cnt += children[i].getLeafChildCount(); 471 } 472 } 473 } 474 return cnt; 475 } 476 477 public int getRGB() { 478 int r = (int)red/colorCount; 479 int g = (int)green/colorCount; 480 int b = (int)blue/colorCount; 481 482 int c = 0xff << 24 | (0xff&r) << 16 | (0xff&g) << 8 | (0xff&b); 483 return c; 484 } 485 } 486 }