/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.imaging.palette;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.imaging.ImageWriteException;
import org.apache.commons.imaging.util.Debug;
public class MedianCutQuantizer {
private final boolean ignoreAlpha;
public MedianCutQuantizer(final boolean ignore_alpha) {
this.ignoreAlpha = ignore_alpha;
}
private Map<Integer, ColorCount> groupColors1(final BufferedImage image, final int max,
final int mask) {
final Map<Integer, ColorCount> color_map = new HashMap<Integer, ColorCount>();
final int width = image.getWidth();
final int height = image.getHeight();
final int row[] = new int[width];
for (int y = 0; y < height; y++) {
image.getRGB(0, y, width, 1, row, 0, width);
for (int x = 0; x < width; x++) {
int argb = row[x];
if (ignoreAlpha) {
argb &= 0xffffff;
}
argb &= mask;
ColorCount color = color_map.get(argb);
if (color == null) {
color = new ColorCount(argb);
color_map.put(argb, color);
if (color_map.keySet().size() > max) {
return null;
}
}
color.count++;
}
}
return color_map;
}
public Map<Integer, ColorCount> groupColors(final BufferedImage image,
final int max_colors) {
final int max = Integer.MAX_VALUE;
for (int i = 0; i < 8; i++) {
int mask = 0xff & (0xff << i);
mask = mask | (mask << 8) | (mask << 16) | (mask << 24);
Debug.debug("mask(" + i + ")",
mask + " (" + Integer.toHexString(mask) + ")");
final Map<Integer, ColorCount> result = groupColors1(image, max, mask);
if (result != null) {
return result;
}
}
throw new Error("");
}
public Palette process(final BufferedImage image, final int max_colors,
final MedianCutImplementation medianCutImplementation, final boolean verbose)
throws ImageWriteException {
final Map<Integer, ColorCount> color_map = groupColors(image, max_colors);
final int discrete_colors = color_map.keySet().size();
if (discrete_colors <= max_colors) {
if (verbose) {
Debug.debug("lossless palette: " + discrete_colors);
}
final int palette[] = new int[discrete_colors];
final List<ColorCount> color_counts = new ArrayList<ColorCount>(
color_map.values());
for (int i = 0; i < color_counts.size(); i++) {
final ColorCount color_count = color_counts.get(i);
palette[i] = color_count.argb;
if (ignoreAlpha) {
palette[i] |= 0xff000000;
}
}
return new SimplePalette(palette);
}
if (verbose) {
Debug.debug("discrete colors: " + discrete_colors);
}
final List<ColorGroup> color_groups = new ArrayList<ColorGroup>();
final ColorGroup root = new ColorGroup(new ArrayList<ColorCount>(
color_map.values()), ignoreAlpha);
color_groups.add(root);
while (color_groups.size() < max_colors) {
if (!medianCutImplementation.performNextMedianCut(color_groups, ignoreAlpha)) {
break;
}
}
final int palette_size = color_groups.size();
if (verbose) {
Debug.debug("palette size: " + palette_size);
}
final int palette[] = new int[palette_size];
for (int i = 0; i < color_groups.size(); i++) {
final ColorGroup color_group = color_groups.get(i);
palette[i] = color_group.getMedianValue();
color_group.palette_index = i;
if (color_group.color_counts.size() < 1) {
throw new ImageWriteException("empty color_group: "
+ color_group);
}
}
if (palette_size > discrete_colors) {
throw new ImageWriteException("palette_size>discrete_colors");
}
return new MedianCutPalette(root, palette);
}
}