KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > imageio > plugins > common > PaletteBuilder


1 /*
2  * @(#)PaletteBuilder.java 1.3 06/04/07
3  *
4  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package com.sun.imageio.plugins.common;
9
10 import java.awt.Transparency JavaDoc;
11 import java.awt.image.BufferedImage JavaDoc;
12 import java.awt.image.RenderedImage JavaDoc;
13 import java.awt.image.ColorModel JavaDoc;
14 import java.awt.image.IndexColorModel JavaDoc;
15 import java.awt.image.Raster JavaDoc;
16 import java.awt.image.WritableRaster JavaDoc;
17 import java.awt.Color JavaDoc;
18 import javax.imageio.ImageTypeSpecifier JavaDoc;
19
20
21 /**
22  * This class implements the octree quantization method
23  * as it is described in the "Graphics Gems"
24  * (ISBN 0-12-286166-3, Chapter 4, pages 297-293)
25  */

26 public class PaletteBuilder {
27
28     /**
29      * maximum of tree depth
30      */

31     protected static final int MAXLEVEL = 8;
32
33     protected RenderedImage JavaDoc src;
34     protected ColorModel JavaDoc srcColorModel;
35     protected Raster JavaDoc srcRaster;
36
37     protected int requiredSize;
38
39     protected ColorNode root;
40
41     protected int numNodes;
42     protected int maxNodes;
43     protected int currLevel;
44     protected int currSize;
45
46     protected ColorNode[] reduceList;
47     protected ColorNode[] palette;
48
49     protected int transparency;
50     protected ColorNode transColor;
51
52
53     /**
54      * Creates an image representing given image
55      * <code>src</code> using <code>IndexColorModel</code>.
56      *
57      * Lossless conversion is not always possible (e.g. if number
58      * of colors in the given image exceeds maximum palette size).
59      * Result image then is an approximation constructed by octree
60      * quantization method.
61      *
62      * @exception IllegalArgumentException if <code>src</code> is
63      * <code>null</code>.
64      *
65      * @exception UnsupportedOperationException if implemented method
66      * is unable to create approximation of <code>src</code>
67      * and <code>canCreatePalette</code> returns <code>false</code>.
68      *
69      * @see createIndexColorModel
70      *
71      * @see canCreatePalette
72      *
73      */

74     public static RenderedImage JavaDoc createIndexedImage(RenderedImage JavaDoc src) {
75         PaletteBuilder pb = new PaletteBuilder(src);
76         pb.buildPalette();
77         return pb.getIndexedImage();
78     }
79
80     /**
81      * Creates an palette representing colors from given image
82      * <code>img</code>. If number of colors in the given image exceeds
83      * maximum palette size closest colors would be merged.
84      *
85      * @exception IllegalArgumentException if <code>img</code> is
86      * <code>null</code>.
87      *
88      * @exception UnsupportedOperationException if implemented method
89      * is unable to create approximation of <code>img</code>
90      * and <code>canCreatePalette</code> returns <code>false</code>.
91      *
92      * @see createIndexedImage
93      *
94      * @see canCreatePalette
95      *
96      */

97     public static IndexColorModel JavaDoc createIndexColorModel(RenderedImage JavaDoc img) {
98         PaletteBuilder pb = new PaletteBuilder(img);
99         pb.buildPalette();
100         return pb.getIndexColorModel();
101     }
102
103     /**
104      * Returns <code>true</code> if PaletteBuilder is able to create
105      * palette for given image type.
106      *
107      * @param type an instance of <code>ImageTypeSpecifier</code> to be
108      * indexed.
109      *
110      * @return <code>true</code> if the <code>PaletteBuilder</code>
111      * is likely to be able to create palette for this image type.
112      *
113      * @exception IllegalArgumentException if <code>type</code>
114      * is <code>null</code>.
115      */

116     public static boolean canCreatePalette(ImageTypeSpecifier JavaDoc type) {
117     if (type == null) {
118         throw new IllegalArgumentException JavaDoc("type == null");
119     }
120         return true;
121     }
122
123     /**
124      * Returns <code>true</code> if PaletteBuilder is able to create
125      * palette for given rendered image.
126      *
127      * @param image an instance of <code>RenderedImage</code> to be
128      * indexed.
129      *
130      * @return <code>true</code> if the <code>PaletteBuilder</code>
131      * is likely to be able to create palette for this image type.
132      *
133      * @exception IllegalArgumentException if <code>image</code>
134      * is <code>null</code>.
135      */

136     public static boolean canCreatePalette(RenderedImage JavaDoc image) {
137     if (image == null) {
138         throw new IllegalArgumentException JavaDoc("image == null");
139     }
140     ImageTypeSpecifier JavaDoc type = new ImageTypeSpecifier JavaDoc(image);
141     return canCreatePalette(type);
142     }
143
144     protected RenderedImage JavaDoc getIndexedImage() {
145         IndexColorModel JavaDoc icm = getIndexColorModel();
146
147         BufferedImage JavaDoc dst =
148         new BufferedImage JavaDoc(src.getWidth(), src.getHeight(),
149                   BufferedImage.TYPE_BYTE_INDEXED, icm);
150
151     WritableRaster JavaDoc wr = dst.getRaster();
152     for (int y =0; y < dst.getHeight(); y++) {
153         for (int x = 0; x < dst.getWidth(); x++) {
154         Color JavaDoc aColor = getSrcColor(x,y);
155         wr.setSample(x, y, 0, findColorIndex(root, aColor));
156         }
157     }
158     
159     return dst;
160     }
161
162
163     protected PaletteBuilder(RenderedImage JavaDoc src) {
164         this(src, 256);
165     }
166
167     protected PaletteBuilder(RenderedImage JavaDoc src, int size) {
168         this.src = src;
169     this.srcColorModel = src.getColorModel();
170     this.srcRaster = src.getData();
171
172         this.transparency =
173         srcColorModel.getTransparency();
174
175         if (transparency != Transparency.OPAQUE) {
176             this.requiredSize = size - 1;
177             transColor = new ColorNode();
178             transColor.isLeaf = true;
179         } else {
180             this.requiredSize = size;
181         }
182     }
183
184     private Color JavaDoc getSrcColor(int x, int y) {
185     int argb = srcColorModel.getRGB(srcRaster.getDataElements(x, y, null));
186     return new Color JavaDoc(argb, transparency != Transparency.OPAQUE);
187     }
188
189     protected int findColorIndex(ColorNode aNode, Color JavaDoc aColor) {
190         if (transparency != Transparency.OPAQUE &&
191         aColor.getAlpha() != 0xff)
192     {
193             return 0; // default transparnt pixel
194
}
195
196         if (aNode.isLeaf) {
197             return aNode.paletteIndex;
198         } else {
199             int childIndex = getBranchIndex(aColor, aNode.level);
200     
201             return findColorIndex(aNode.children[childIndex], aColor);
202         }
203     }
204
205     protected void buildPalette() {
206         reduceList = new ColorNode[MAXLEVEL + 1];
207     for (int i = 0; i < reduceList.length; i++) {
208         reduceList[i] = null;
209     }
210     
211     numNodes = 0;
212     maxNodes = 0;
213     root = null;
214     currSize = 0;
215     currLevel = MAXLEVEL;
216
217         /*
218           from the book
219
220         */

221     
222         int w = src.getWidth();
223         int h = src.getHeight();
224     for (int y = 0; y < h; y++) {
225         for (int x = 0; x < w; x++) {
226
227                 Color JavaDoc aColor = getSrcColor(w - x - 1, h - y - 1);
228                 /*
229                  * If transparency of given image is not opaque we assume all
230                  * colors with alpha less than 1.0 as fully transparent.
231                  */

232                 if (transparency != Transparency.OPAQUE &&
233             aColor.getAlpha() != 0xff)
234         {
235                     transColor = insertNode(transColor, aColor, 0);
236                 } else {
237                     root = insertNode(root, aColor, 0);
238                 }
239                 if (currSize > requiredSize) {
240                     reduceTree();
241                 }
242         }
243         }
244     }
245
246     protected ColorNode insertNode(ColorNode aNode, Color JavaDoc aColor, int aLevel) {
247
248         if (aNode == null) {
249         aNode = new ColorNode();
250         numNodes++;
251         if (numNodes > maxNodes) {
252         maxNodes = numNodes;
253         }
254         aNode.level = aLevel;
255         aNode.isLeaf = (aLevel > MAXLEVEL);
256         if (aNode.isLeaf) {
257         currSize++;
258         }
259     }
260     aNode.colorCount++;
261     aNode.red += aColor.getRed();
262     aNode.green += aColor.getGreen();
263     aNode.blue += aColor.getBlue();
264     
265     if (!aNode.isLeaf) {
266         int branchIndex = getBranchIndex(aColor, aLevel);
267         if (aNode.children[branchIndex] == null) {
268         aNode.childCount++;
269         if (aNode.childCount == 2) {
270             aNode.nextReducible = reduceList[aLevel];
271             reduceList[aLevel] = aNode;
272         }
273         }
274         aNode.children[branchIndex] =
275         insertNode(aNode.children[branchIndex], aColor, aLevel + 1);
276     }
277     return aNode;
278     }
279
280     protected IndexColorModel JavaDoc getIndexColorModel() {
281         int size = currSize;
282         if (transparency == Transparency.BITMASK) {
283             size ++; // we need place for transparent color;
284
}
285
286         byte[] red = new byte[size];
287         byte[] green = new byte[size];
288         byte[] blue = new byte[size];
289
290         int index = 0;
291         palette = new ColorNode[size];
292         if (transparency == Transparency.BITMASK) {
293             index ++;
294         }
295
296         int lastIndex = findPaletteEntry(root, index, red, green, blue);
297
298         IndexColorModel JavaDoc icm = null;
299         if (transparency == Transparency.BITMASK) {
300             icm = new IndexColorModel JavaDoc(8, size, red, green, blue, 0);
301         } else {
302             icm = new IndexColorModel JavaDoc(8, currSize, red, green, blue);
303         }
304         return icm;
305     }
306
307     protected int findPaletteEntry(ColorNode aNode, int index,
308                    byte[] red, byte[] green, byte[] blue)
309         {
310             if (aNode.isLeaf) {
311                 red[index] = (byte)(aNode.red/aNode.colorCount);
312                 green[index] = (byte)(aNode.green/aNode.colorCount);
313                 blue[index] = (byte)(aNode.blue/aNode.colorCount);
314                 aNode.paletteIndex = index;
315
316                 palette[index] = aNode;
317
318                 index++;
319             } else {
320                 for (int i = 0; i < 8; i++) {
321                     if (aNode.children[i] != null) {
322                         index = findPaletteEntry(aNode.children[i], index,
323                                                  red, green, blue);
324                     }
325                 }
326             }
327             return index;
328         }
329
330     protected int getBranchIndex(Color JavaDoc aColor, int aLevel) {
331         if (aLevel > MAXLEVEL || aLevel < 0) {
332             throw new IllegalArgumentException JavaDoc("Invalid octree node depth: " +
333                            aLevel);
334         }
335
336         int shift = MAXLEVEL - aLevel;
337         int red_index = 0x1 & ((0xff & aColor.getRed()) >> shift);
338         int green_index = 0x1 & ((0xff & aColor.getGreen()) >> shift);
339         int blue_index = 0x1 & ((0xff & aColor.getBlue()) >> shift);
340         int index = (red_index << 2) | (green_index << 1) | blue_index;
341         return index;
342     }
343
344     protected void reduceTree() {
345         int level = reduceList.length - 1;
346     while (reduceList[level] == null && level >= 0) {
347         level--;
348     }
349
350         ColorNode thisNode = reduceList[level];
351     if (thisNode == null) {
352             // nothing to reduce
353
return;
354     }
355
356         // look for element with lower color count
357
ColorNode pList = thisNode;
358         int minColorCount = pList.colorCount;
359
360         int cnt = 1;
361         while (pList.nextReducible != null) {
362             if (minColorCount > pList.nextReducible.colorCount) {
363                 thisNode = pList;
364                 minColorCount = pList.colorCount;
365             }
366             pList = pList.nextReducible;
367             cnt++;
368         }
369
370         // save pointer to first reducible node
371
// NB: current color count for node could be changed in future
372
if (thisNode == reduceList[level]) {
373             reduceList[level] = thisNode.nextReducible;
374         } else {
375             pList = thisNode.nextReducible; // we need to process it
376
thisNode.nextReducible = pList.nextReducible;
377             thisNode = pList;
378         }
379     
380         if (thisNode.isLeaf) {
381             return;
382         }
383
384         // reduce node
385
int leafChildCount = thisNode.getLeafChildCount();
386         thisNode.isLeaf = true;
387         currSize -= (leafChildCount - 1);
388         int aDepth = thisNode.level;
389         for (int i = 0; i < 8; i++) {
390             thisNode.children[i] = freeTree(thisNode.children[i]);
391         }
392         thisNode.childCount = 0;
393     }
394
395     protected ColorNode freeTree(ColorNode aNode) {
396     if (aNode == null) {
397         return null;
398     }
399     for (int i = 0; i < 8; i++) {
400         aNode.children[i] = freeTree(aNode.children[i]);
401     }
402
403         numNodes--;
404     return null;
405     }
406
407     /**
408      * The node of color tree.
409      */

410     protected class ColorNode {
411         public boolean isLeaf;
412         public int childCount;
413         ColorNode[] children;
414
415         public int colorCount;
416         public long red;
417         public long blue;
418         public long green;
419
420         public int paletteIndex;
421
422         public int level;
423         ColorNode nextReducible;
424
425         public ColorNode() {
426             isLeaf = false;
427             level = 0;
428             childCount = 0;
429             children = new ColorNode[8];
430             for (int i = 0; i < 8; i++) {
431                 children[i] = null;
432             }
433
434             colorCount = 0;
435             red = green = blue = 0;
436
437             paletteIndex = 0;
438         }
439
440         public int getLeafChildCount() {
441             if (isLeaf) {
442                 return 0;
443             }
444             int cnt = 0;
445             for (int i = 0; i < children.length; i++) {
446                 if (children[i] != null) {
447                     if (children[i].isLeaf) {
448                         cnt ++;
449                     } else {
450                         cnt += children[i].getLeafChildCount();
451                     }
452                 }
453             }
454             return cnt;
455         }
456
457         public int getRGB() {
458             int r = (int)red/colorCount;
459             int g = (int)green/colorCount;
460             int b = (int)blue/colorCount;
461
462             int c = 0xff << 24 | (0xff&r) << 16 | (0xff&g) << 8 | (0xff&b);
463             return c;
464         }
465     }
466 }
467
Popular Tags