• 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

97.62
/src/main/java/org/carrot2/util/ColorQuantizer.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 org.carrot2.util;
38

39
import java.awt.Color;
40
import java.awt.image.*;
41

42
import amd.Quantize;
43

44
/**
45
 * A simple utility wrapping the {@link Quantize} class to work on {@link BufferedImage}s
46
 * and handle transparency.
47
 */
48
public class ColorQuantizer
49
{
50
    /** Maximum number of colors in an indexed image, leaving one for transparency */
51
    public static final int MAX_INDEXED_COLORS = 255;
52

53
    private ColorQuantizer()
54
    {
55
        // Prevent Instantiation
56
    }
57

58
    /**
59
     * Quantizes the image to {@link #MAX_INDEXED_COLORS} with white matte for areas with
60
     * partial transparency (full transparency will be preserved).
61
     * 
62
     * @return {@link BufferedImage} with type {@link BufferedImage#TYPE_BYTE_INDEXED} and
63
     *         quantized colors
64
     */
65
    public static BufferedImage quantize(BufferedImage source)
66
    {
67
        return quantize(source, Color.WHITE);
1✔
68
    }
69

70
    /**
71
     * Quantizes the image to {@link #MAX_INDEXED_COLORS} with the provided matte
72
     * {@link Color} for areas with partial transparency (full transparency will be
73
     * preserved).
74
     * 
75
     * @return {@link BufferedImage} with type {@link BufferedImage#TYPE_BYTE_INDEXED} and
76
     *         quantized colors
77
     */
78
    public static BufferedImage quantize(BufferedImage source, Color matteColor)
79
    {
80
        return quantize(source, matteColor, MAX_INDEXED_COLORS);
1✔
81
    }
82

83
    /**
84
     * Quantizes the image to the provided number of colors with the provided matte
85
     * {@link Color} for areas with partial transparency (full transparency will be
86
     * preserved).
87
     * 
88
     * @return {@link BufferedImage} with type {@link BufferedImage#TYPE_BYTE_INDEXED} and
89
     *         quantized colors
90
     */
91
    public static BufferedImage quantize(BufferedImage source, Color matteColor,
92
        int maxColors)
93
    {
94
        final int width = source.getWidth();
1✔
95
        final int height = source.getHeight();
1✔
96

97
        // First put the matte color so that we have a sensible result
98
        // for images with full alpha transparencies
99
        final BufferedImage mattedSource = BufferedImageUtils.matte(source, matteColor);
1✔
100

101
        // Get two copies of RGB data (quantization will overwrite one)
102
        final int [][] bitmap = BufferedImageUtils.getRgb(mattedSource);
1✔
103

104
        // Quantize colors and shift palette by one for transparency color
105
        // We'll keep transparency color black for now.
106
        final int [] colors = Quantize.quantizeImage(bitmap, maxColors);
1✔
107
        final int [] colorsWithAlpha = new int [colors.length + 1];
1✔
108
        System.arraycopy(colors, 0, colorsWithAlpha, 1, colors.length);
1✔
109
        colorsWithAlpha[0] = matteColor.getRGB();
1✔
110
        final IndexColorModel colorModel = new IndexColorModel(8, colorsWithAlpha.length,
1✔
111
            colorsWithAlpha, 0, false, 0, DataBuffer.TYPE_BYTE);
112

113
        // Write the results to an indexed image, skipping the fully transparent bits
114
        final BufferedImage quantized = new BufferedImage(width, height,
1✔
115
            BufferedImage.TYPE_BYTE_INDEXED, colorModel);
116
        final WritableRaster raster = quantized.getRaster();
1✔
117
        final int [][] rgb = BufferedImageUtils.getRgb(source);
1✔
118
        for (int x = 0; x < width; x++)
1✔
119
        {
120
            for (int y = 0; y < height; y++)
1✔
121
            {
122
                final int value = (rgb[x][y] & 0xff000000) != 0x00000000 ? bitmap[x][y] + 1
1✔
123
                    : 0;
1✔
124
                raster.setPixel(x, y, new int []
1✔
125
                {
126
                    value
127
                });
128
            }
129
        }
130

131
        return quantized;
1✔
132
    }
133

134
    /**
135
     * Reduces a direct color buffered image to an indexed color one without quality loss.
136
     * To make sure no quality loss will occur, check the results of the
137
     * {@link #getColorReductionInfo(BufferedImage)} method call.
138
     * 
139
     * @throws IllegalArgumentException if the application of this method would result in
140
     *             image quality loss
141
     */
142
    public static BufferedImage reduce(BufferedImage source)
143
    {
144
        final int width = source.getWidth();
1✔
145
        final int height = source.getHeight();
1✔
146

147
        if (BufferedImageUtils.hasPartialTransparency(source))
1✔
148
        {
149
            throw new IllegalArgumentException(
×
150
                "The source image cannot contain translucent areas");
151
        }
152

153
        final int [] colorsWithAlpha = BufferedImageUtils.getDistinctColors(source, 1);
1✔
154
        if (colorsWithAlpha.length - 1 > MAX_INDEXED_COLORS)
1✔
155
        {
156
            throw new IllegalArgumentException(
1✔
157
                "The source image cannot contain more than " + MAX_INDEXED_COLORS
158
                    + " colors");
159
        }
160

161
        final IndexColorModel colorModel = new IndexColorModel(8, colorsWithAlpha.length,
1✔
162
            colorsWithAlpha, 0, false, 0, DataBuffer.TYPE_BYTE);
163

164
        // Write the results to an indexed image, skipping the fully transparent bits
165
        final BufferedImage quantized = new BufferedImage(width, height,
1✔
166
            BufferedImage.TYPE_BYTE_INDEXED, colorModel);
167
        final int [][] rgb = BufferedImageUtils.getRgb(source);
1✔
168

169
        for (int x = 0; x < width; x++)
1✔
170
        {
171
            for (int y = 0; y < height; y++)
1✔
172
            {
173
                if ((rgb[x][y] & 0xff000000) != 0x00000000)
1✔
174
                {
175
                    quantized.setRGB(x, y, source.getRGB(x, y));
1✔
176
                }
177
            }
178
        }
179

180
        return quantized;
1✔
181
    }
182

183
    /**
184
     * Returns a {@link ColorReductionInfo} for the provided image.
185
     */
186
    public static ColorReductionInfo getColorReductionInfo(BufferedImage source)
187
    {
188
        return new ColorReductionInfo(BufferedImageUtils.hasPartialTransparency(source),
1✔
189
            BufferedImageUtils.countDistinctColors(source));
1✔
190
    }
191

192
    /**
193
     * Indicates how many distinct colors an image has, whether it has partial transparency
194
     * (alpha channel).
195
     */
196
    public static class ColorReductionInfo
197
    {
198
        /** Number of distinct colors in the image */
199
        public int distinctColors;
200

201
        /** True if the image has partially transparent areas (alpha channel) */
202
        public boolean hasPartialTransparency;
203

204
        public ColorReductionInfo(boolean hasPartialTransparency, int distinctColors)
205
        {
1✔
206
            this.hasPartialTransparency = hasPartialTransparency;
1✔
207
            this.distinctColors = distinctColors;
1✔
208
        }
1✔
209

210
        /**
211
         * Returns true if the image can be saved in a 8-bit indexed color format with
212
         * 1-bit transparency without quality loss.
213
         */
214
        public boolean canReduceWithoutQualityLoss()
215
        {
216
            return !hasPartialTransparency && distinctColors <= MAX_INDEXED_COLORS;
1✔
217
        }
218
    }
219
}
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