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