001 /* 002 * RGBToGrayConversion 003 * 004 * Copyright (c) 2000, 2001, 2002, 2003 Marco Schmidt. 005 * All rights reserved. 006 */ 007 008 package net.sourceforge.jiu.color.reduction; 009 010 import net.sourceforge.jiu.data.Gray16Image; 011 import net.sourceforge.jiu.data.Gray8Image; 012 import net.sourceforge.jiu.data.GrayIntegerImage; 013 import net.sourceforge.jiu.data.MemoryGray16Image; 014 import net.sourceforge.jiu.data.MemoryGray8Image; 015 import net.sourceforge.jiu.data.Palette; 016 import net.sourceforge.jiu.data.Paletted8Image; 017 import net.sourceforge.jiu.data.PixelImage; 018 import net.sourceforge.jiu.data.RGB24Image; 019 import net.sourceforge.jiu.data.RGB48Image; 020 import net.sourceforge.jiu.data.RGBIndex; 021 import net.sourceforge.jiu.data.RGBIntegerImage; 022 import net.sourceforge.jiu.ops.ImageToImageOperation; 023 import net.sourceforge.jiu.ops.MissingParameterException; 024 import net.sourceforge.jiu.ops.WrongParameterException; 025 026 /** 027 * Converts RGB color images (both truecolor and paletted) to grayscale images. 028 * The weights to be used with the three base colors red, green and blue can be 029 * modified with a call to 030 * {@link #setColorWeights(float, float, float)}. 031 * <h3>Supported image types</h3> 032 * {@link RGB24Image} and {@link Paletted8Image} can be used as input image types. 033 * A {@link Gray8Image} be will be created from them. 034 * <p> 035 * Could be optimized to use int multiplication instead of float multiplication. 036 * <p> 037 * NOTE: Should be adjusted to support RGB48Image objects once they're available. 038 * <h3>Usage example</h3> 039 * <pre> 040 * RGBToGrayConversion rgbtogray = new RGBToGrayConversion(); 041 * rgbtogray.setInputImage(image); 042 * rgbtogray.process(); 043 * PixelImage grayImage = rgbtogray.getOutputImage(); 044 * </pre> 045 * @author Marco Schmidt 046 */ 047 public class RGBToGrayConversion extends ImageToImageOperation 048 { 049 /** 050 * The default weight for red samples in the conversion, 0.3f. 051 */ 052 public static final float DEFAULT_RED_WEIGHT = 0.3f; 053 054 /** 055 * The default weight for green samples in the conversion, 0.59f. 056 */ 057 public static final float DEFAULT_GREEN_WEIGHT = 0.59f; 058 059 /** 060 * The default weight for blue samples in the conversion, 0.11f. 061 */ 062 public static final float DEFAULT_BLUE_WEIGHT = 0.11f; 063 064 private float redWeight = DEFAULT_RED_WEIGHT; 065 private float greenWeight = DEFAULT_GREEN_WEIGHT; 066 private float blueWeight = DEFAULT_BLUE_WEIGHT; 067 068 private void convert(RGBIntegerImage in, GrayIntegerImage out) 069 { 070 final int WIDTH = in.getWidth(); 071 final int HEIGHT = in.getHeight(); 072 for (int y = 0; y < HEIGHT; y++) 073 { 074 for (int x = 0; x < WIDTH; x++) 075 { 076 int red = in.getSample(RGBIndex.INDEX_RED, x, y); 077 int green = in.getSample(RGBIndex.INDEX_GREEN, x, y); 078 int blue = in.getSample(RGBIndex.INDEX_BLUE, x, y); 079 out.putSample(x, y, (int)(red * redWeight + green * greenWeight + blue * blueWeight)); 080 } 081 setProgress(y, HEIGHT); 082 } 083 setOutputImage(out); 084 } 085 086 public void process() throws 087 MissingParameterException, 088 WrongParameterException 089 { 090 ensureInputImageIsAvailable(); 091 PixelImage in = getInputImage(); 092 if (in instanceof RGB24Image) 093 { 094 process((RGB24Image)in); 095 } 096 else 097 if (in instanceof RGB48Image) 098 { 099 process((RGB48Image)in); 100 } 101 else 102 if (in instanceof Paletted8Image) 103 { 104 process((Paletted8Image)in); 105 } 106 else 107 { 108 throw new WrongParameterException("Type of input image unsupported: " + in.getImageType().getName()); 109 } 110 } 111 112 private void process(Paletted8Image in) throws 113 MissingParameterException, 114 WrongParameterException 115 { 116 PixelImage image = getOutputImage(); 117 Gray8Image out = null; 118 if (image == null) 119 { 120 out = new MemoryGray8Image(in.getWidth(), in.getHeight()); 121 } 122 else 123 { 124 if (!(image instanceof Gray8Image)) 125 { 126 throw new WrongParameterException("Specified output image must be of type Gray8Image for input image of type Paletted8Image."); 127 } 128 out = (Gray8Image)image; 129 ensureImagesHaveSameResolution(); 130 } 131 Palette palette = in.getPalette(); 132 int[] lut = new int[palette.getNumEntries()]; 133 for (int i = 0; i < lut.length; i++) 134 { 135 int red = palette.getSample(RGBIndex.INDEX_RED, i); 136 int green = palette.getSample(RGBIndex.INDEX_GREEN, i); 137 int blue = palette.getSample(RGBIndex.INDEX_BLUE, i); 138 lut[i] = (int)(red * redWeight + green * greenWeight + blue * blueWeight); 139 } 140 final int WIDTH = in.getWidth(); 141 final int HEIGHT = in.getHeight(); 142 for (int y = 0; y < HEIGHT; y++) 143 { 144 for (int x = 0; x < WIDTH; x++) 145 { 146 try 147 { 148 out.putSample(0, x, y, lut[in.getSample(0, x, y)]); 149 } 150 catch (ArrayIndexOutOfBoundsException aioobe) 151 { 152 } 153 } 154 setProgress(y, HEIGHT); 155 } 156 setOutputImage(out); 157 } 158 159 private void process(RGB24Image in) throws WrongParameterException 160 { 161 PixelImage out = getOutputImage(); 162 if (out == null) 163 { 164 out = new MemoryGray8Image(in.getWidth(), in.getHeight()); 165 } 166 else 167 { 168 if (!(out instanceof Gray8Image)) 169 { 170 throw new WrongParameterException("Specified output image must be of type Gray8Image for input image of type RGB24Image."); 171 } 172 ensureImagesHaveSameResolution(); 173 } 174 convert(in, (GrayIntegerImage)out); 175 } 176 177 private void process(RGB48Image in) throws WrongParameterException 178 { 179 PixelImage out = getOutputImage(); 180 if (out == null) 181 { 182 out = new MemoryGray16Image(in.getWidth(), in.getHeight()); 183 } 184 else 185 { 186 if (!(out instanceof Gray16Image)) 187 { 188 throw new WrongParameterException("Specified output image must be of type Gray16Image for input image of type RGB48Image."); 189 } 190 ensureImagesHaveSameResolution(); 191 } 192 convert(in, (GrayIntegerImage)out); 193 } 194 195 /** 196 * Sets the weights for the three colors red, green and blue used in the conversion procedure. 197 * For each RGB value <code>(r, g, b)</code> to be converted (whether in a truecolor 198 * image or in the palette), the formula is <code>gray = r * red + g * green + b * blue</code>. 199 * The default values for these weights are {@link #DEFAULT_RED_WEIGHT}, 200 * {@link #DEFAULT_GREEN_WEIGHT} and {@link #DEFAULT_BLUE_WEIGHT}. 201 * This method lets the user change these values. 202 * Each of these arguments must be >= 0.0f and <= 1.0f. 203 * The sum of the three must be <= 1.0f. 204 * For any resulting gray value to be spread over the complete scale from 0.0f to 1.0f it is 205 * preferable for the sum to be equal to or at least close to 1.0f. 206 * However, this is not checked. 207 * The smaller the sum of the weights is, the darker the resulting gray image will become. 208 * @param red weight of the red sample in the conversion, between <code>0.0f</code> and <code>1.0f</code> 209 * @param green weight of the green sample in the conversion, between <code>0.0f</code> and <code>1.0f</code> 210 * @param blue weight of the blue sample in the conversion, between <code>0.0f</code> and <code>1.0f</code> 211 * @throws IllegalArgumentException if any one of the above mentioned constraints for the arguments is not met 212 */ 213 public void setColorWeights(float red, float green, float blue) 214 { 215 if (red < 0.0f) 216 { 217 throw new IllegalArgumentException("The red weight must be larger than or equal to 0; got " + red); 218 } 219 if (green < 0.0f) 220 { 221 throw new IllegalArgumentException("The green weight must be larger than or equal to 0; got " + green); 222 } 223 if (blue < 0.0f) 224 { 225 throw new IllegalArgumentException("The blue weight must be larger than or equal to 0; got " + blue); 226 } 227 if (red > 1.0f) 228 { 229 throw new IllegalArgumentException("The red weight must be smaller than or equal to 1.0f; got " + red); 230 } 231 if (green > 1.0f) 232 { 233 throw new IllegalArgumentException("The green weight must be smaller than or equal to 1.0f; got " + green); 234 } 235 if (blue > 1.0f) 236 { 237 throw new IllegalArgumentException("The blue weight must be smaller than or equal to 1.0f; got " + blue); 238 } 239 float sum = red + green + blue; 240 if (sum > 1.0f) 241 { 242 throw new IllegalArgumentException("The sum of the three weights must be smaller than or equal to 1.0f; got " + sum); 243 } 244 redWeight = red; 245 greenWeight = green; 246 blueWeight = blue; 247 } 248 }