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;
020
021import org.apache.commons.imaging.ImagingException;
022
023/**
024 * Dithering algorithms to use when quantizing an image to palette form.
025 */
026public final class Dithering {
027    private static int adjustPixel(final int argb, final int errA, final int errR, final int errG, final int errB, final int mul) {
028        int a = argb >> 24 & 0xff;
029        int r = argb >> 16 & 0xff;
030        int g = argb >> 8 & 0xff;
031        int b = argb & 0xff;
032
033        a += errA * mul / 16;
034        r += errR * mul / 16;
035        g += errG * mul / 16;
036        b += errB * mul / 16;
037
038        if (a < 0) {
039            a = 0;
040        } else if (a > 0xff) {
041            a = 0xff;
042        }
043        if (r < 0) {
044            r = 0;
045        } else if (r > 0xff) {
046            r = 0xff;
047        }
048        if (g < 0) {
049            g = 0;
050        } else if (g > 0xff) {
051            g = 0xff;
052        }
053        if (b < 0) {
054            b = 0;
055        } else if (b > 0xff) {
056            b = 0xff;
057        }
058
059        return a << 24 | r << 16 | g << 8 | b;
060    }
061
062    /**
063     * Changes the given image to only use colors from the given palette, applying Floyd-Steinberg dithering in the process. Ensure that your alpha values in
064     * the image and in the palette are consistent.
065     *
066     * @param image   the image to change
067     * @param palette the palette to use
068     * @throws ImagingException if it fails to read the palette index
069     */
070    public static void applyFloydSteinbergDithering(final BufferedImage image, final Palette palette) throws ImagingException {
071        for (int y = 0; y < image.getHeight(); y++) {
072            for (int x = 0; x < image.getWidth(); x++) {
073                final int argb = image.getRGB(x, y);
074                final int index = palette.getPaletteIndex(argb);
075                final int nextArgb = palette.getEntry(index);
076                image.setRGB(x, y, nextArgb);
077
078                final int a = argb >> 24 & 0xff;
079                final int r = argb >> 16 & 0xff;
080                final int g = argb >> 8 & 0xff;
081                final int b = argb & 0xff;
082
083                final int na = nextArgb >> 24 & 0xff;
084                final int nr = nextArgb >> 16 & 0xff;
085                final int ng = nextArgb >> 8 & 0xff;
086                final int nb = nextArgb & 0xff;
087
088                final int errA = a - na;
089                final int errR = r - nr;
090                final int errG = g - ng;
091                final int errB = b - nb;
092
093                if (x + 1 < image.getWidth()) {
094                    int update = adjustPixel(image.getRGB(x + 1, y), errA, errR, errG, errB, 7);
095                    image.setRGB(x + 1, y, update);
096                    if (y + 1 < image.getHeight()) {
097                        update = adjustPixel(image.getRGB(x + 1, y + 1), errA, errR, errG, errB, 1);
098                        image.setRGB(x + 1, y + 1, update);
099                    }
100                }
101                if (y + 1 < image.getHeight()) {
102                    int update = adjustPixel(image.getRGB(x, y + 1), errA, errR, errG, errB, 5);
103                    image.setRGB(x, y + 1, update);
104                    if (x - 1 >= 0) {
105                        update = adjustPixel(image.getRGB(x - 1, y + 1), errA, errR, errG, errB, 3);
106                        image.setRGB(x - 1, y + 1, update);
107                    }
108
109                }
110            }
111        }
112    }
113
114    private Dithering() {
115        // no instances.
116    }
117}