• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / smartsprites / #43

11 Nov 2023 07:47PM UTC coverage: 88.431%. Remained the same
#43

push

github

hazendaz
[tests] Fix tests given they expected order and now license on everything for +36

1437 of 1625 relevant lines covered (88.43%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.39
/src/main/java/amd/Quantize.java
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 amd;
38
/**
39
 * (#)Quantize.java    0.90 9/19/00 Adam Doppelt
40
 * 
41
 * An efficient color quantization algorithm, adapted from the C++
42
 * implementation quantize.c in <a
43
 * href="http://www.imagemagick.org/">ImageMagick</a>. The pixels for
44
 * an image are placed into an oct tree. The oct tree is reduced in
45
 * size, and the pixels from the original image are reassigned to the
46
 * nodes in the reduced tree.<p>
47
 *
48
 * Here is the copyright notice from ImageMagick:
49
 * 
50
 * <pre>
51
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
52
%  Permission is hereby granted, free of charge, to any person obtaining a    %
53
%  copy of this software and associated documentation files ("ImageMagick"),  %
54
%  to deal in ImageMagick without restriction, including without limitation   %
55
%  the rights to use, copy, modify, merge, publish, distribute, sublicense,   %
56
%  and/or sell copies of ImageMagick, and to permit persons to whom the       %
57
%  ImageMagick is furnished to do so, subject to the following conditions:    %
58
%                                                                             %
59
%  The above copyright notice and this permission notice shall be included in %
60
%  all copies or substantial portions of ImageMagick.                         %
61
%                                                                             %
62
%  The software is provided "as is", without warranty of any kind, express or %
63
%  implied, including but not limited to the warranties of merchantability,   %
64
%  fitness for a particular purpose and noninfringement.  In no event shall   %
65
%  E. I. du Pont de Nemours and Company be liable for any claim, damages or   %
66
%  other liability, whether in an action of contract, tort or otherwise,      %
67
%  arising from, out of or in connection with ImageMagick or the use or other %
68
%  dealings in ImageMagick.                                                   %
69
%                                                                             %
70
%  Except as contained in this notice, the name of the E. I. du Pont de       %
71
%  Nemours and Company shall not be used in advertising or otherwise to       %
72
%  promote the sale, use or other dealings in ImageMagick without prior       %
73
%  written authorization from the E. I. du Pont de Nemours and Company.       %
74
%                                                                             %
75
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76
</pre>
77
 *
78
 *
79
 * @version 0.90 19 Sep 2000
80
 * @author <a href="http://www.gurge.com/amd/">Adam Doppelt</a>
81
 */
82
public class Quantize {
83

84
/*
85
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86
%                                                                             %
87
%                                                                             %
88
%                                                                             %
89
%           QQQ   U   U   AAA   N   N  TTTTT  IIIII   ZZZZZ  EEEEE            %
90
%          Q   Q  U   U  A   A  NN  N    T      I        ZZ  E                %
91
%          Q   Q  U   U  AAAAA  N N N    T      I      ZZZ   EEEEE            %
92
%          Q  QQ  U   U  A   A  N  NN    T      I     ZZ     E                %
93
%           QQQQ   UUU   A   A  N   N    T    IIIII   ZZZZZ  EEEEE            %
94
%                                                                             %
95
%                                                                             %
96
%              Reduce the Number of Unique Colors in an Image                 %
97
%                                                                             %
98
%                                                                             %
99
%                           Software Design                                   %
100
%                             John Cristy                                     %
101
%                              July 1992                                      %
102
%                                                                             %
103
%                                                                             %
104
%  Copyright 1998 E. I. du Pont de Nemours and Company                        %
105
%                                                                             %
106
%  Permission is hereby granted, free of charge, to any person obtaining a    %
107
%  copy of this software and associated documentation files ("ImageMagick"),  %
108
%  to deal in ImageMagick without restriction, including without limitation   %
109
%  the rights to use, copy, modify, merge, publish, distribute, sublicense,   %
110
%  and/or sell copies of ImageMagick, and to permit persons to whom the       %
111
%  ImageMagick is furnished to do so, subject to the following conditions:    %
112
%                                                                             %
113
%  The above copyright notice and this permission notice shall be included in %
114
%  all copies or substantial portions of ImageMagick.                         %
115
%                                                                             %
116
%  The software is provided "as is", without warranty of any kind, express or %
117
%  implied, including but not limited to the warranties of merchantability,   %
118
%  fitness for a particular purpose and noninfringement.  In no event shall   %
119
%  E. I. du Pont de Nemours and Company be liable for any claim, damages or   %
120
%  other liability, whether in an action of contract, tort or otherwise,      %
121
%  arising from, out of or in connection with ImageMagick or the use or other %
122
%  dealings in ImageMagick.                                                   %
123
%                                                                             %
124
%  Except as contained in this notice, the name of the E. I. du Pont de       %
125
%  Nemours and Company shall not be used in advertising or otherwise to       %
126
%  promote the sale, use or other dealings in ImageMagick without prior       %
127
%  written authorization from the E. I. du Pont de Nemours and Company.       %
128
%                                                                             %
129
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
130
%
131
%  Realism in computer graphics typically requires using 24 bits/pixel to
132
%  generate an image. Yet many graphic display devices do not contain
133
%  the amount of memory necessary to match the spatial and color
134
%  resolution of the human eye. The QUANTIZE program takes a 24 bit
135
%  image and reduces the number of colors so it can be displayed on
136
%  raster device with less bits per pixel. In most instances, the
137
%  quantized image closely resembles the original reference image.
138
%
139
%  A reduction of colors in an image is also desirable for image
140
%  transmission and real-time animation.
141
%
142
%  Function Quantize takes a standard RGB or monochrome images and quantizes
143
%  them down to some fixed number of colors.
144
%
145
%  For purposes of color allocation, an image is a set of n pixels, where
146
%  each pixel is a point in RGB space. RGB space is a 3-dimensional
147
%  vector space, and each pixel, pi, is defined by an ordered triple of
148
%  red, green, and blue coordinates, (ri, gi, bi).
149
%
150
%  Each primary color component (red, green, or blue) represents an
151
%  intensity which varies linearly from 0 to a maximum value, cmax, which
152
%  corresponds to full saturation of that color. Color allocation is
153
%  defined over a domain consisting of the cube in RGB space with
154
%  opposite vertices at (0,0,0) and (cmax,cmax,cmax). QUANTIZE requires
155
%  cmax = 255.
156
%
157
%  The algorithm maps this domain onto a tree in which each node
158
%  represents a cube within that domain. In the following discussion
159
%  these cubes are defined by the coordinate of two opposite vertices:
160
%  The vertex nearest the origin in RGB space and the vertex farthest
161
%  from the origin.
162
%
163
%  The tree's root node represents the the entire domain, (0,0,0) through
164
%  (cmax,cmax,cmax). Each lower level in the tree is generated by
165
%  subdividing one node's cube into eight smaller cubes of equal size.
166
%  This corresponds to bisecting the parent cube with planes passing
167
%  through the midpoints of each edge.
168
%
169
%  The basic algorithm operates in three phases: Classification,
170
%  Reduction, and Assignment. Classification builds a color
171
%  description tree for the image. Reduction collapses the tree until
172
%  the number it represents, at most, the number of colors desired in the
173
%  output image. Assignment defines the output image's color map and
174
%  sets each pixel's color by reclassification in the reduced tree.
175
%  Our goal is to minimize the numerical discrepancies between the original
176
%  colors and quantized colors (quantization error).
177
%
178
%  Classification begins by initializing a color description tree of
179
%  sufficient depth to represent each possible input color in a leaf.
180
%  However, it is impractical to generate a fully-formed color
181
%  description tree in the classification phase for realistic values of
182
%  cmax. If colors components in the input image are quantized to k-bit
183
%  precision, so that cmax= 2k-1, the tree would need k levels below the
184
%  root node to allow representing each possible input color in a leaf.
185
%  This becomes prohibitive because the tree's total number of nodes is
186
%  1 + sum(i=1,k,8k).
187
%
188
%  A complete tree would require 19,173,961 nodes for k = 8, cmax = 255.
189
%  Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
190
%  Initializes data structures for nodes only as they are needed;  (2)
191
%  Chooses a maximum depth for the tree as a function of the desired
192
%  number of colors in the output image (currently log2(colormap size)).
193
%
194
%  For each pixel in the input image, classification scans downward from
195
%  the root of the color description tree. At each level of the tree it
196
%  identifies the single node which represents a cube in RGB space
197
%  containing the pixel's color. It updates the following data for each
198
%  such node:
199
%
200
%    n1: Number of pixels whose color is contained in the RGB cube
201
%    which this node represents;
202
%
203
%    n2: Number of pixels whose color is not represented in a node at
204
%    lower depth in the tree;  initially,  n2 = 0 for all nodes except
205
%    leaves of the tree.
206
%
207
%    Sr, Sg, Sb: Sums of the red, green, and blue component values for
208
%    all pixels not classified at a lower depth. The combination of
209
%    these sums and n2  will ultimately characterize the mean color of a
210
%    set of pixels represented by this node.
211
%
212
%    E: The distance squared in RGB space between each pixel contained
213
%    within a node and the nodes' center. This represents the quantization
214
%    error for a node.
215
%
216
%  Reduction repeatedly prunes the tree until the number of nodes with
217
%  n2 > 0 is less than or equal to the maximum number of colors allowed
218
%  in the output image. On any given iteration over the tree, it selects
219
%  those nodes whose E count is minimal for pruning and merges their
220
%  color statistics upward. It uses a pruning threshold, Ep, to govern
221
%  node selection as follows:
222
%
223
%    Ep = 0
224
%    while number of nodes with (n2 > 0) > required maximum number of colors
225
%      prune all nodes such that E <= Ep
226
%      Set Ep to minimum E in remaining nodes
227
%
228
%  This has the effect of minimizing any quantization error when merging
229
%  two nodes together.
230
%
231
%  When a node to be pruned has offspring, the pruning procedure invokes
232
%  itself recursively in order to prune the tree from the leaves upward.
233
%  n2,  Sr, Sg,  and  Sb in a node being pruned are always added to the
234
%  corresponding data in that node's parent. This retains the pruned
235
%  node's color characteristics for later averaging.
236
%
237
%  For each node, n2 pixels exist for which that node represents the
238
%  smallest volume in RGB space containing those pixel's colors. When n2
239
%  > 0 the node will uniquely define a color in the output image. At the
240
%  beginning of reduction,  n2 = 0  for all nodes except a the leaves of
241
%  the tree which represent colors present in the input image.
242
%
243
%  The other pixel count, n1, indicates the total number of colors
244
%  within the cubic volume which the node represents. This includes n1 -
245
%  n2  pixels whose colors should be defined by nodes at a lower level in
246
%  the tree.
247
%
248
%  Assignment generates the output image from the pruned tree. The
249
%  output image consists of two parts: (1)  A color map, which is an
250
%  array of color descriptions (RGB triples) for each color present in
251
%  the output image;  (2)  A pixel array, which represents each pixel as
252
%  an index into the color map array.
253
%
254
%  First, the assignment phase makes one pass over the pruned color
255
%  description tree to establish the image's color map. For each node
256
%  with n2  > 0, it divides Sr, Sg, and Sb by n2 . This produces the
257
%  mean color of all pixels that classify no lower than this node. Each
258
%  of these colors becomes an entry in the color map.
259
%
260
%  Finally,  the assignment phase reclassifies each pixel in the pruned
261
%  tree to identify the deepest node containing the pixel's color. The
262
%  pixel's value in the pixel array becomes the index of this node's mean
263
%  color in the color map.
264
%
265
%  With the permission of USC Information Sciences Institute, 4676 Admiralty
266
%  Way, Marina del Rey, California  90292, this code was adapted from module
267
%  ALCOLS written by Paul Raveling.
268
%
269
%  The names of ISI and USC are not used in advertising or publicity
270
%  pertaining to distribution of the software without prior specific
271
%  written permission from ISI.
272
%
273
*/
274
    
275
    static final boolean QUICK = false;
276
    
277
    static final int MAX_RGB = 255;
278
    static final int MAX_NODES = 266817;
279
    static final int MAX_TREE_DEPTH = 8;
280

281
    // these are precomputed in advance
282
    static int[] SQUARES;
283
    static int[] SHIFT;
284

285
    static {
286
        SQUARES = new int[MAX_RGB + MAX_RGB + 1];
1✔
287
        for (int i= -MAX_RGB; i <= MAX_RGB; i++) {
1✔
288
            SQUARES[i + MAX_RGB] = i * i;
1✔
289
        }
290

291
        SHIFT = new int[MAX_TREE_DEPTH + 1];
1✔
292
        for (int i = 0; i < MAX_TREE_DEPTH + 1; ++i) {
1✔
293
            SHIFT[i] = 1 << (15 - i);
1✔
294
        }
295
    }
1✔
296

297
    private Quantize()
298
    {
299
        // Prevent Instantiation
300
    }
301

302
    /**
303
     * Reduce the image to the given number of colors. The pixels are
304
     * reduced in place.
305
     * @return The new color palette.
306
     */
307
    public static int[] quantizeImage(int[][] pixels, int maxColors) {
308
        Cube cube = new Cube(pixels, maxColors);
1✔
309
        cube.classification();
1✔
310
        cube.reduction();
1✔
311
        cube.assignment();
1✔
312
        return cube.colormap;
1✔
313
    }
314
    
315
    static class Cube {
316
        int[][] pixels;
317
        int maxColors;
318
        int[] colormap;
319
        
320
        Node root;
321
        int depth;
322

323
        // counter for the number of colors in the cube. this gets
324
        // recalculated often.
325
        int colors;
326

327
        // counter for the number of nodes in the tree
328
        int nodes;
329

330
        Cube(int[][] pixels, int maxColors) {
1✔
331
            this.pixels = pixels;
1✔
332
            this.maxColors = maxColors;
1✔
333

334
            int i = maxColors;
1✔
335
            // tree_depth = log max_colors
336
            //                 4
337
            for (depth = 1; i != 0; depth++) {
1✔
338
                i /= 4;
1✔
339
            }
340
            if (depth > 1) {
1✔
341
                --depth;
1✔
342
            }
343
            if (depth > MAX_TREE_DEPTH) {
1✔
344
                depth = MAX_TREE_DEPTH;
×
345
            } else if (depth < 2) {
1✔
346
                depth = 2;
×
347
            }
348
            
349
            root = new Node(this);
1✔
350
        }
1✔
351

352
        /**
353
         * Procedure Classification begins by initializing a color
354
         * description tree of sufficient depth to represent each
355
         * possible input color in a leaf. However, it is impractical
356
         * to generate a fully-formed color description tree in the
357
         * classification phase for realistic values of cmax. If
358
         * colors components in the input image are quantized to k-bit
359
         * precision, so that cmax= 2k-1, the tree would need k levels
360
         * below the root node to allow representing each possible
361
         * input color in a leaf. This becomes prohibitive because the
362
         * tree's total number of nodes is 1 + sum(i=1,k,8k).
363
         *
364
         * A complete tree would require 19,173,961 nodes for k = 8,
365
         * cmax = 255. Therefore, to avoid building a fully populated
366
         * tree, QUANTIZE: (1) Initializes data structures for nodes
367
         * only as they are needed; (2) Chooses a maximum depth for
368
         * the tree as a function of the desired number of colors in
369
         * the output image (currently log2(colormap size)).
370
         *
371
         * For each pixel in the input image, classification scans
372
         * downward from the root of the color description tree. At
373
         * each level of the tree it identifies the single node which
374
         * represents a cube in RGB space containing It updates the
375
         * following data for each such node:
376
         *
377
         *   number_pixels : Number of pixels whose color is contained
378
         *   in the RGB cube which this node represents;
379
         *
380
         *   unique : Number of pixels whose color is not represented
381
         *   in a node at lower depth in the tree; initially, n2 = 0
382
         *   for all nodes except leaves of the tree.
383
         *
384
         *   total_red/green/blue : Sums of the red, green, and blue
385
         *   component values for all pixels not classified at a lower
386
         *   depth. The combination of these sums and n2 will
387
         *   ultimately characterize the mean color of a set of pixels
388
         *   represented by this node.
389
         */
390
        void classification() {
391
            int[][] pixels = this.pixels;
1✔
392

393
            int width = pixels.length;
1✔
394
            int height = pixels[0].length;
1✔
395

396
            // convert to indexed color
397
            for (int x = width; x-- > 0; ) {
1✔
398
                for (int y = height; y-- > 0; ) {
1✔
399
                    int pixel = pixels[x][y];
1✔
400
                    int red   = (pixel >> 16) & 0xFF;
1✔
401
                    int green = (pixel >>  8) & 0xFF;
1✔
402
                    int blue  = (pixel >>  0) & 0xFF;
1✔
403

404
                    // a hard limit on the number of nodes in the tree
405
                    if (nodes > MAX_NODES) {
1✔
406
                        System.out.println("pruning");
×
407
                        root.pruneLevel();
×
408
                        --depth;
×
409
                    }
410

411
                    // walk the tree to depth, increasing the
412
                    // number_pixels count for each node
413
                    Node node = root;
1✔
414
                    for (int level = 1; level <= depth; ++level) {
1✔
415
                        int id = (((red   > node.midRed   ? 1 : 0) << 0) |
1✔
416
                                  ((green > node.midGreen ? 1 : 0) << 1) |
1✔
417
                                  ((blue  > node.midBlue  ? 1 : 0) << 2));
1✔
418
                        if (node.child[id] == null) {
1✔
419
                            new Node(node, id, level);
1✔
420
                        }
421
                        node = node.child[id];
1✔
422
                        node.numberPixels += SHIFT[level];
1✔
423
                    }
424

425
                    ++node.unique;
1✔
426
                    node.totalRed   += red;
1✔
427
                    node.totalGreen += green;
1✔
428
                    node.totalBlue  += blue;
1✔
429
                }
1✔
430
            }
431
        }
1✔
432

433
        /*
434
         * reduction repeatedly prunes the tree until the number of
435
         * nodes with unique > 0 is less than or equal to the maximum
436
         * number of colors allowed in the output image.
437
         *
438
         * When a node to be pruned has offspring, the pruning
439
         * procedure invokes itself recursively in order to prune the
440
         * tree from the leaves upward.  The statistics of the node
441
         * being pruned are always added to the corresponding data in
442
         * that node's parent.  This retains the pruned node's color
443
         * characteristics for later averaging.
444
         */
445
        void reduction() {
446
            int threshold = 1;
1✔
447
            while (colors > maxColors) {
1✔
448
                colors = 0;
1✔
449
                threshold = root.reduce(threshold, Integer.MAX_VALUE);
1✔
450
            }
451
        }
1✔
452

453
        /**
454
         * The result of a closest color search.
455
         */
456
        static class Search {
1✔
457
            int distance;
458
            int colorNumber;
459
        }
460

461
        /*
462
         * Procedure assignment generates the output image from the
463
         * pruned tree. The output image consists of two parts: (1) A
464
         * color map, which is an array of color descriptions (RGB
465
         * triples) for each color present in the output image; (2) A
466
         * pixel array, which represents each pixel as an index into
467
         * the color map array.
468
         *
469
         * First, the assignment phase makes one pass over the pruned
470
         * color description tree to establish the image's color map.
471
         * For each node with n2 > 0, it divides Sr, Sg, and Sb by n2.
472
         * This produces the mean color of all pixels that classify no
473
         * lower than this node. Each of these colors becomes an entry
474
         * in the color map.
475
         *
476
         * Finally, the assignment phase reclassifies each pixel in
477
         * the pruned tree to identify the deepest node containing the
478
         * pixel's color. The pixel's value in the pixel array becomes
479
         * the index of this node's mean color in the color map.
480
         */
481
        void assignment() {
482
            colormap = new int[colors];
1✔
483

484
            colors = 0;
1✔
485
            root.colormap();
1✔
486
  
487
            int[][] pixels = this.pixels;
1✔
488

489
            int width = pixels.length;
1✔
490
            int height = pixels[0].length;
1✔
491

492
            Search search = new Search();
1✔
493
            
494
            // convert to indexed color
495
            for (int x = width; x-- > 0; ) {
1✔
496
                for (int y = height; y-- > 0; ) {
1✔
497
                    int pixel = pixels[x][y];
1✔
498
                    int red   = (pixel >> 16) & 0xFF;
1✔
499
                    int green = (pixel >>  8) & 0xFF;
1✔
500
                    int blue  = (pixel >>  0) & 0xFF;
1✔
501

502
                    // walk the tree to find the cube containing that color
503
                    Node node = root;
1✔
504
                    for ( ; ; ) {
505
                        int id = (((red   > node.midRed   ? 1 : 0) << 0) |
1✔
506
                                  ((green > node.midGreen ? 1 : 0) << 1) |
1✔
507
                                  ((blue  > node.midBlue  ? 1 : 0) << 2)  );
1✔
508
                        if (node.child[id] == null) {
1✔
509
                            break;
1✔
510
                        }
511
                        node = node.child[id];
1✔
512
                    }
1✔
513

514
                    if (QUICK) {
515
                        // if QUICK is set, just use that
516
                        // node. Strictly speaking, this isn't
517
                        // necessarily best match.
518
                        pixels[x][y] = node.colorNumber;
519
                    } else {
520
                        // Find the closest color.
521
                        search.distance = Integer.MAX_VALUE;
1✔
522
                        node.parent.closestColor(red, green, blue, search);
1✔
523
                        pixels[x][y] = search.colorNumber;
1✔
524
                    }
525
                }
1✔
526
            }
527
        }
1✔
528

529
        /**
530
         * A single Node in the tree.
531
         */
532
        static class Node {
533
            Cube cube;
534

535
            // parent node
536
            Node parent;
537

538
            // child nodes
539
            Node[] child;
540
            int nchild;
541

542
            // our index within our parent
543
            int id;
544
            // our level within the tree
545
            int level;
546
            // our color midpoint
547
            int midRed;
548
            int midGreen;
549
            int midBlue;
550

551
            // the pixel count for this node and all children
552
            int numberPixels;
553
            
554
            // the pixel count for this node
555
            int unique;
556
            // the sum of all pixels contained in this node
557
            int totalRed;
558
            int totalGreen;
559
            int totalBlue;
560

561
            // used to build the colormap
562
            int colorNumber;
563

564
            Node(Cube cube) {
1✔
565
                this.cube = cube;
1✔
566
                this.parent = this;
1✔
567
                this.child = new Node[8];
1✔
568
                this.id = 0;
1✔
569
                this.level = 0;
1✔
570

571
                this.numberPixels = Integer.MAX_VALUE;
1✔
572
            
573
                this.midRed   = (MAX_RGB + 1) >> 1;
1✔
574
                this.midGreen = (MAX_RGB + 1) >> 1;
1✔
575
                this.midBlue  = (MAX_RGB + 1) >> 1;
1✔
576
            }
1✔
577
        
578
            Node(Node parent, int id, int level) {
1✔
579
                this.cube = parent.cube;
1✔
580
                this.parent = parent;
1✔
581
                this.child = new Node[8];
1✔
582
                this.id = id;
1✔
583
                this.level = level;
1✔
584

585
                // add to the cube
586
                ++cube.nodes;
1✔
587
                if (level == cube.depth) {
1✔
588
                    ++cube.colors;
1✔
589
                }
590

591
                // add to the parent
592
                ++parent.nchild;
1✔
593
                parent.child[id] = this;
1✔
594

595
                // figure out our midpoint
596
                int bi = (1 << (MAX_TREE_DEPTH - level)) >> 1;
1✔
597
                midRed   = parent.midRed   + ((id & 1) > 0 ? bi : -bi);
1✔
598
                midGreen = parent.midGreen + ((id & 2) > 0 ? bi : -bi);
1✔
599
                midBlue  = parent.midBlue  + ((id & 4) > 0 ? bi : -bi);
1✔
600
            }
1✔
601

602
            /**
603
             * Remove this child node, and make sure our parent
604
             * absorbs our pixel statistics.
605
             */
606
            void pruneChild() {
607
                --parent.nchild;
1✔
608
                parent.unique += unique;
1✔
609
                parent.totalRed     += totalRed;
1✔
610
                parent.totalGreen   += totalGreen;
1✔
611
                parent.totalBlue    += totalBlue;
1✔
612
                parent.child[id] = null;
1✔
613
                --cube.nodes;
1✔
614
                cube = null;
1✔
615
                parent = null;
1✔
616
            }
1✔
617

618
            /**
619
             * Prune the lowest layer of the tree.
620
             */
621
            void pruneLevel() {
622
                if (nchild != 0) {
×
623
                    for (int i = 0; i < 8; i++) {
×
624
                        if (child[i] != null) {
×
625
                            child[i].pruneLevel();
×
626
                        }
627
                    }
628
                }
629
                if (level == cube.depth) {
×
630
                    pruneChild();
×
631
                }
632
            }
×
633

634
            /**
635
             * Remove any nodes that have fewer than threshold
636
             * pixels. Also, as long as we're walking the tree:
637
             *
638
             *  - figure out the color with the fewest pixels
639
             *  - recalculate the total number of colors in the tree
640
             */
641
            int reduce(int threshold, int nextThreshold) {
642
                if (nchild != 0) {
1✔
643
                    for (int i = 0; i < 8; i++) {
1✔
644
                        if (child[i] != null) {
1✔
645
                            nextThreshold = child[i].reduce(threshold, nextThreshold);
1✔
646
                        }
647
                    }
648
                }
649
                if (numberPixels <= threshold) {
1✔
650
                    pruneChild();
1✔
651
                } else {
652
                    if (unique != 0) {
1✔
653
                        cube.colors++;
1✔
654
                    }
655
                    if (numberPixels < nextThreshold) {
1✔
656
                        nextThreshold = numberPixels;
1✔
657
                    }
658
                }
659
                return nextThreshold;
1✔
660
            }
661

662
            /*
663
             * colormap traverses the color cube tree and notes each
664
             * colormap entry. A colormap entry is any node in the
665
             * color cube tree where the number of unique colors is
666
             * not zero.
667
             */
668
            void colormap() {
669
                if (nchild != 0) {
1✔
670
                    for (int i = 0; i < 8; i++) {
1✔
671
                        if (child[i] != null) {
1✔
672
                            child[i].colormap();
1✔
673
                        }
674
                    }
675
                }
676
                if (unique != 0) {
1✔
677
                    int r = ((totalRed   + (unique >> 1)) / unique);
1✔
678
                    int g = ((totalGreen + (unique >> 1)) / unique);
1✔
679
                    int b = ((totalBlue  + (unique >> 1)) / unique);
1✔
680
                    cube.colormap[cube.colors] = (((    0xFF) << 24) |
1✔
681
                                                  ((r & 0xFF) << 16) |
682
                                                  ((g & 0xFF) <<  8) |
683
                                                  ((b & 0xFF) <<  0));
684
                    colorNumber = cube.colors++;
1✔
685
                }
686
            }
1✔
687

688
            /* ClosestColor traverses the color cube tree at a
689
             * particular node and determines which colormap entry
690
             * best represents the input color.
691
             */
692
            void closestColor(int red, int green, int blue, Search search) {
693
                if (nchild != 0) {
1✔
694
                    for (int i = 0; i < 8; i++) {
1✔
695
                        if (child[i] != null) {
1✔
696
                            child[i].closestColor(red, green, blue, search);
1✔
697
                        }
698
                    }
699
                }
700

701
                if (unique != 0) {
1✔
702
                    int color = cube.colormap[colorNumber];
1✔
703
                    int distance = distance(color, red, green, blue);
1✔
704
                    if (distance < search.distance) {
1✔
705
                        search.distance = distance;
1✔
706
                        search.colorNumber = colorNumber;
1✔
707
                    }
708
                }
709
            }
1✔
710

711
            /**
712
             * Figure out the distance between this node and som color.
713
             */
714
            static final int distance(int color, int r, int g, int b) {
715
                return (SQUARES[((color >> 16) & 0xFF) - r + MAX_RGB] +
1✔
716
                        SQUARES[((color >>  8) & 0xFF) - g + MAX_RGB] +
717
                        SQUARES[((color >>  0) & 0xFF) - b + MAX_RGB]);
718
            }
719

720
            public String toString() {
721
                StringBuilder buf = new StringBuilder();
×
722
                if (parent == this) {
×
723
                    buf.append("root");
×
724
                } else {
725
                    buf.append("node");
×
726
                }
727
                buf.append(' ');
×
728
                buf.append(level);
×
729
                buf.append(" [");
×
730
                buf.append(midRed);
×
731
                buf.append(',');
×
732
                buf.append(midGreen);
×
733
                buf.append(',');
×
734
                buf.append(midBlue);
×
735
                buf.append(']');
×
736
                return new String(buf);
×
737
            }
738
        }
739
    }
740
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc