001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.palette; 018 019import java.awt.image.BufferedImage; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.commons.imaging.ImageWriteException; 026import org.apache.commons.imaging.internal.Debug; 027 028public class MedianCutQuantizer { 029 private final boolean ignoreAlpha; 030 031 public MedianCutQuantizer(final boolean ignoreAlpha) { 032 this.ignoreAlpha = ignoreAlpha; 033 } 034 035 private Map<Integer, ColorCount> groupColors1(final BufferedImage image, final int max, 036 final int mask) { 037 final Map<Integer, ColorCount> colorMap = new HashMap<>(); 038 039 final int width = image.getWidth(); 040 final int height = image.getHeight(); 041 042 final int[] row = new int[width]; 043 for (int y = 0; y < height; y++) { 044 image.getRGB(0, y, width, 1, row, 0, width); 045 for (int x = 0; x < width; x++) { 046 int argb = row[x]; 047 048 if (ignoreAlpha) { 049 argb &= 0xffffff; 050 } 051 argb &= mask; 052 053 ColorCount color = colorMap.get(argb); 054 if (color == null) { 055 color = new ColorCount(argb); 056 colorMap.put(argb, color); 057 if (colorMap.size() > max) { 058 return null; 059 } 060 } 061 color.count++; 062 } 063 } 064 065 return colorMap; 066 } 067 068 public Map<Integer, ColorCount> groupColors(final BufferedImage image, final int maxColors) { 069 final int max = Integer.MAX_VALUE; 070 071 for (int i = 0; i < 8; i++) { 072 int mask = 0xff & (0xff << i); 073 mask = mask | (mask << 8) | (mask << 16) | (mask << 24); 074 075 Debug.debug("mask(" + i + "): " + mask + " (" + Integer.toHexString(mask) + ")"); 076 077 final Map<Integer, ColorCount> result = groupColors1(image, max, mask); 078 if (result != null) { 079 return result; 080 } 081 } 082 throw new Error(""); 083 } 084 085 public Palette process(final BufferedImage image, final int maxColors, 086 final MedianCut medianCut) 087 throws ImageWriteException { 088 final Map<Integer, ColorCount> colorMap = groupColors(image, maxColors); 089 090 final int discreteColors = colorMap.size(); 091 if (discreteColors <= maxColors) { 092 Debug.debug("lossless palette: " + discreteColors); 093 094 final int[] palette = new int[discreteColors]; 095 final List<ColorCount> colorCounts = new ArrayList<>( 096 colorMap.values()); 097 098 for (int i = 0; i < colorCounts.size(); i++) { 099 final ColorCount colorCount = colorCounts.get(i); 100 palette[i] = colorCount.argb; 101 if (ignoreAlpha) { 102 palette[i] |= 0xff000000; 103 } 104 } 105 106 return new SimplePalette(palette); 107 } 108 109 Debug.debug("discrete colors: " + discreteColors); 110 111 final List<ColorGroup> colorGroups = new ArrayList<>(); 112 final ColorGroup root = new ColorGroup(new ArrayList<>(colorMap.values()), ignoreAlpha); 113 colorGroups.add(root); 114 115 while (colorGroups.size() < maxColors) { 116 if (!medianCut.performNextMedianCut(colorGroups, ignoreAlpha)) { 117 break; 118 } 119 } 120 121 final int paletteSize = colorGroups.size(); 122 Debug.debug("palette size: " + paletteSize); 123 124 final int[] palette = new int[paletteSize]; 125 126 for (int i = 0; i < colorGroups.size(); i++) { 127 final ColorGroup colorGroup = colorGroups.get(i); 128 129 palette[i] = colorGroup.getMedianValue(); 130 131 colorGroup.paletteIndex = i; 132 133 if (colorGroup.getColorCounts().isEmpty()) { 134 throw new ImageWriteException("empty color_group: " 135 + colorGroup); 136 } 137 } 138 139 if (paletteSize > discreteColors) { 140 throw new ImageWriteException("palette_size > discrete_colors"); 141 } 142 143 return new MedianCutPalette(root, palette); 144 } 145}