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