org.apache.commons.imaging.palette.PaletteFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.commons.imaging.palette.PaletteFactory.java

Source

/*
 * 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.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.imaging.ImageWriteException;

/**
 * Factory for creating palettes.
 */
public class PaletteFactory {
    private static final boolean debug = false;

    /**
     * Builds an exact complete opaque palette containing all the colors in {@code src},
     * using an algorithm that is faster than {@linkplain #makeExactRgbPaletteSimple} for large images
     * but uses 2 mebibytes of working memory. Treats all the colors as opaque.
     * @param src the image whose palette to build
     * @return the palette
     */
    public Palette makeExactRgbPaletteFancy(final BufferedImage src) {
        // map what rgb values have been used

        final byte rgbmap[] = new byte[256 * 256 * 32];

        final int width = src.getWidth();
        final int height = src.getHeight();

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                final int argb = src.getRGB(x, y);
                final int rggbb = 0x1fffff & argb;
                final int highred = 0x7 & (argb >> 21);
                final int mask = 1 << highred;
                rgbmap[rggbb] |= mask;
            }
        }

        int count = 0;
        for (final byte element : rgbmap) {
            final int eight = 0xff & element;
            count += Integer.bitCount(eight);
        }

        if (debug) {
            System.out.println("Used colors: " + count);
        }

        final int colormap[] = new int[count];
        int mapsize = 0;
        for (int i = 0; i < rgbmap.length; i++) {
            final int eight = 0xff & rgbmap[i];
            int mask = 0x80;
            for (int j = 0; j < 8; j++) {
                final int bit = eight & mask;
                mask >>>= 1;

                if (bit > 0) {
                    final int rgb = i | ((7 - j) << 21);

                    colormap[mapsize++] = rgb;
                }
            }
        }

        Arrays.sort(colormap);
        return new SimplePalette(colormap);
    }

    private int pixelToQuantizationTableIndex(int argb, final int precision) {
        int result = 0;
        final int precision_mask = (1 << precision) - 1;

        for (int i = 0; i < components; i++) {
            int sample = argb & 0xff;
            argb >>= 8;

            sample >>= (8 - precision);
            result = (result << precision) | (sample & precision_mask);
        }

        return result;
    }

    private int getFrequencyTotal(final int table[], final int mins[], final int maxs[], final int precision) {
        int sum = 0;

        for (int blue = mins[2]; blue <= maxs[2]; blue++) {
            final int b = (blue << (2 * precision));
            for (int green = mins[1]; green <= maxs[1]; green++) {
                final int g = (green << (1 * precision));
                for (int red = mins[0]; red <= maxs[0]; red++) {
                    final int index = b | g | red;

                    sum += table[index];
                }
            }
        }

        return sum;
    }

    private DivisionCandidate finishDivision(final int table[], final ColorSpaceSubset subset, final int component,
            final int precision, final int sum, final int slice) {
        if (debug) {
            subset.dump("trying (" + component + "): ");
        }

        final int total = subset.total;

        if ((slice < subset.mins[component]) || (slice >= subset.maxs[component])) {
            return null;
        }

        if ((sum < 1) || (sum >= total)) {
            return null;
        }

        final int remainder = total - sum;
        if ((remainder < 1) || (remainder >= total)) {
            return null;
        }

        final int slice_mins[] = new int[subset.mins.length];
        System.arraycopy(subset.mins, 0, slice_mins, 0, subset.mins.length);
        final int slice_maxs[] = new int[subset.maxs.length];
        System.arraycopy(subset.maxs, 0, slice_maxs, 0, subset.maxs.length);

        slice_maxs[component] = slice;
        slice_mins[component] = slice + 1;

        if (debug) {
            System.out.println("total: " + total);
            System.out.println("first total: " + sum);
            System.out.println("second total: " + (total - sum));
            // System.out.println("start: " + start);
            // System.out.println("end: " + end);
            System.out.println("slice: " + slice);

        }

        final ColorSpaceSubset first = new ColorSpaceSubset(sum, precision, subset.mins, slice_maxs, table);
        final ColorSpaceSubset second = new ColorSpaceSubset(total - sum, precision, slice_mins, subset.maxs,
                table);

        return new DivisionCandidate(subset, first, second);

    }

    private List<DivisionCandidate> divideSubset2(final int table[], final ColorSpaceSubset subset,
            final int component, final int precision) {
        if (debug) {
            subset.dump("trying (" + component + "): ");
        }

        final int total = subset.total;

        final int slice_mins[] = new int[subset.mins.length];
        System.arraycopy(subset.mins, 0, slice_mins, 0, subset.mins.length);
        final int slice_maxs[] = new int[subset.maxs.length];
        System.arraycopy(subset.maxs, 0, slice_maxs, 0, subset.maxs.length);

        int sum1 = 0, sum2;
        int slice1, slice2;
        int last = 0;

        for (slice1 = subset.mins[component]; slice1 != subset.maxs[component] + 1; slice1++) {

            slice_mins[component] = slice1;
            slice_maxs[component] = slice1;

            last = getFrequencyTotal(table, slice_mins, slice_maxs, precision);

            sum1 += last;

            if (sum1 >= (total / 2)) {
                break;
            }
        }

        sum2 = sum1 - last;
        slice2 = slice1 - 1;

        final DivisionCandidate dc1 = finishDivision(table, subset, component, precision, sum1, slice1);
        final DivisionCandidate dc2 = finishDivision(table, subset, component, precision, sum2, slice2);

        final List<DivisionCandidate> result = new ArrayList<DivisionCandidate>();

        if (dc1 != null) {
            result.add(dc1);
        }
        if (dc2 != null) {
            result.add(dc2);
        }

        return result;
    }

    private DivisionCandidate divideSubset2(final int table[], final ColorSpaceSubset subset, final int precision) {
        final List<DivisionCandidate> dcs = new ArrayList<DivisionCandidate>();

        dcs.addAll(divideSubset2(table, subset, 0, precision));
        dcs.addAll(divideSubset2(table, subset, 1, precision));
        dcs.addAll(divideSubset2(table, subset, 2, precision));

        DivisionCandidate best_v = null;
        double best_score = Double.MAX_VALUE;

        for (int i = 0; i < dcs.size(); i++) {
            final DivisionCandidate dc = dcs.get(i);

            final ColorSpaceSubset first = dc.dst_a;
            final ColorSpaceSubset second = dc.dst_b;
            final int area1 = first.total;
            final int area2 = second.total;

            final int diff = Math.abs(area1 - area2);
            final double score = ((double) diff) / ((double) Math.max(area1, area2));

            if (best_v == null) {
                best_v = dc;
                best_score = score;
            } else if (score < best_score) {
                best_v = dc;
                best_score = score;
            }

        }

        return best_v;
    }

    public static final int components = 3; // in bits

    private static class DivisionCandidate {
        // private final ColorSpaceSubset src;
        private final ColorSpaceSubset dst_a, dst_b;

        public DivisionCandidate(final ColorSpaceSubset src, final ColorSpaceSubset dst_a,
                final ColorSpaceSubset dst_b) {
            // this.src = src;
            this.dst_a = dst_a;
            this.dst_b = dst_b;
        }
    }

    private List<ColorSpaceSubset> divide(final List<ColorSpaceSubset> v, final int desired_count,
            final int table[], final int precision) {
        final List<ColorSpaceSubset> ignore = new ArrayList<ColorSpaceSubset>();

        while (true) {
            int max_area = -1;
            ColorSpaceSubset max_subset = null;

            for (int i = 0; i < v.size(); i++) {
                final ColorSpaceSubset subset = v.get(i);
                if (ignore.contains(subset)) {
                    continue;
                }
                final int area = subset.total;

                if (max_subset == null) {
                    max_subset = subset;
                    max_area = area;
                } else if (area > max_area) {
                    max_subset = subset;
                    max_area = area;
                }
            }

            if (max_subset == null) {
                return v;
            }
            if (debug) {
                System.out.println("\t" + "area: " + max_area);
            }

            final DivisionCandidate dc = divideSubset2(table, max_subset, precision);
            if (dc != null) {
                v.remove(max_subset);
                v.add(dc.dst_a);
                v.add(dc.dst_b);
            } else {
                ignore.add(max_subset);
            }

            if (v.size() == desired_count) {
                return v;
            }
        }
    }

    /**
     * Builds an inexact opaque palette of at most {@code max} colors in {@code src}
     * using a variation of the Median Cut algorithm. Accurate to 6 bits per component,
     * and works by splitting the color bounding box most heavily populated by colors
     * along the component which splits the colors in that box most evenly.
     * @param src the image whose palette to build
     * @param max the maximum number of colors the palette can contain
     * @return the palette of at most {@code max} colors
     */
    public Palette makeQuantizedRgbPalette(final BufferedImage src, final int max) {
        final int precision = 6; // in bits

        final int table_scale = precision * components;
        final int table_size = 1 << table_scale;
        final int table[] = new int[table_size];

        final int width = src.getWidth();
        final int height = src.getHeight();

        List<ColorSpaceSubset> subsets = new ArrayList<ColorSpaceSubset>();
        final ColorSpaceSubset all = new ColorSpaceSubset(width * height, precision);
        subsets.add(all);

        if (debug) {
            final int pre_total = getFrequencyTotal(table, all.mins, all.maxs, precision);
            System.out.println("pre total: " + pre_total);
        }

        // step 1: count frequency of colors
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                final int argb = src.getRGB(x, y);

                final int index = pixelToQuantizationTableIndex(argb, precision);

                table[index]++;
            }
        }

        if (debug) {
            final int all_total = getFrequencyTotal(table, all.mins, all.maxs, precision);
            System.out.println("all total: " + all_total);
            System.out.println("width * height: " + (width * height));
        }

        subsets = divide(subsets, max, table, precision);

        if (debug) {
            System.out.println("subsets: " + subsets.size());
            System.out.println("width*height: " + width * height);
        }

        for (int i = 0; i < subsets.size(); i++) {
            final ColorSpaceSubset subset = subsets.get(i);

            subset.setAverageRGB(table);

            if (debug) {
                subset.dump(i + ": ");
            }
        }

        Collections.sort(subsets, ColorSpaceSubset.rgbComparator);

        return new QuantizedPalette(subsets, precision);
    }

    /**
     * Builds an inexact possibly translucent palette of at most {@code max} colors in {@code src}
     * using the traditional Median Cut algorithm. Color bounding boxes are split along the
     * longest axis, with each step splitting the box. All bits in each component are used.
     * The Algorithm is slower and seems exact than {@linkplain #makeQuantizedRgbPalette(BufferedImage, int)}.
     * @param src the image whose palette to build
     * @param transparent whether to consider the alpha values
     * @param max the maximum number of colors the palette can contain
     * @return the palette of at most {@code max} colors
     */
    public Palette makeQuantizedRgbaPalette(final BufferedImage src, final boolean transparent, final int max)
            throws ImageWriteException {
        return new MedianCutQuantizer(!transparent).process(src, max, new MedianCutLongestAxisImplementation(),
                false);
    }

    /**
     * Builds an exact complete opaque palette containing all the colors in {@code src},
     * and fails by returning {@code null} if there are more than {@code max} colors necessary to do this.
     * @param src the image whose palette to build
     * @param max the maximum number of colors the palette can contain
     * @return the complete palette of {@code max} or less colors, or {@code null} if more than {@code max} colors are necessary
     */
    public SimplePalette makeExactRgbPaletteSimple(final BufferedImage src, final int max) {
        // This is not efficient for large values of max, say, max > 256;
        final Set<Integer> rgbs = new HashSet<Integer>();

        final int width = src.getWidth();
        final int height = src.getHeight();

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                final int argb = src.getRGB(x, y);
                final int rgb = 0xffffff & argb;

                if (rgbs.add(rgb) && rgbs.size() > max) {
                    return null;
                }
            }
        }

        final int result[] = new int[rgbs.size()];
        int next = 0;
        for (final int rgb : rgbs) {
            result[next++] = rgb;
        }
        Arrays.sort(result);

        return new SimplePalette(result);
    }

    public boolean isGrayscale(final BufferedImage src) {
        final int width = src.getWidth();
        final int height = src.getHeight();

        if (ColorSpace.TYPE_GRAY == src.getColorModel().getColorSpace().getType()) {
            return true;
        }

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                final int argb = src.getRGB(x, y);

                final int red = 0xff & (argb >> 16);
                final int green = 0xff & (argb >> 8);
                final int blue = 0xff & (argb >> 0);

                if (red != green || red != blue) {
                    return false;
                }
            }
        }
        return true;
    }

    public boolean hasTransparency(final BufferedImage src) {
        return hasTransparency(src, 255);
    }

    public boolean hasTransparency(final BufferedImage src, final int threshold) {
        final int width = src.getWidth();
        final int height = src.getHeight();

        if (!src.getColorModel().hasAlpha()) {
            return false;
        }

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                final int argb = src.getRGB(x, y);
                final int alpha = 0xff & (argb >> 24);
                if (alpha < threshold) {
                    return true;
                }
            }
        }
        return false;
    }

    public int countTrasparentColors(final int rgbs[]) {
        int first = -1;

        for (final int rgb : rgbs) {
            final int alpha = 0xff & (rgb >> 24);
            if (alpha < 0xff) {
                if (first < 0) {
                    first = rgb;
                } else if (rgb != first) {
                    return 2; // more than one transparent color;
                }
            }
        }

        if (first < 0) {
            return 0;
        }
        return 1;
    }

    public int countTransparentColors(final BufferedImage src) {
        final ColorModel cm = src.getColorModel();
        if (!cm.hasAlpha()) {
            return 0;
        }

        final int width = src.getWidth();
        final int height = src.getHeight();

        int first = -1;

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                final int rgb = src.getRGB(x, y);
                final int alpha = 0xff & (rgb >> 24);
                if (alpha < 0xff) {
                    if (first < 0) {
                        first = rgb;
                    } else if (rgb != first) {
                        return 2; // more than one transparent color;
                    }
                }
            }
        }

        if (first < 0) {
            return 0;
        }
        return 1;
    }

}