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.Comparator; 021import java.util.List; 022 023import org.apache.commons.imaging.ImageWriteException; 024 025public class LongestAxisMedianCut implements MedianCut { 026 private static final Comparator<ColorGroup> COMPARATOR = (cg1, cg2) -> { 027 if (cg1.maxDiff == cg2.maxDiff) { 028 return cg2.diffTotal - cg1.diffTotal; 029 } 030 return cg2.maxDiff - cg1.maxDiff; 031 }; 032 033 @Override 034 public boolean performNextMedianCut(final List<ColorGroup> colorGroups, final boolean ignoreAlpha) 035 throws ImageWriteException { 036 colorGroups.sort(COMPARATOR); 037 final ColorGroup colorGroup = colorGroups.get(0); 038 039 if (colorGroup.maxDiff == 0) { 040 return false; 041 } 042 if (!ignoreAlpha 043 && colorGroup.alphaDiff > colorGroup.redDiff 044 && colorGroup.alphaDiff > colorGroup.greenDiff 045 && colorGroup.alphaDiff > colorGroup.blueDiff) { 046 doCut(colorGroup, ColorComponent.ALPHA, colorGroups, ignoreAlpha); 047 } else if (colorGroup.redDiff > colorGroup.greenDiff 048 && colorGroup.redDiff > colorGroup.blueDiff) { 049 doCut(colorGroup, ColorComponent.RED, colorGroups, ignoreAlpha); 050 } else if (colorGroup.greenDiff > colorGroup.blueDiff) { 051 doCut(colorGroup, ColorComponent.GREEN, colorGroups, ignoreAlpha); 052 } else { 053 doCut(colorGroup, ColorComponent.BLUE, colorGroups, ignoreAlpha); 054 } 055 return true; 056 } 057 058 private void doCut(final ColorGroup colorGroup, final ColorComponent mode, 059 final List<ColorGroup> colorGroups, final boolean ignoreAlpha) throws ImageWriteException { 060 061 final List<ColorCount> colorCounts = colorGroup.getColorCounts(); 062 colorCounts.sort(new ColorCountComparator(mode)); 063 final int countHalf = (int) Math.round((double) colorGroup.totalPoints / 2); 064 int oldCount = 0; 065 int newCount = 0; 066 int medianIndex; 067 for (medianIndex = 0; medianIndex < colorCounts.size(); medianIndex++) { 068 final ColorCount colorCount = colorCounts.get(medianIndex); 069 070 newCount += colorCount.count; 071 072 if (newCount >= countHalf) { 073 break; 074 } 075 oldCount = newCount; 076 } 077 078 if (medianIndex == colorCounts.size() - 1) { 079 medianIndex--; 080 } else if (medianIndex > 0) { 081 final int newDiff = Math.abs(newCount - countHalf); 082 final int oldDiff = Math.abs(countHalf - oldCount); 083 if (oldDiff < newDiff) { 084 medianIndex--; 085 } 086 } 087 088 colorGroups.remove(colorGroup); 089 final List<ColorCount> colorCounts1 = new ArrayList<>( 090 colorCounts.subList(0, medianIndex + 1)); 091 final List<ColorCount> colorCounts2 = new ArrayList<>( 092 colorCounts.subList(medianIndex + 1, 093 colorCounts.size())); 094 095 final ColorGroup less = new ColorGroup(new ArrayList<>(colorCounts1), ignoreAlpha); 096 colorGroups.add(less); 097 final ColorGroup more = new ColorGroup(new ArrayList<>(colorCounts2), ignoreAlpha); 098 colorGroups.add(more); 099 100 final ColorCount medianValue = colorCounts.get(medianIndex); 101 int limit; 102 switch (mode) { 103 case ALPHA: 104 limit = medianValue.alpha; 105 break; 106 case RED: 107 limit = medianValue.red; 108 break; 109 case GREEN: 110 limit = medianValue.green; 111 break; 112 case BLUE: 113 limit = medianValue.blue; 114 break; 115 default: 116 throw new Error("Bad mode."); 117 } 118 colorGroup.cut = new ColorGroupCut(less, more, mode, limit); 119 } 120}