001 /* 002 * HueSaturationValue 003 * 004 * Copyright (c) 2001, 2002 Marco Schmidt <marcoschmidt@users.sourceforge.net> 005 * All rights reserved. 006 */ 007 008 package net.sourceforge.jiu.color.adjustment; 009 010 import net.sourceforge.jiu.data.Palette; 011 import net.sourceforge.jiu.data.Paletted8Image; 012 import net.sourceforge.jiu.data.PixelImage; 013 import net.sourceforge.jiu.data.RGB24Image; 014 import net.sourceforge.jiu.data.RGBIndex; 015 import net.sourceforge.jiu.ops.ImageToImageOperation; 016 import net.sourceforge.jiu.ops.MissingParameterException; 017 import net.sourceforge.jiu.ops.WrongParameterException; 018 019 /** 020 * Adjusts saturation and value of a color image, optionally hue as well. 021 * <p> 022 * Supported image types: {@link RGB24Image}, {@link Paletted8Image}. 023 * @author Marco Schmidt 024 * @since 0.5.0 025 */ 026 public class HueSaturationValue extends ImageToImageOperation implements RGBIndex 027 { 028 private float hue; 029 private boolean modifyHue; 030 private float sMult; 031 private boolean sNegative; 032 private float vMult; 033 private boolean vNegative; 034 035 private final void adjust(int[] orig, int[] adjusted, float maxSample) 036 { 037 // get r-g-b as values from 0.0f to 1.0f 038 float r = orig[INDEX_RED] / maxSample; 039 float g = orig[INDEX_GREEN] / maxSample; 040 float b = orig[INDEX_BLUE] / maxSample; 041 // (1) compute h-s-v 042 float max = Math.max(Math.max(r, g), b); 043 float min = Math.min(Math.min(r, g), b); 044 float v = max; 045 float s; 046 if (max != 0.0f) 047 { 048 s = (max - min) / max; 049 } 050 else 051 { 052 s = 0.0f; 053 } 054 float h; 055 if (s == 0.0f) 056 { 057 h = Float.NaN; 058 } 059 else 060 { 061 float delta = max - min; 062 if (r == max) 063 { 064 h = (g - b) / delta; 065 } 066 else 067 if (g == max) 068 { 069 h = 2.0f + (b - r) / delta; 070 } 071 else 072 { 073 h = 4.0f + (r - g) / delta; 074 } 075 h *= 60.0f; 076 if (h < 0.0f) 077 { 078 h += 360.0f; 079 } 080 } 081 // (2) adjust h-s-v 082 if (modifyHue) 083 { 084 h = hue; 085 } 086 if (sNegative) 087 { 088 s *= sMult; 089 } 090 else 091 { 092 s += (1.0f - s) * sMult; 093 } 094 if (vNegative) 095 { 096 v *= vMult; 097 } 098 else 099 { 100 v += (1.0f - v) * vMult; 101 } 102 // (3) convert back to r-g-b 103 if (s == 0.0f) 104 { 105 if (h == Float.NaN) 106 { 107 int value = (int)(v * maxSample); 108 adjusted[INDEX_RED] = value; 109 adjusted[INDEX_GREEN] = value; 110 adjusted[INDEX_BLUE] = value; 111 return; 112 } 113 else 114 { 115 return; 116 } 117 } 118 if (h == 360.0f) 119 { 120 h = 0.0f; 121 } 122 h /= 60.0f; 123 int i = (int)Math.floor(h); 124 float f = h - i; 125 float p = v * (1 - s); 126 float q = v * (1 - (s * f)); 127 float t = v * (1 - (s * (1 - f))); 128 switch(i) 129 { 130 case(0): 131 { 132 adjusted[INDEX_RED] = (int)(v * maxSample); 133 adjusted[INDEX_GREEN] = (int)(t * maxSample); 134 adjusted[INDEX_BLUE] = (int)(p * maxSample); 135 break; 136 } 137 case(1): 138 { 139 adjusted[INDEX_RED] = (int)(q * maxSample); 140 adjusted[INDEX_GREEN] = (int)(v * maxSample); 141 adjusted[INDEX_BLUE] = (int)(p * maxSample); 142 break; 143 } 144 case(2): 145 { 146 adjusted[INDEX_RED] = (int)(p * maxSample); 147 adjusted[INDEX_GREEN] = (int)(v * maxSample); 148 adjusted[INDEX_BLUE] = (int)(t * maxSample); 149 break; 150 } 151 case(3): 152 { 153 adjusted[INDEX_RED] = (int)(p * maxSample); 154 adjusted[INDEX_GREEN] = (int)(q * maxSample); 155 adjusted[INDEX_BLUE] = (int)(v * maxSample); 156 break; 157 } 158 case(4): 159 { 160 adjusted[INDEX_RED] = (int)(t * maxSample); 161 adjusted[INDEX_GREEN] = (int)(p * maxSample); 162 adjusted[INDEX_BLUE] = (int)(v * maxSample); 163 break; 164 } 165 case(5): 166 { 167 adjusted[INDEX_RED] = (int)(v * maxSample); 168 adjusted[INDEX_GREEN] = (int)(p * maxSample); 169 adjusted[INDEX_BLUE] = (int)(q * maxSample); 170 break; 171 } 172 } 173 } 174 175 private void process(Paletted8Image in, Paletted8Image out) 176 { 177 Palette inPal = in.getPalette(); 178 Palette outPal = out.getPalette(); 179 int[] orig = new int[3]; 180 int[] adjusted = new int[3]; 181 final int MAX = inPal.getMaxValue(); 182 final int WIDTH = in.getWidth(); 183 final int HEIGHT = in.getHeight(); 184 for (int i = 0; i < inPal.getNumEntries(); i++) 185 { 186 orig[INDEX_RED] = inPal.getSample(INDEX_RED, i); 187 orig[INDEX_GREEN] = inPal.getSample(INDEX_GREEN, i); 188 orig[INDEX_BLUE] = inPal.getSample(INDEX_BLUE, i); 189 adjust(orig, adjusted, MAX); 190 outPal.putSample(INDEX_RED, i, adjusted[INDEX_RED]); 191 outPal.putSample(INDEX_GREEN, i, adjusted[INDEX_GREEN]); 192 outPal.putSample(INDEX_BLUE, i, adjusted[INDEX_BLUE]); 193 } 194 for (int y = 0; y < HEIGHT; y++) 195 { 196 for (int x = 0; x < WIDTH; x++) 197 { 198 out.putSample(0, x, y, in.getSample(0, x, y)); 199 } 200 setProgress(y, HEIGHT); 201 } 202 } 203 204 private void process(RGB24Image in, RGB24Image out) 205 { 206 final int MAX = in.getMaxSample(0); 207 final int WIDTH = in.getWidth(); 208 final int HEIGHT = in.getHeight(); 209 int[] orig = new int[3]; 210 int[] adjusted = new int[3]; 211 for (int y = 0; y < HEIGHT; y++) 212 { 213 for (int x = 0; x < WIDTH; x++) 214 { 215 orig[INDEX_RED] = in.getSample(INDEX_RED, x, y); 216 orig[INDEX_GREEN] = in.getSample(INDEX_GREEN, x, y); 217 orig[INDEX_BLUE] = in.getSample(INDEX_BLUE, x, y); 218 adjust(orig, adjusted, MAX); 219 out.putSample(INDEX_RED, x, y, adjusted[INDEX_RED]); 220 out.putSample(INDEX_GREEN, x, y, adjusted[INDEX_GREEN]); 221 out.putSample(INDEX_BLUE, x, y, adjusted[INDEX_BLUE]); 222 } 223 setProgress(y, HEIGHT); 224 } 225 } 226 227 public void process() throws 228 MissingParameterException, 229 WrongParameterException 230 { 231 PixelImage in = getInputImage(); 232 if (in == null) 233 { 234 throw new MissingParameterException("Input image missing."); 235 } 236 PixelImage out = getOutputImage(); 237 if (out == null) 238 { 239 out = in.createCompatibleImage(in.getWidth(), in.getHeight()); 240 setOutputImage(out); 241 } 242 if (in instanceof RGB24Image) 243 { 244 process((RGB24Image)in, (RGB24Image)out); 245 } 246 else 247 if (in instanceof Paletted8Image) 248 { 249 process((Paletted8Image)in, (Paletted8Image)out); 250 } 251 else 252 { 253 throw new WrongParameterException("Input image type not supported."); 254 } 255 } 256 257 /** 258 * Set the values for the adjustment of hue, saturation and value (brightness). 259 * Saturation and value must be from the interval -100 to 100 (also see {@link #setSaturationValue(int, int)}). 260 * Hue must be from the interval 0 to 359. 261 * @param hue the hue to be used for the complete image, between 0 and 359 262 * @param saturation change of saturation, between -100 and 100 263 * @param value change of saturation, between -100 and 100 264 * @throws IllegalArgumentException if one of the arguments does not stay within 265 * the valid interval 266 */ 267 public void setHueSaturationValue(int hue, int saturation, int value) 268 { 269 if (hue < 0 || hue >= 360) 270 { 271 throw new IllegalArgumentException("Hue must be from 0..359; got " + hue); 272 } 273 modifyHue = true; 274 this.hue = hue; 275 setSv(saturation, value); 276 } 277 278 /** 279 * Set the amount of change to saturation and value (brightness) for this operation, 280 * between -100 and 100. 281 * Calling this method also tells the operation not to modify the hue of the image. 282 * @param saturation change of saturation, between -100 and 100 283 * @param value change of saturation, between -100 and 100 284 * @throws IllegalArgumentException if one of the two arguments does not stay within 285 * the -100 .. 100 interval 286 */ 287 public void setSaturationValue(int saturation, int value) 288 { 289 modifyHue = false; 290 setSv(saturation, value); 291 } 292 293 private void setSv(int saturation, int value) 294 { 295 if (saturation < -100 || saturation > 100) 296 { 297 throw new IllegalArgumentException("Saturation must be from -100..100; got " + saturation); 298 } 299 sNegative = (saturation < 0); 300 if (sNegative) 301 { 302 sMult = (100.0f + saturation) / 100.0f; 303 } 304 else 305 if (saturation > 0) 306 { 307 sMult = ((float)saturation) / 100.0f; 308 } 309 else 310 { 311 sMult = 0.0f; 312 } 313 if (value < -100 || value > 100) 314 { 315 throw new IllegalArgumentException("Saturation must be from -100..100; got " + value); 316 } 317 vNegative = (value < 0); 318 if (vNegative) 319 { 320 vMult = (100.0f + value) / 100.0f; 321 } 322 else 323 if (value > 0) 324 { 325 vMult = ((float)value) / 100.0f; 326 } 327 else 328 { 329 vMult = 0.0f; 330 } 331 } 332 }