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.util.ArrayList; 020import java.util.List; 021 022import org.apache.commons.imaging.ImageWriteException; 023 024public class MostPopulatedBoxesMedianCut implements MedianCut { 025 026 @Override 027 public boolean performNextMedianCut(final List<ColorGroup> colorGroups, 028 final boolean ignoreAlpha) throws ImageWriteException { 029 int maxPoints = 0; 030 ColorGroup colorGroup = null; 031 for (final ColorGroup group : colorGroups) { 032 if (group.maxDiff > 0) { 033 if (group.totalPoints > maxPoints) { 034 colorGroup = group; 035 maxPoints = group.totalPoints; 036 } 037 } 038 } 039 if (colorGroup == null) { 040 return false; 041 } 042 043 final List<ColorCount> colorCounts = colorGroup.getColorCounts(); 044 045 double bestScore = Double.MAX_VALUE; 046 ColorComponent bestColorComponent = null; 047 int bestMedianIndex = -1; 048 for (final ColorComponent colorComponent : ColorComponent.values()) { 049 if (ignoreAlpha && colorComponent == ColorComponent.ALPHA) { 050 continue; 051 } 052 colorCounts.sort(new ColorCountComparator(colorComponent)); 053 final int countHalf = (int) Math.round((double) colorGroup.totalPoints / 2); 054 int oldCount = 0; 055 int newCount = 0; 056 int medianIndex; 057 for (medianIndex = 0; medianIndex < colorCounts.size(); medianIndex++) { 058 final ColorCount colorCount = colorCounts.get(medianIndex); 059 060 newCount += colorCount.count; 061 062 if (newCount >= countHalf) { 063 break; 064 } 065 oldCount = newCount; 066 } 067 if (medianIndex == colorCounts.size() - 1) { 068 medianIndex--; 069 } else if (medianIndex > 0) { 070 final int newDiff = Math.abs(newCount - countHalf); 071 final int oldDiff = Math.abs(countHalf - oldCount); 072 if (oldDiff < newDiff) { 073 medianIndex--; 074 } 075 } 076 077 final List<ColorCount> lowerColors = new ArrayList<>( 078 colorCounts.subList(0, medianIndex + 1)); 079 final List<ColorCount> upperColors = new ArrayList<>( 080 colorCounts.subList(medianIndex + 1, 081 colorCounts.size())); 082 if (lowerColors.isEmpty() || upperColors.isEmpty()) { 083 continue; 084 } 085 final ColorGroup lowerGroup = new ColorGroup(lowerColors, ignoreAlpha); 086 final ColorGroup upperGroup = new ColorGroup(upperColors, ignoreAlpha); 087 final int diff = Math.abs(lowerGroup.totalPoints - upperGroup.totalPoints); 088 final double score = diff / (double) Math.max(lowerGroup.totalPoints, upperGroup.totalPoints); 089 if (score < bestScore) { 090 bestScore = score; 091 bestColorComponent = colorComponent; 092 bestMedianIndex = medianIndex; 093 } 094 } 095 096 if (bestColorComponent == null) { 097 return false; 098 } 099 100 colorCounts.sort(new ColorCountComparator(bestColorComponent)); 101 final List<ColorCount> lowerColors = new ArrayList<>( 102 colorCounts.subList(0, bestMedianIndex + 1)); 103 final List<ColorCount> upperColors = new ArrayList<>( 104 colorCounts.subList(bestMedianIndex + 1, 105 colorCounts.size())); 106 final ColorGroup lowerGroup = new ColorGroup(lowerColors, ignoreAlpha); 107 final ColorGroup upperGroup = new ColorGroup(upperColors, ignoreAlpha); 108 colorGroups.remove(colorGroup); 109 colorGroups.add(lowerGroup); 110 colorGroups.add(upperGroup); 111 112 final ColorCount medianValue = colorCounts.get(bestMedianIndex); 113 int limit; 114 switch (bestColorComponent) { 115 case ALPHA: 116 limit = medianValue.alpha; 117 break; 118 case RED: 119 limit = medianValue.red; 120 break; 121 case GREEN: 122 limit = medianValue.green; 123 break; 124 case BLUE: 125 limit = medianValue.blue; 126 break; 127 default: 128 throw new Error("Bad mode."); 129 } 130 colorGroup.cut = new ColorGroupCut(lowerGroup, upperGroup, bestColorComponent, limit); 131 return true; 132 } 133}