001 /* 002 * ReduceShadesOfGray 003 * 004 * Copyright (c) 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.BilevelImage; 011 import net.sourceforge.jiu.data.Gray16Image; 012 import net.sourceforge.jiu.data.Gray8Image; 013 import net.sourceforge.jiu.data.GrayIntegerImage; 014 import net.sourceforge.jiu.data.MemoryBilevelImage; 015 import net.sourceforge.jiu.data.MemoryGray16Image; 016 import net.sourceforge.jiu.data.MemoryGray8Image; 017 import net.sourceforge.jiu.data.PixelImage; 018 import net.sourceforge.jiu.ops.ImageToImageOperation; 019 import net.sourceforge.jiu.ops.MissingParameterException; 020 import net.sourceforge.jiu.ops.WrongParameterException; 021 022 /** 023 * Reduces the number of shades of gray of a grayscale image. 024 * This class uses the most simple possible algorithm. 025 * Only the most significant N bits are kept (where N is the 026 * number specified with {@link #setBits}), the others are dropped 027 * and the result is scaled back to either 8 or 16 bits to fit 028 * into the two grayscale image types. 029 * <h3>Supported image classes</h3> 030 * This class works with {@link net.sourceforge.jiu.data.Gray8Image} 031 * and {@link net.sourceforge.jiu.data.Gray16Image}. 032 * <h3>Usage example</h3> 033 * Reduce a grayscale image to 3 bit (2<sup>3</sup> = 8 shades of gray): 034 * <pre> 035 * ReduceShadesOfGray reduce = new ReduceShadesOfGray(); 036 * reduce.setBits(3); 037 * reduce.setInputImage(image); // some Gray8Image or Gray16Image 038 * reduce.process(); 039 * PixelImage reducedImage = reduce.getOutputImage(); 040 * </pre> 041 * @author Marco Schmidt 042 * @since 0.3.0 043 */ 044 public class ReduceShadesOfGray extends ImageToImageOperation 045 { 046 /** 047 * Number of significant bits in the destination grayscale image. 048 */ 049 private Integer destBits; 050 051 /** 052 * Lookup table, for each possible input sample stores the 053 * corresponding output sample. 054 */ 055 private int[] lut; 056 057 private void createLut(int inDepth) 058 { 059 int outDepth = destBits.intValue(); 060 lut = new int[1 << inDepth]; 061 final int SHIFT = inDepth - outDepth; 062 final int MAX_IN_VALUE = (1 << inDepth) - 1; 063 final int MAX_OUT_VALUE = (1 << outDepth) - 1; 064 for (int i = 0; i < lut.length; i++) 065 { 066 int value = i >> SHIFT; 067 lut[i] = (value * MAX_IN_VALUE) / MAX_OUT_VALUE; 068 } 069 } 070 071 private void process(GrayIntegerImage in, final int MASK, BilevelImage out) 072 { 073 if (out == null) 074 { 075 out = new MemoryBilevelImage(in.getWidth(), in.getHeight()); 076 } 077 out.clear(BilevelImage.BLACK); 078 for (int y = 0; y < in.getHeight(); y++) 079 { 080 for (int x = 0; x < in.getWidth(); x++) 081 { 082 if ((in.getSample(x, y) & MASK) != 0) 083 { 084 out.putWhite(x, y); 085 } 086 } 087 setProgress(y, in.getHeight()); 088 } 089 setOutputImage(out); 090 } 091 092 private void process(GrayIntegerImage in, GrayIntegerImage out) 093 { 094 int bits = destBits.intValue(); 095 for (int y = 0; y < in.getHeight(); y++) 096 { 097 for (int x = 0; x < in.getWidth(); x++) 098 { 099 out.putSample(x, y, lut[in.getSample(0, x, y)]); 100 } 101 setProgress(y, in.getHeight()); 102 } 103 setOutputImage(out); 104 } 105 106 public void process() throws MissingParameterException, WrongParameterException 107 { 108 if (destBits == null) 109 { 110 throw new MissingParameterException("The number of destination bits has not been specified."); 111 } 112 ensureInputImageIsAvailable(); 113 ensureImagesHaveSameResolution(); 114 PixelImage in = getInputImage(); 115 boolean gray8 = in instanceof Gray8Image; 116 boolean gray16 = in instanceof Gray16Image; 117 if (!(gray8 || gray16)) 118 { 119 throw new WrongParameterException("Input image must be either Gray8Image or Gray16Image."); 120 } 121 if (destBits.intValue() == 1) 122 { 123 process((GrayIntegerImage)in, gray8 ? 0x80 : 0x8000, (BilevelImage)getOutputImage()); 124 } 125 else 126 if (gray8) 127 { 128 if (destBits.intValue() > 7) 129 { 130 throw new WrongParameterException("For a Gray8Image destination bits must be 7 or less."); 131 } 132 PixelImage out = getOutputImage(); 133 if (out == null) 134 { 135 out = new MemoryGray8Image(in.getWidth(), in.getHeight()); 136 } 137 else 138 { 139 if (!(out instanceof Gray8Image)) 140 { 141 throw new WrongParameterException("For this input image, output image must be a Gray8Image."); 142 } 143 } 144 createLut(8); 145 process((GrayIntegerImage)in, (GrayIntegerImage)out); 146 } 147 else 148 if (gray16) 149 { 150 PixelImage out = getOutputImage(); 151 if (out == null) 152 { 153 out = new MemoryGray16Image(in.getWidth(), in.getHeight()); 154 } 155 else 156 { 157 if (destBits.intValue() <= 8 && !(out instanceof Gray8Image)) 158 { 159 throw new WrongParameterException("For this input image, output image must be a Gray8Image."); 160 } 161 if (destBits.intValue() <= 15 && !(out instanceof Gray16Image)) 162 { 163 throw new WrongParameterException("For this input image, output image must be a Gray16Image."); 164 } 165 } 166 createLut(16); 167 process((GrayIntegerImage)in, (GrayIntegerImage)out); 168 } 169 } 170 171 /** 172 * Specifies the number of bits the output image is supposed to have. 173 * @param bits number of bits in output image, from 1 to 15 174 * @throws IllegalArgumentException if bits is smaller than 1 or larger than 15 175 */ 176 public void setBits(int bits) 177 { 178 if (bits < 1) 179 { 180 throw new IllegalArgumentException("Number of bits must be 1 or larger."); 181 } 182 if (bits > 15) 183 { 184 throw new IllegalArgumentException("Number of bits must be 15 or smaller."); 185 } 186 destBits = new Integer(bits); 187 } 188 }