001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.image.colour; 031 032import org.openimaj.image.FImage; 033import org.openimaj.image.ImageUtilities; 034import org.openimaj.image.MBFImage; 035import org.openimaj.util.array.ArrayUtils; 036 037/** 038 * A collection of static methods for colour transformations 039 * 040 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 041 * 042 */ 043public class Transforms { 044 045 /** 046 * Calculate intensity by averaging R, G, B planes. Assumes planes are all 047 * in the same magnitude. 048 * 049 * @param in 050 * MBFImage with 3 bands 051 * @return intensity image 052 */ 053 public static FImage calculateIntensity(final MBFImage in) { 054 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 055 throw new UnsupportedOperationException("Can only convert RGB or RGBA images"); 056 057 final FImage out = new FImage(in.getWidth(), in.getHeight()); 058 059 for (int r = 0; r < in.getHeight(); r++) { 060 for (int c = 0; c < in.getWidth(); c++) { 061 out.pixels[r][c] = (in.getBand(0).pixels[r][c] + 062 in.getBand(1).pixels[r][c] + 063 in.getBand(2).pixels[r][c]) / 3.0F; 064 } 065 } 066 067 return out; 068 } 069 070 /** 071 * Calculate Intensity image from an RGB or RGBA MBFImage with given 072 * weightings for R, G and B 073 * 074 * @param in 075 * input image 076 * @param wR 077 * red weight 078 * @param wG 079 * green weight 080 * @param wB 081 * blue weight 082 * @return Intensity image 083 */ 084 public static FImage calculateIntensity(final MBFImage in, final float wR, final float wG, final float wB) { 085 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 086 throw new UnsupportedOperationException("Can only convert RGB or RGBA images"); 087 088 final FImage out = new FImage(in.getWidth(), in.getHeight()); 089 090 final float[][] ra = in.getBand(0).pixels; 091 final float[][] ga = in.getBand(1).pixels; 092 final float[][] ba = in.getBand(2).pixels; 093 094 for (int rr = 0; rr < in.getHeight(); rr++) { 095 for (int c = 0; c < in.getWidth(); c++) { 096 final double r = ra[rr][c]; 097 final double g = ga[rr][c]; 098 final double b = ba[rr][c]; 099 100 out.pixels[rr][c] = (float) (wR * r + wG * g + wB * b); 101 if (Float.isNaN(out.pixels[rr][c])) 102 out.pixels[rr][c] = 0; 103 } 104 } 105 106 return out; 107 } 108 109 /** 110 * Calculate intensity by a weighted average of the R, G, B planes. Assumes 111 * planes are all in the same magnitude, and NTSC weighting coefficients. 112 * 113 * @param in 114 * MBFImage with 3 bands 115 * @return intensity image 116 */ 117 public static FImage calculateIntensityNTSC(final MBFImage in) { 118 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 119 throw new UnsupportedOperationException("Can only convert RGB or RGBA images"); 120 121 final FImage out = new FImage(in.getWidth(), in.getHeight()); 122 123 for (int r = 0; r < in.getHeight(); r++) { 124 for (int c = 0; c < in.getWidth(); c++) { 125 out.pixels[r][c] = (0.299f * in.getBand(0).pixels[r][c] + 126 0.587f * in.getBand(1).pixels[r][c] + 127 0.114f * in.getBand(2).pixels[r][c]); 128 } 129 } 130 131 return out; 132 } 133 134 /** 135 * Calculate intensity by a weighted average of the R, G, B planes. Assumes 136 * planes are all in 0..1, and NTSC weighting coefficients. Assignment to 137 * graylevels is done using a LUT, so greys will have one of 256 discrete 138 * levels. The primary purpose of this is to be compatible with 139 * {@link FImage#FImage(int[], int, int)} and give exactly the same result. 140 * 141 * @param in 142 * MBFImage with 3 bands 143 * @return intensity image 144 */ 145 public static FImage calculateIntensityNTSC_LUT(final MBFImage in) { 146 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 147 throw new UnsupportedOperationException("Can only convert RGB or RGBA images"); 148 149 final FImage out = new FImage(in.getWidth(), in.getHeight()); 150 151 for (int r = 0; r < in.getHeight(); r++) { 152 for (int c = 0; c < in.getWidth(); c++) { 153 out.pixels[r][c] = ImageUtilities.BYTE_TO_FLOAT_LUT[(int) (( 154 0.299f * (255 * in.getBand(0).pixels[r][c]) + 155 0.587f * (255 * in.getBand(1).pixels[r][c]) + 156 0.114f * (255 * in.getBand(2).pixels[r][c])))]; 157 } 158 } 159 160 return out; 161 } 162 163 /** 164 * Calculate Hue in 0..1 range from a 3-band RGB MBFImage 165 * 166 * @param in 167 * RGB or RGBA image 168 * @return Hue image 169 */ 170 public static FImage calculateHue(final MBFImage in) { 171 if (in.colourSpace == ColourSpace.HSV) 172 return in.getBand(0); 173 174 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 175 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 176 177 final FImage out = new FImage(in.getWidth(), in.getHeight()); 178 179 final float[][] ra = in.getBand(0).pixels; 180 final float[][] ga = in.getBand(1).pixels; 181 final float[][] ba = in.getBand(2).pixels; 182 183 for (int rr = 0; rr < in.getHeight(); rr++) { 184 for (int c = 0; c < in.getWidth(); c++) { 185 final double r = ra[rr][c]; 186 final double g = ga[rr][c]; 187 final double b = ba[rr][c]; 188 final double i = (r + g + b) / 3.0; 189 190 // from Sonka, Hlavac & Boyle; p.26 191 final double num = 0.5 * ((r - g) + (r - b)); 192 final double den = Math.sqrt(((r - g) * (r - g)) + ((r - b) * (g - b))); 193 194 if (den == 0) 195 out.pixels[rr][c] = 0; 196 else 197 out.pixels[rr][c] = (float) Math.acos(num / den); 198 199 if ((b / i) > (g / i)) 200 out.pixels[rr][c] = (float) ((2 * Math.PI) - out.pixels[rr][c]); 201 202 // normalise to 0..1 203 out.pixels[rr][c] /= 2 * Math.PI; 204 } 205 } 206 207 return out; 208 } 209 210 /** 211 * Calculate Saturation image from a 3-band RGB MBFImage 212 * 213 * @param in 214 * RGB or RGBA image 215 * @return Saturation image 216 */ 217 public static FImage calculateSaturation(final MBFImage in) { 218 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 219 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 220 221 final FImage out = new FImage(in.getWidth(), in.getHeight()); 222 223 final float[][] ra = in.getBand(0).pixels; 224 final float[][] ga = in.getBand(1).pixels; 225 final float[][] ba = in.getBand(2).pixels; 226 227 for (int rr = 0; rr < in.getHeight(); rr++) { 228 for (int c = 0; c < in.getWidth(); c++) { 229 final double r = ra[rr][c]; 230 final double g = ga[rr][c]; 231 final double b = ba[rr][c]; 232 233 out.pixels[rr][c] = (float) (1.0 - ((3.0 / (r + g + b)) * Math.min(r, Math.min(g, b)))); 234 if (Float.isNaN(out.pixels[rr][c])) 235 out.pixels[rr][c] = 0; 236 } 237 } 238 239 return out; 240 } 241 242 /** 243 * Transform 3 band RGB image to HSV 244 * 245 * @param in 246 * RGB or RGBA image 247 * @return HSV image 248 */ 249 public static MBFImage RGB_TO_HSI(final MBFImage in) { 250 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 251 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 252 253 final MBFImage out = new MBFImage(ColourSpace.HSI); 254 255 out.addBand(Transforms.calculateHue(in)); 256 out.addBand(Transforms.calculateSaturation(in)); 257 out.addBand(Transforms.calculateIntensity(in)); 258 259 return out; 260 } 261 262 /** 263 * Transform 3 band RGB image to HSL 264 * 265 * @param in 266 * RGB or RGBA image 267 * @return HSL image 268 */ 269 public static MBFImage RGB_TO_HSL(final MBFImage in) { 270 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 271 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 272 273 final MBFImage out = in.clone(); 274 final float[] pix = new float[in.numBands()]; 275 for (int y = 0; y < in.getHeight(); y++) { 276 for (int x = 0; x < in.getWidth(); x++) { 277 for (int b = 0; b < in.numBands(); b++) 278 pix[b] = in.getBand(b).pixels[y][x]; 279 Transforms.RGB_TO_HSL(pix, pix); 280 for (int b = 0; b < in.numBands(); b++) 281 out.getBand(b).pixels[y][x] = pix[b]; 282 } 283 } 284 285 // final MBFImage out = Transforms.RGB_TO_HSV(in); 286 // 287 // final FImage R = in.getBand(0); 288 // final FImage G = in.getBand(1); 289 // final FImage B = in.getBand(2); 290 // 291 // final FImage L = out.getBand(2); 292 // for (int y = 0; y < L.height; y++) { 293 // for (int x = 0; x < L.width; x++) { 294 // final float max = Math.max(Math.max(R.pixels[y][x], G.pixels[y][x]), 295 // B.pixels[y][x]); 296 // final float min = Math.min(Math.min(R.pixels[y][x], G.pixels[y][x]), 297 // B.pixels[y][x]); 298 // L.pixels[y][x] = 0.5f * (max - min); 299 // } 300 // } 301 302 out.colourSpace = ColourSpace.HSL; 303 304 return out; 305 } 306 307 /** 308 * Converts an RGB color value to HSL. Conversion formula adapted from 309 * http://en.wikipedia.org/wiki/HSL_color_space. Assumes r, g, and b are 310 * contained in the set [0, 1] and returns h, s, and l in the set [0, 1]. 311 * 312 * @see "http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion" 313 * @param rgb 314 * The input RGB pixel 315 * @param hsl 316 * The output HSL pixel 317 * @return The output HSL pixel 318 */ 319 public static float[] RGB_TO_HSL(final float[] rgb, final float[] hsl) 320 { 321 final float r = rgb[0], g = rgb[1], b = rgb[2]; 322 final float max = Math.max(r, Math.max(g, b)), min = Math.min(r, Math.min(g, b)); 323 float h = 0, s; 324 final float l = (max + min) / 2f; 325 326 if (max == min) { 327 h = s = 0f; // achromatic 328 } else { 329 final float d = max - min; 330 s = l > 0.5f ? d / (2f - max - min) : d / (max + min); 331 if (max == r) 332 h = (g - b) / d + (g < b ? 6f : 0); 333 else if (max == g) 334 h = (b - r) / d + 2f; 335 else if (max == b) 336 h = (r - g) / d + 4f; 337 h /= 6f; 338 } 339 340 hsl[0] = h; 341 hsl[1] = s; 342 hsl[2] = l; 343 return hsl; 344 } 345 346 /** 347 * Converts an HSL color value to RGB. Conversion formula adapted from 348 * http://en.wikipedia.org/wiki/HSL_color_space. Assumes h, s, and l are 349 * contained in the set [0, 1] and returns r, g, and b in the set [0, 255]. 350 * 351 * @param hsl 352 * The HSL input pixel 353 * @param rgb 354 * The RGB output pixel 355 * @return The RGB output pixel 356 * 357 * @see "http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion" 358 */ 359 public static float[] HSL_TO_RGB(final float[] hsl, final float[] rgb) 360 { 361 float r, g, b; 362 final float h = hsl[0], s = hsl[1], l = hsl[2]; 363 364 if (s == 0) { 365 r = g = b = l; // achromatic 366 } else { 367 368 final float q = l < 0.5f ? l * (1f + s) : l + s - l * s; 369 final float p = 2f * l - q; 370 r = Transforms.hue2rgb(p, q, h + 1f / 3f); 371 g = Transforms.hue2rgb(p, q, h); 372 b = Transforms.hue2rgb(p, q, h - 1f / 3f); 373 } 374 375 rgb[0] = r; 376 rgb[1] = g; 377 rgb[2] = b; 378 return rgb; 379 } 380 381 /** 382 * Used for the HSL to RGB conversion. 383 * 384 * @return The hue 385 */ 386 private static float hue2rgb(final float p, final float q, float t) 387 { 388 if (t < 0f) 389 t += 1f; 390 if (t > 1f) 391 t -= 1f; 392 if (t < 1f / 6f) 393 return p + (q - p) * 6f * t; 394 if (t < 1f / 2f) 395 return q; 396 if (t < 2f / 3f) 397 return p + (q - p) * (2f / 3f - t) * 6f; 398 return p; 399 } 400 401 /** 402 * Transform 3 band RGB image to HSY 403 * 404 * @param in 405 * RGB or RGBA image 406 * @return HSV image 407 */ 408 public static MBFImage RGB_TO_HSY(final MBFImage in) { 409 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 410 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 411 412 final MBFImage out = new MBFImage(ColourSpace.HSY); 413 414 out.addBand(Transforms.calculateHue(in)); 415 out.addBand(Transforms.calculateSaturation(in)); 416 out.addBand(Transforms.calculateIntensityNTSC(in)); 417 418 return out; 419 } 420 421 /** 422 * Transform 3 band RGB image to HS 423 * 424 * @param in 425 * RGB or RGBA image 426 * @return HS image 427 */ 428 public static MBFImage RGB_TO_HS(final MBFImage in) { 429 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 430 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 431 432 final MBFImage out = new MBFImage(); 433 434 out.addBand(Transforms.calculateHue(in)); 435 out.addBand(Transforms.calculateSaturation(in)); 436 437 return out; 438 } 439 440 /** 441 * Convert to HS using the formulation from 442 * http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space 443 * 444 * @param in 445 * RGB or RGBA image 446 * @return HS image 447 */ 448 public static MBFImage RGB_TO_HS_2(final MBFImage in) { 449 final MBFImage hsv = Transforms.RGB_TO_HSV(in); 450 hsv.deleteBand(2); 451 hsv.colourSpace = ColourSpace.HS; 452 return hsv; 453 } 454 455 /** 456 * Transform the Hue and Saturation components of a MBFImage by projecting 457 * them from a radial coordinate system to Cartesian coordinates. Assumes 458 * the Hue is the first band and Saturation is the second band. Any 459 * additional bands will be cloned to the result image. 460 * 461 * @param in 462 * input image 463 * @return Multi-band image with coupled first and second bands calculated 464 * by projecting from radial to Cartesian coordinates. 465 */ 466 public static MBFImage projectHS(final MBFImage in) { 467 if (in.colourSpace != ColourSpace.HS && in.colourSpace != ColourSpace.HSI && 468 in.colourSpace != ColourSpace.HSV && in.colourSpace != ColourSpace.HSY) 469 throw new IllegalArgumentException("HS* colourspace is required"); 470 471 final MBFImage out = in.clone(); 472 473 final float[][] h = in.getBand(0).pixels; 474 final float[][] s = in.getBand(1).pixels; 475 final float[][] o1 = out.getBand(0).pixels; 476 final float[][] o2 = out.getBand(1).pixels; 477 478 for (int r = 0; r < in.getHeight(); r++) { 479 for (int c = 0; c < in.getWidth(); c++) { 480 o1[r][c] = (float) (s[r][c] * Math.cos(2.0 * Math.PI * h[r][c])); 481 o2[r][c] = (float) (s[r][c] * Math.sin(2.0 * Math.PI * h[r][c])); 482 } 483 } 484 485 out.colourSpace = ColourSpace.CUSTOM; 486 487 return out; 488 } 489 490 /** 491 * Convert to HSV using the formulation from: 492 * http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space The 493 * assumption is that RGB are in the range 0..1. H is output in the range 494 * 0..1, SV are output in the range 0..1 495 * 496 * @param in 497 * RGB or RGBA image 498 * @return HSV image 499 */ 500 public static MBFImage RGB_TO_HSV(final MBFImage in) { 501 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 502 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 503 504 final int width = in.getWidth(); 505 final int height = in.getHeight(); 506 507 final MBFImage out = new MBFImage(width, height, ColourSpace.HSV); 508 509 final float[][] R = in.getBand(0).pixels; 510 final float[][] G = in.getBand(1).pixels; 511 final float[][] B = in.getBand(2).pixels; 512 513 final float[][] H = out.getBand(0).pixels; 514 final float[][] S = out.getBand(1).pixels; 515 final float[][] V = out.getBand(2).pixels; 516 517 final float[] pIn = new float[3]; 518 final float[] pOut = new float[3]; 519 for (int y = 0; y < height; y++) { 520 for (int x = 0; x < width; x++) { 521 522 pIn[0] = R[y][x]; 523 pIn[1] = G[y][x]; 524 pIn[2] = B[y][x]; 525 526 Transforms.RGB_TO_HSV(pIn, pOut); 527 528 H[y][x] = pOut[0]; 529 S[y][x] = pOut[1]; 530 V[y][x] = pOut[2]; 531 } 532 } 533 return out; 534 } 535 536 /** 537 * Convert a single RGB pixel to HSV. 538 * 539 * @param rgb 540 * The pixel in RGB 541 * @param hsv 542 * The output pixel in HSV 543 * @return the output pixel, hsv 544 */ 545 static float[] RGB_TO_HSV(final float[] rgb, final float[] hsv) 546 { 547 float H, S, V; 548 549 final float R = rgb[0]; 550 final float G = rgb[1]; 551 final float B = rgb[2]; 552 553 // Blue Is the dominant color 554 if ((B > G) && (B > R)) 555 { 556 // Value is set as the dominant color 557 V = B; 558 if (V != 0) 559 { 560 float min; 561 if (R > G) 562 min = G; 563 else 564 min = R; 565 566 // Delta is the difference between the most dominant 567 // color 568 // and the least dominant color. This will be used to 569 // compute saturation. 570 final float delta = V - min; 571 if (delta != 0) { 572 S = (delta / V); 573 H = 4 + (R - G) / delta; 574 } else { 575 S = 0; 576 H = 4 + (R - G); 577 } 578 579 // Hue is just the difference between the two least 580 // dominant 581 // colors offset by the dominant color. That is, here 4 582 // puts 583 // hue in the blue range. Then red and green just tug it 584 // one 585 // way or the other. Notice if red and green are equal, 586 // hue 587 // will stick squarely on blue 588 H *= 60; 589 if (H < 0) 590 H += 360; 591 592 H /= 360; 593 } 594 else 595 { 596 S = 0; 597 H = 0; 598 } 599 } 600 // Green is the dominant color 601 else if (G > R) 602 { 603 V = G; 604 if (V != 0) 605 { 606 float min; 607 if (R > B) 608 min = B; 609 else 610 min = R; 611 612 final float delta = V - min; 613 614 if (delta != 0) { 615 S = (delta / V); 616 H = 2 + (B - R) / delta; 617 } else { 618 S = 0; 619 H = 2 + (B - R); 620 } 621 H *= 60; 622 if (H < 0) 623 H += 360; 624 625 H /= 360; 626 } else { 627 S = 0; 628 H = 0; 629 } 630 } 631 // Red is the dominant color 632 else 633 { 634 V = R; 635 if (V != 0) 636 { 637 float min; 638 if (G > B) 639 min = B; 640 else 641 min = G; 642 643 final float delta = V - min; 644 if (delta != 0) { 645 S = (delta / V); 646 H = (G - B) / delta; 647 } else { 648 S = 0; 649 H = (G - B); 650 } 651 H *= 60; 652 653 if (H < 0) 654 H += 360; 655 H /= 360; 656 } 657 else 658 { 659 S = 0; 660 H = 0; 661 } 662 } 663 664 hsv[0] = H; 665 hsv[1] = S; 666 hsv[2] = V; 667 return hsv; 668 } 669 670 /** 671 * Convert from HSV using the formulation from: 672 * http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space Assumption is 673 * that H is in the range 0..1 and SV are in the range 0..1. RGB are output 674 * in the range 0..1 675 * 676 * @param in 677 * input image 678 * @return RGB image 679 */ 680 public static MBFImage HSV_TO_RGB(final MBFImage in) 681 { 682 if (in.colourSpace != ColourSpace.HSV) 683 throw new IllegalArgumentException("HSV colourspace is required"); 684 685 final int width = in.getWidth(); 686 final int height = in.getHeight(); 687 688 final MBFImage out = new MBFImage(width, height, ColourSpace.RGB); 689 690 final float[][] H = in.getBand(0).pixels; 691 final float[][] S = in.getBand(1).pixels; 692 final float[][] V = in.getBand(2).pixels; 693 694 final float[][] R = out.getBand(0).pixels; 695 final float[][] G = out.getBand(1).pixels; 696 final float[][] B = out.getBand(2).pixels; 697 698 for (int y = 0; y < height; y++) 699 { 700 for (int x = 0; x < width; x++) 701 { 702 if (V[y][x] == 0) 703 { 704 R[y][x] = 0; 705 G[y][x] = 0; 706 B[y][x] = 0; 707 } 708 else if (S[y][x] == 0) 709 { 710 R[y][x] = V[y][x]; 711 G[y][x] = V[y][x]; 712 B[y][x] = V[y][x]; 713 } 714 else 715 { 716 final float hf = H[y][x] * 360f / 60.0f; 717 final int i = (int) Math.floor(hf); 718 final float f = hf - i; 719 final float pv = V[y][x] * (1 - S[y][x]); 720 final float qv = V[y][x] * (1 - S[y][x] * f); 721 final float tv = V[y][x] * (1 - S[y][x] * (1 - f)); 722 switch (i) 723 { 724 // Red is the dominant color 725 case 0: 726 R[y][x] = V[y][x]; 727 G[y][x] = tv; 728 B[y][x] = pv; 729 break; 730 // Green is the dominant color 731 case 1: 732 R[y][x] = qv; 733 G[y][x] = V[y][x]; 734 B[y][x] = pv; 735 break; 736 case 2: 737 R[y][x] = pv; 738 G[y][x] = V[y][x]; 739 B[y][x] = tv; 740 break; 741 // Blue is the dominant color 742 case 3: 743 R[y][x] = pv; 744 G[y][x] = qv; 745 B[y][x] = V[y][x]; 746 break; 747 case 4: 748 R[y][x] = tv; 749 G[y][x] = pv; 750 B[y][x] = V[y][x]; 751 break; 752 // Red is the dominant color 753 case 5: 754 R[y][x] = V[y][x]; 755 G[y][x] = pv; 756 B[y][x] = qv; 757 break; 758 // Just in case we overshoot on our math by a little, we put 759 // these here. Since its a switch it won't slow us down at 760 // all to put these here. 761 case 6: 762 R[y][x] = V[y][x]; 763 G[y][x] = tv; 764 B[y][x] = pv; 765 break; 766 case -1: 767 R[y][x] = V[y][x]; 768 G[y][x] = pv; 769 B[y][x] = qv; 770 break; 771 // The color is not defined, we should throw an error. 772 default: 773 System.out.println(" Unknown colour " + hf); 774 break; 775 } 776 } 777 } 778 } 779 780 return out; 781 } 782 783 /** 784 * Convert to Hue to H2 using the formulation from: 785 * http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space 786 * 787 * @param in 788 * input image 789 * @return Two-component hue image 790 */ 791 public static MBFImage H_TO_H1H2(final FImage in) { 792 final int width = in.getWidth(); 793 final int height = in.getHeight(); 794 795 final MBFImage out = new MBFImage(width, height, ColourSpace.H1H2); 796 797 final float[][] H = in.pixels; 798 799 final float[][] H1 = out.getBand(0).pixels; 800 final float[][] H2 = out.getBand(1).pixels; 801 802 for (int y = 0; y < height; y++) { 803 for (int x = 0; x < width; x++) { 804 if (H[y][x] > 0.5F) 805 { 806 H2[y][x] = ((H[y][x] - 0.5F) / 0.5F); 807 if (H[y][x] > 0.75) 808 H1[y][x] = ((H[y][x] - 0.75F) / 0.5F); 809 else 810 H1[y][x] = (1 - (H[y][x] - 0.25F) / 0.5F); 811 } 812 else 813 { 814 H2[y][x] = (1F - H[y][x] / 0.5F); 815 if (H[y][x] > 0.25F) 816 H1[y][x] = (1 - (H[y][x] - 0.25F) / 0.5F); 817 else 818 H1[y][x] = (0.5F + H[y][x] / 0.5F); 819 } 820 } 821 } 822 823 return out; 824 } 825 826 /** 827 * Convert HSV to H2SV using the formulation from: 828 * http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space 829 * 830 * @param in 831 * HSV image 832 * @return H2SV image 833 */ 834 public static MBFImage HSV_TO_H2SV(final MBFImage in) { 835 if (in.colourSpace != ColourSpace.HSV) 836 throw new IllegalArgumentException("HSV colourspace is required"); 837 838 final MBFImage out = Transforms.H_TO_H1H2(in.getBand(0)); 839 out.addBand(in.getBand(1)); // S 840 out.addBand(in.getBand(2)); // V 841 842 return out; 843 } 844 845 /** 846 * Convert RGB to H2SV 847 * 848 * @param in 849 * RGB or RGBA image 850 * @return H2SV image 851 */ 852 public static MBFImage RGB_TO_H2SV(final MBFImage in) { 853 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 854 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 855 856 final MBFImage HSV = Transforms.RGB_TO_HSV(in); 857 858 return Transforms.HSV_TO_H2SV(HSV); 859 } 860 861 /** 862 * Convert RGB to H2S 863 * 864 * @param in 865 * RGB or RGBA image 866 * @return H2S image 867 */ 868 public static MBFImage RGB_TO_H2S(final MBFImage in) { 869 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 870 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 871 872 final MBFImage H2S = Transforms.RGB_TO_H2SV(in); 873 H2S.deleteBand(3); // remove V 874 H2S.colourSpace = ColourSpace.H2S; 875 876 return H2S; 877 } 878 879 /** 880 * Convert to Hue to H2 VARIANT 2 using the formulation from: 881 * http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space 882 * 883 * @param in 884 * Hue image 885 * @return H1H2_2 image 886 */ 887 public static MBFImage H_TO_H1H2_2(final FImage in) { 888 final int width = in.getWidth(); 889 final int height = in.getHeight(); 890 891 final MBFImage out = new MBFImage(width, height, ColourSpace.H1H2_2); 892 893 final float[][] H = in.pixels; 894 895 final float[][] H1 = out.getBand(0).pixels; 896 final float[][] H2 = out.getBand(1).pixels; 897 898 for (int y = 0; y < height; y++) { 899 for (int x = 0; x < width; x++) { 900 if (H[y][x] > 0.3333333333F) 901 { 902 H2[y][x] = ((H[y][x] - 0.3333333333F) / 0.6666666666F); 903 if (H[y][x] > 0.6666666666F) 904 H1[y][x] = ((H[y][x] - 0.6666666666F) / 0.5F); 905 else 906 H1[y][x] = (1 - (H[y][x] - 0.1666666666F) / 0.5F); 907 } 908 else 909 { 910 H2[y][x] = (1 - H[y][x] / 0.3333333333F); 911 if (H[y][x] > 0.1666666666F) 912 H1[y][x] = (1 - (H[y][x] - 0.1666666666F) / 0.5F); 913 else 914 H1[y][x] = ((2.0F / 3.0F) + H[y][x] / 0.5F); 915 } 916 } 917 } 918 919 return out; 920 } 921 922 /** 923 * Convert H2SV to HSV VARIANT 1 using the simple formulation from: 924 * http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space Assumes H1 925 * and H2 are 0..1. Output H is 0..1 926 * 927 * @param in 928 * H2SV image 929 * @return HSV image 930 */ 931 public static MBFImage H2SV_TO_HSV_Simple(final MBFImage in) 932 { 933 final MBFImage out = new MBFImage(in.getWidth(), in.getHeight(), ColourSpace.HSV); 934 final float[][] H = out.getBand(0).pixels; 935 final float[][] H1 = in.getBand(0).pixels; 936 final float[][] H2 = in.getBand(1).pixels; 937 938 final int width = in.getWidth(); 939 final int height = in.getHeight(); 940 for (int y = 0; y < height; y++) { 941 for (int x = 0; x < width; x++) { 942 if (H1[y][x] > 0.5) 943 if (H2[y][x] > 0.5) 944 H[y][x] = 0.5f * H1[y][x] - 0.25f; 945 else 946 H[y][x] = 0.25f + 0.5f * (1f - H1[y][x]); 947 else if (H2[y][x] <= 0.5) 948 H[y][x] = 0.25f + 0.5f * (1f - H1[y][x]); 949 else 950 H[y][x] = 0.75f + 0.5f * H1[y][x]; 951 } 952 } 953 954 out.addBand(in.getBand(2)); // S 955 out.addBand(in.getBand(3)); // V 956 return out; 957 } 958 959 /** 960 * Convert H2SV to HSV VARIANT 2 using the simple formulation from: 961 * http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space Assumes H1 962 * and H2 are 0..1. Output H is 0..1 963 * 964 * @param in 965 * H2SV2 image 966 * @return HSV image 967 */ 968 public static MBFImage H2SV2_TO_HSV_Simple(final MBFImage in) 969 { 970 final MBFImage out = new MBFImage(in.getWidth(), in.getHeight(), ColourSpace.HSV); 971 final float[][] H = out.getBand(0).pixels; 972 final float[][] H1 = in.getBand(0).pixels; 973 final float[][] H2 = in.getBand(1).pixels; 974 975 final int width = in.getWidth(); 976 final int height = in.getHeight(); 977 for (int y = 0; y < height; y++) { 978 for (int x = 0; x < width; x++) { 979 if (H1[y][x] > 2.0 / 3.0) 980 if (H2[y][x] > 0.5) 981 H[y][x] = 0.5f * H1[y][x] - 1 / 3f; 982 else 983 H[y][x] = 1 / 6f + 0.5f * (1f - H1[y][x]); 984 else if (H2[y][x] <= 0.5) 985 H[y][x] = 1 / 3f + 0.5f * (1f - H1[y][x]); 986 else 987 H[y][x] = 2 / 3f + 0.5f * H1[y][x]; 988 } 989 } 990 991 out.addBand(in.getBand(2)); // S 992 out.addBand(in.getBand(3)); // V 993 return out; 994 } 995 996 /** 997 * Convert HSV to H2SV VARIANT 2 using the formulation from: 998 * http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space 999 * 1000 * @param in 1001 * HSV image 1002 * @return H2SV_2 image 1003 */ 1004 public static MBFImage HSV_TO_H2SV_2(final MBFImage in) { 1005 if (in.colourSpace != ColourSpace.HSV) 1006 throw new IllegalArgumentException("HSV colourspace is required"); 1007 1008 final MBFImage out = Transforms.H_TO_H1H2_2(in.getBand(0)); 1009 out.addBand(in.getBand(1)); // S 1010 out.addBand(in.getBand(2)); // V 1011 out.colourSpace = ColourSpace.H2SV_2; 1012 return out; 1013 } 1014 1015 /** 1016 * Convert RGB to H2SV2 VARIANT 2 1017 * 1018 * @param in 1019 * RGB or RGBA image 1020 * @return H2SV_2 image 1021 */ 1022 public static MBFImage RGB_TO_H2SV_2(final MBFImage in) { 1023 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 1024 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 1025 1026 final MBFImage HSV = Transforms.RGB_TO_HSV(in); 1027 1028 return Transforms.HSV_TO_H2SV_2(HSV); 1029 } 1030 1031 /** 1032 * Convert RGB to H2S VARIANT 2 1033 * 1034 * @param in 1035 * RGB or RGBA image 1036 * @return H2S image 1037 */ 1038 public static MBFImage RGB_TO_H2S_2(final MBFImage in) { 1039 if (in.colourSpace != ColourSpace.RGB && in.colourSpace != ColourSpace.RGBA) 1040 throw new IllegalArgumentException("RGB or RGBA colourspace is required"); 1041 1042 final MBFImage H2S = Transforms.RGB_TO_H2SV_2(in); 1043 H2S.deleteBand(3); // remove V 1044 H2S.colourSpace = ColourSpace.H2S_2; 1045 return H2S; 1046 } 1047 1048 /** 1049 * Intensity normalisation 1050 * 1051 * @param in 1052 * RGB image 1053 * @return normalised RGB image 1054 */ 1055 public static MBFImage RGB_TO_RGB_NORMALISED(final MBFImage in) { 1056 final int r = in.getHeight(); 1057 final int c = in.getWidth(); 1058 final MBFImage out = new MBFImage(c, r, ColourSpace.RGB_INTENSITY_NORMALISED); 1059 final float max = (float) Math.sqrt(3); 1060 1061 final float grey = (1.0f / max); 1062 for (int j = 0; j < r; j++) { 1063 for (int i = 0; i < c; i++) { 1064 final Float[] pixin = in.getPixel(i, j); 1065 1066 if (pixin[0] == pixin[1] && pixin[1] == pixin[2] && pixin[0] == 0.0) { 1067 out.setPixel(i, j, new Float[] { grey, grey, grey }); 1068 } 1069 else if (pixin[0] == pixin[1] && pixin[1] == pixin[2] && pixin[0] == 1.0) { 1070 out.setPixel(i, j, new Float[] { grey, grey, grey }); 1071 } 1072 else { 1073 final float length = (float) Math.sqrt(pixin[0] * pixin[0] + pixin[1] * pixin[1] + pixin[2] 1074 * pixin[2]); 1075 out.setPixel(i, j, new Float[] { (pixin[0] / length), (pixin[1] / length), (pixin[2] / length) }); 1076 } 1077 1078 } 1079 } 1080 1081 return out; 1082 } 1083 1084 /** 1085 * CIE_XYZ color space transform from RGB. Uses inverse sRGB companding for 1086 * energy normalisation and assumes a D65 whitepoint. 1087 * 1088 * Transform described here: 1089 * http://www.brucelindbloom.com/Eqn_RGB_to_XYZ.html 1090 * 1091 * @param in 1092 * input RGB image 1093 * @return CIEXYZ image 1094 */ 1095 public static MBFImage RGB_TO_CIEXYZ(final MBFImage in) { 1096 final int height = in.getHeight(); 1097 final int width = in.getWidth(); 1098 final MBFImage out = new MBFImage(width, height, ColourSpace.CIE_XYZ); 1099 1100 final FImage Rb = in.getBand(0); 1101 final FImage Gb = in.getBand(1); 1102 final FImage Bb = in.getBand(2); 1103 1104 final FImage Xb = out.getBand(0); 1105 final FImage Yb = out.getBand(1); 1106 final FImage Zb = out.getBand(2); 1107 1108 for (int y = 0; y < height; y++) { 1109 for (int x = 0; x < width; x++) { 1110 final float R = Rb.pixels[y][x]; 1111 final float G = Gb.pixels[y][x]; 1112 final float B = Bb.pixels[y][x]; 1113 1114 // inverse sRGB companding 1115 final double r = (R <= 0.04045) ? (R / 12.92) : (Math.pow((R + 0.055) / 1.055, 2.4)); 1116 final double g = (G <= 0.04045) ? (G / 12.92) : (Math.pow((G + 0.055) / 1.055, 2.4)); 1117 final double b = (B <= 0.04045) ? (B / 12.92) : (Math.pow((B + 0.055) / 1.055, 2.4)); 1118 1119 // XYZ linear transform 1120 Xb.pixels[y][x] = (float) (r * 0.4124564 + g * 0.3575761 + b * 0.1804375); 1121 Yb.pixels[y][x] = (float) (r * 0.2126729 + g * 0.7151522 + b * 0.0721750); 1122 Zb.pixels[y][x] = (float) (r * 0.0193339 + g * 0.1191920 + b * 0.9503041); 1123 } 1124 } 1125 1126 return out; 1127 } 1128 1129 /** 1130 * CIE_XYZ color space transform to RGB. Uses sRGB companding for energy 1131 * normalisation and assumes a D65 whitepoint. 1132 * 1133 * Transform described here: 1134 * http://www.brucelindbloom.com/Eqn_XYZ_to_RGB.html 1135 * 1136 * @param in 1137 * input CIEXYZ image 1138 * @return RGB image 1139 */ 1140 public static MBFImage CIEXYZ_TO_RGB(final MBFImage in) { 1141 return Transforms.CIEXYZ_TO_RGB(in, false); 1142 } 1143 1144 /** 1145 * CIE_XYZ color space transform to RGB. Uses sRGB companding for energy 1146 * normalisation and assumes a D65 whitepoint. 1147 * 1148 * Transform described here: 1149 * http://www.brucelindbloom.com/Eqn_XYZ_to_RGB.html 1150 * 1151 * @param in 1152 * input CIEXYZ image 1153 * @param inPlace 1154 * if true then input image is modified, rather than creating a 1155 * new image 1156 * @return RGB image 1157 */ 1158 public static MBFImage CIEXYZ_TO_RGB(final MBFImage in, final boolean inPlace) { 1159 final int height = in.getHeight(); 1160 final int width = in.getWidth(); 1161 1162 MBFImage out; 1163 if (inPlace) { 1164 out = in; 1165 out.colourSpace = ColourSpace.RGB; 1166 } else { 1167 out = new MBFImage(width, height, ColourSpace.RGB); 1168 } 1169 1170 final FImage Xb = in.getBand(0); 1171 final FImage Yb = in.getBand(1); 1172 final FImage Zb = in.getBand(2); 1173 1174 final FImage Rb = out.getBand(0); 1175 final FImage Gb = out.getBand(1); 1176 final FImage Bb = out.getBand(2); 1177 1178 for (int y = 0; y < height; y++) { 1179 for (int x = 0; x < width; x++) { 1180 final float X = Xb.pixels[y][x]; 1181 final float Y = Yb.pixels[y][x]; 1182 final float Z = Zb.pixels[y][x]; 1183 1184 // XYZ to linear rgb 1185 final double r = X * 3.2404542 + Y * -1.5371385 + Z * -0.4985314; 1186 final double g = X * -0.9692660 + Y * 1.8760108 + Z * 0.0415560; 1187 final double b = X * 0.0556434 + Y * -0.2040259 + Z * 1.0572252; 1188 1189 // sRGB companding 1190 Rb.pixels[y][x] = (float) ((r <= 0.0031308) ? (r * 12.92) : (1.055 * Math.pow(r, 1 / 2.4) - 0.055)); 1191 Gb.pixels[y][x] = (float) ((g <= 0.0031308) ? (g * 12.92) : (1.055 * Math.pow(g, 1 / 2.4) - 0.055)); 1192 Bb.pixels[y][x] = (float) ((b <= 0.0031308) ? (b * 12.92) : (1.055 * Math.pow(b, 1 / 2.4) - 0.055)); 1193 } 1194 } 1195 1196 return out; 1197 } 1198 1199 /** 1200 * Convert CIEXYZ to CIELab. See 1201 * http://www.brucelindbloom.com/Eqn_XYZ_to_Lab.html 1202 * 1203 * @param input 1204 * input image 1205 * @return converted image 1206 */ 1207 public static MBFImage CIEXYZ_TO_CIELab(final MBFImage input) { 1208 return Transforms.CIEXYZ_TO_CIELab(input, false); 1209 } 1210 1211 /** 1212 * Convert CIEXYZ to CIELab. See 1213 * http://www.brucelindbloom.com/Eqn_XYZ_to_Lab.html 1214 * 1215 * @param input 1216 * input image 1217 * @param inPlace 1218 * if true then input image is modified, rather than creating a 1219 * new image 1220 * @return converted image 1221 */ 1222 public static MBFImage CIEXYZ_TO_CIELab(final MBFImage input, final boolean inPlace) { 1223 return Transforms.CIEXYZ_TO_CIELab(input, inPlace, false); 1224 } 1225 1226 private static MBFImage CIEXYZ_TO_CIELab(final MBFImage input, final boolean inPlace, final boolean norm) { 1227 final double epsilon = 0.008856; // actual CIE standard 1228 final double kappa = 903.3; // actual CIE standard 1229 1230 final double Xr = 0.950456; // reference white 1231 final double Yr = 1.0; // reference white 1232 final double Zr = 1.088754; // reference white 1233 1234 final int height = input.getHeight(); 1235 final int width = input.getWidth(); 1236 1237 MBFImage out; 1238 if (inPlace) { 1239 out = input; 1240 out.colourSpace = ColourSpace.CIE_Lab; 1241 } else { 1242 out = new MBFImage(width, height, ColourSpace.CIE_Lab); 1243 } 1244 1245 final FImage Xb = input.getBand(0); 1246 final FImage Yb = input.getBand(1); 1247 final FImage Zb = input.getBand(2); 1248 1249 final FImage Lb = out.getBand(0); 1250 final FImage ab = out.getBand(1); 1251 final FImage bb = out.getBand(2); 1252 1253 final float Lscale = norm ? 1f / 100f : 1; 1254 final float ascale = norm ? 1f / 256f : 1; 1255 final float bscale = norm ? 1f / 256f : 1; 1256 final float abdelta = norm ? 127 : 0; 1257 1258 for (int y = 0; y < height; y++) { 1259 for (int x = 0; x < width; x++) { 1260 final float X = Xb.pixels[y][x]; 1261 final float Y = Yb.pixels[y][x]; 1262 final float Z = Zb.pixels[y][x]; 1263 1264 final double xr = X / Xr; 1265 final double yr = Y / Yr; 1266 final double zr = Z / Zr; 1267 1268 final double fx = (xr > epsilon) ? (Math.pow(xr, 1.0 / 3.0)) : ((kappa * xr + 16.0) / 116.0); 1269 final double fy = (yr > epsilon) ? (Math.pow(yr, 1.0 / 3.0)) : ((kappa * yr + 16.0) / 116.0); 1270 final double fz = (zr > epsilon) ? (Math.pow(zr, 1.0 / 3.0)) : ((kappa * zr + 16.0) / 116.0); 1271 1272 Lb.pixels[y][x] = ((float) (116.0 * fy - 16.0)) * Lscale; 1273 ab.pixels[y][x] = ((float) (500.0 * (fx - fy)) + abdelta) * ascale; 1274 bb.pixels[y][x] = ((float) (200.0 * (fy - fz)) + abdelta) * bscale; 1275 } 1276 } 1277 1278 return out; 1279 } 1280 1281 /** 1282 * Convert RGB to CIE Lab. See <a 1283 * href="http://www.brucelindbloom.com/index.html?Math.html"> 1284 * http://www.brucelindbloom.com/index.html?Math.html</a> 1285 * 1286 * Conversion goes from RGB->XYZ->Lab 1287 * 1288 * @param input 1289 * input RGB image 1290 * @return transformed CIE Lab image 1291 */ 1292 public static MBFImage RGB_TO_CIELab(final MBFImage input) { 1293 return Transforms.CIEXYZ_TO_CIELab(Transforms.RGB_TO_CIEXYZ(input), true); 1294 } 1295 1296 /** 1297 * Convert CIELab to CIEXYZ. See <a 1298 * href="http://www.brucelindbloom.com/Eqn_Lab_to_XYZ.html"> 1299 * http://www.brucelindbloom.com/Eqn_Lab_to_XYZ.html</a> 1300 * 1301 * @param input 1302 * input image 1303 * @return converted image 1304 */ 1305 public static MBFImage CIELab_TO_CIEXYZ(final MBFImage input) { 1306 return Transforms.CIELab_TO_CIEXYZ(input, false); 1307 } 1308 1309 private static MBFImage CIELab_TO_CIEXYZ(final MBFImage input, final boolean norm) { 1310 final double epsilon = 0.008856; // actual CIE standard 1311 final double kappa = 903.3; // actual CIE standard 1312 1313 final double Xr = 0.950456; // reference white 1314 final double Yr = 1.0; // reference white 1315 final double Zr = 1.088754; // reference white 1316 1317 final int height = input.getHeight(); 1318 final int width = input.getWidth(); 1319 1320 final MBFImage out = new MBFImage(width, height, ColourSpace.CIE_XYZ); 1321 1322 final FImage Lb = input.getBand(0); 1323 final FImage ab = input.getBand(1); 1324 final FImage bb = input.getBand(2); 1325 1326 final FImage Xb = out.getBand(0); 1327 final FImage Yb = out.getBand(1); 1328 final FImage Zb = out.getBand(2); 1329 1330 final float Lscale = norm ? 100 : 1; 1331 final float ascale = norm ? 256 : 1; 1332 final float bscale = norm ? 256 : 1; 1333 final float abdelta = norm ? -127 : 0; 1334 1335 for (int y = 0; y < height; y++) { 1336 for (int x = 0; x < width; x++) { 1337 final float L = (Lb.pixels[y][x] * Lscale); 1338 final float a = (ab.pixels[y][x] * ascale) + abdelta; 1339 final float b = (bb.pixels[y][x] * bscale) + abdelta; 1340 1341 final double fy = (L + 16) / 116; 1342 final double fx = a / 500 + fy; 1343 final double fz = fy - (b / 200); 1344 1345 final double fx3 = fx * fx * fx; 1346 final double fz3 = fz * fz * fz; 1347 1348 final double xr = (fx3 > epsilon) ? fx3 : (116 * fx - 16) / kappa; 1349 final double yr = (L > kappa * epsilon) ? Math.pow((L + 16) / 116, 3) : L / kappa; 1350 final double zr = (fz3 > epsilon) ? fz3 : (116 * fz - 16) / kappa; 1351 1352 Xb.pixels[y][x] = (float) (Xr * xr); 1353 Yb.pixels[y][x] = (float) (Yr * yr); 1354 Zb.pixels[y][x] = (float) (Zr * zr); 1355 } 1356 } 1357 1358 return out; 1359 } 1360 1361 /** 1362 * Convert CIE Lab to RGB. See <a 1363 * href="http://www.brucelindbloom.com/index.html?Math.html"> 1364 * http://www.brucelindbloom.com/index.html?Math.html</a> 1365 * 1366 * Conversion goes from Lab->XYZ->RGB 1367 * 1368 * @param input 1369 * input CIE Lab image 1370 * @return transformed RGB image 1371 */ 1372 public static MBFImage CIELab_TO_RGB(final MBFImage input) { 1373 return Transforms.CIEXYZ_TO_RGB(Transforms.CIELab_TO_CIEXYZ(input), true); 1374 } 1375 1376 /** 1377 * Convert CIEXYZ to CIELab and normalise the resultant L, a & b values to 1378 * 0..1. See <a href="http://www.brucelindbloom.com/Eqn_XYZ_to_Lab.html"> 1379 * http://www.brucelindbloom.com/Eqn_XYZ_to_Lab.html</a>. 1380 * 1381 * @param input 1382 * input image 1383 * @return converted image 1384 */ 1385 public static MBFImage RGB_TO_CIELabNormalised(final MBFImage input) { 1386 return Transforms.CIEXYZ_TO_CIELab(Transforms.RGB_TO_CIEXYZ(input), true, true); 1387 } 1388 1389 /** 1390 * Convert normalised CIE Lab to RGB. The L, a & b values are in 0..1. See 1391 * <a href="http://www.brucelindbloom.com/index.html?Math.html"> 1392 * http://www.brucelindbloom.com/index.html?Math.html</a> 1393 * 1394 * Conversion goes from Lab->XYZ->RGB 1395 * 1396 * @param input 1397 * input CIE Lab image 1398 * @return transformed RGB image 1399 */ 1400 public static MBFImage CIELabNormalised_TO_RGB(final MBFImage input) { 1401 return Transforms.CIEXYZ_TO_RGB(Transforms.CIELab_TO_CIEXYZ(input, true), true); 1402 } 1403 1404 /** 1405 * Convert CIEXYZ to CIELUV (CIE 1976) See 1406 * http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html 1407 * 1408 * @param input 1409 * input image 1410 * @return converted image 1411 */ 1412 public static MBFImage CIEXYZ_TO_CIELUV(final MBFImage input) { 1413 return Transforms.CIEXYZ_TO_CIELUV(input, false); 1414 } 1415 1416 /** 1417 * Convert CIEXYZ to CIELUV (CIE 1976) See 1418 * http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html 1419 * 1420 * @param input 1421 * input image 1422 * @param inPlace 1423 * if true then input image is modified, rather than creating a 1424 * new image 1425 * @return converted image 1426 */ 1427 public static MBFImage CIEXYZ_TO_CIELUV(final MBFImage input, final boolean inPlace) { 1428 final int width = input.getWidth(); 1429 final int height = input.getHeight(); 1430 1431 MBFImage out; 1432 if (inPlace) { 1433 out = input; 1434 out.colourSpace = ColourSpace.CIE_Luv; 1435 } else { 1436 out = new MBFImage(width, height, ColourSpace.CIE_Luv); 1437 } 1438 1439 final FImage Xb = input.getBand(0); 1440 final FImage Yb = input.getBand(1); 1441 final FImage Zb = input.getBand(2); 1442 1443 final FImage Lb = out.getBand(0); 1444 final FImage ub = out.getBand(1); 1445 final FImage vb = out.getBand(2); 1446 1447 final double Xr = 0.950456; // reference white 1448 final double Yr = 1.0; // reference white 1449 final double Zr = 1.088754; // reference white 1450 1451 final double epsilon = 0.008856; 1452 final double kappa = 903.3; 1453 1454 for (int r = 0; r < height; r++) { 1455 for (int c = 0; c < width; c++) { 1456 final float X = Xb.pixels[r][c]; 1457 final float Y = Yb.pixels[r][c]; 1458 final float Z = Zb.pixels[r][c]; 1459 1460 final double yr = Y / Yr; 1461 1462 float L; 1463 if (yr > epsilon) { 1464 L = (float) (116 * Math.cbrt(yr) - 16); 1465 } else { 1466 L = (float) (kappa * yr); 1467 } 1468 1469 final double up = (4 * X) / (X + 15 * Y + 3 * Z); 1470 final double urp = (float) ((4 * Xr) / (Xr + 15 * Yr + 3 * Zr)); 1471 final float u = (float) (13 * L * (up - urp)); 1472 1473 final double vp = (9 * Y) / (X + 15 * Y + 3 * Z); 1474 final double vrp = (9 * Yr) / (Xr + 15 * Yr + 3 * Zr); 1475 final float v = (float) (13 * L * (vp - vrp)); 1476 1477 Lb.pixels[r][c] = L; 1478 ub.pixels[r][c] = u; 1479 vb.pixels[r][c] = v; 1480 } 1481 } 1482 1483 return out; 1484 } 1485 1486 /** 1487 * Convert RGB to CIE LUV. See <a 1488 * href="http://www.brucelindbloom.com/index.html?Math.html"> 1489 * http://www.brucelindbloom.com/index.html?Math.html</a> 1490 * 1491 * Conversion goes from RGB->XYZ->LUV 1492 * 1493 * @param input 1494 * input RGB image 1495 * @return transformed CIE LUV image 1496 */ 1497 public static MBFImage RGB_TO_CIELUV(final MBFImage input) { 1498 return Transforms.CIEXYZ_TO_CIELUV(Transforms.RGB_TO_CIEXYZ(input), true); 1499 } 1500 1501 /** 1502 * Convert CIELUV to CIEXYZ. See <a 1503 * href="http://www.brucelindbloom.com/Eqn_Lab_to_XYZ.html"> 1504 * http://www.brucelindbloom.com/Eqn_Lab_to_XYZ.html</a> 1505 * 1506 * @param input 1507 * input image 1508 * @return converted image 1509 */ 1510 public static MBFImage CIELUV_TO_CIEXYZ(final MBFImage input) { 1511 final double epsilon = 0.008856; // actual CIE standard 1512 final double kappa = 903.3; // actual CIE standard 1513 1514 final double Xr = 0.950456; // reference white 1515 final double Yr = 1.0; // reference white 1516 final double Zr = 1.088754; // reference white 1517 1518 final int height = input.getHeight(); 1519 final int width = input.getWidth(); 1520 1521 final MBFImage out = new MBFImage(width, height, ColourSpace.CIE_XYZ); 1522 1523 final FImage Lb = input.getBand(0); 1524 final FImage ub = input.getBand(1); 1525 final FImage vb = input.getBand(2); 1526 1527 final FImage Xb = out.getBand(0); 1528 final FImage Yb = out.getBand(1); 1529 final FImage Zb = out.getBand(2); 1530 1531 for (int y = 0; y < height; y++) { 1532 for (int x = 0; x < width; x++) { 1533 final float L = Lb.pixels[y][x]; 1534 final float u = ub.pixels[y][x]; 1535 final float v = vb.pixels[y][x]; 1536 1537 double Y; 1538 if (L > kappa * epsilon) { 1539 Y = Yr * Math.pow(((L + 16) / 116), 3); 1540 } else { 1541 Y = Yr * L / kappa; 1542 } 1543 1544 final double u0 = (4 * Xr) / (Xr + 15 * Yr + 3 * Zr); 1545 final double v0 = (9 * Yr) / (Xr + 15 * Yr + 3 * Zr); 1546 1547 final double a = (1.0 / 3.0) * (((52 * L) / (u + 13 * L * u0)) - 1); 1548 final double b = -5 * Y; 1549 final double c = -1.0 / 3.0; 1550 final double d = Y * (((39 * L) / (v + 13 * L * v0)) - 5); 1551 1552 final double X = (d - b) / (a - c); 1553 final double Z = X * a + b; 1554 1555 Xb.pixels[y][x] = (float) X; 1556 Yb.pixels[y][x] = (float) Y; 1557 Zb.pixels[y][x] = (float) Z; 1558 } 1559 } 1560 1561 return out; 1562 } 1563 1564 /** 1565 * Convert CIE LUV to RGB. See <a 1566 * href="http://www.brucelindbloom.com/index.html?Math.html"> 1567 * http://www.brucelindbloom.com/index.html?Math.html</a> 1568 * 1569 * Conversion goes from LUV->XYZ->RGB 1570 * 1571 * @param input 1572 * input CIE LUV image 1573 * @return transformed RGB image 1574 */ 1575 public static MBFImage CIELUV_TO_RGB(final MBFImage input) { 1576 return Transforms.CIEXYZ_TO_RGB(Transforms.CIELUV_TO_CIEXYZ(input)); 1577 } 1578 1579 /** 1580 * Convert from RGB to YUV. Y is in the range [0, 1]; U is [-0.436, 0.436] 1581 * and V is [-0.615, 0.615]. 1582 * 1583 * @param input 1584 * input RGB image 1585 * @return transformed YUV image 1586 */ 1587 public static MBFImage RGB_TO_YUV(final MBFImage input) { 1588 return Transforms.RGB_TO_YUV(input, false, false); 1589 } 1590 1591 /** 1592 * Convert from RGB to normalised YUV. Y, U and V are all in the range [0, 1593 * 1]. 1594 * 1595 * @param input 1596 * input RGB image 1597 * @return transformed normalised YUV image 1598 */ 1599 public static MBFImage RGB_TO_YUVNormalised(final MBFImage input) { 1600 return Transforms.RGB_TO_YUV(input, false, true); 1601 } 1602 1603 /** 1604 * Convert from RGB to YUV. Conversion can be either in place or in a new 1605 * image. The U and V components can optionally be normalised to 0..1 or 1606 * alternatively take the standard ranges: U in [-0.436, 0.436] and V in 1607 * [-0.615, 0.615]. 1608 * 1609 * @param input 1610 * input RGB image 1611 * @param inPlace 1612 * if true the output will overwrite the input; otherwise a new 1613 * image is created. 1614 * @param norm 1615 * true if the U and V values are normalised to [0, 1]. 1616 * @return transformed YUV image 1617 */ 1618 public static MBFImage RGB_TO_YUV(final MBFImage input, final boolean inPlace, final boolean norm) { 1619 final int width = input.getWidth(); 1620 final int height = input.getHeight(); 1621 MBFImage out = null; 1622 1623 if (inPlace) { 1624 out = input; 1625 out.colourSpace = norm ? ColourSpace.YUV_Norm : ColourSpace.YUV; 1626 } else { 1627 out = new MBFImage(width, height, norm ? ColourSpace.YUV_Norm : ColourSpace.YUV); 1628 } 1629 1630 final float[][] Rb = input.getBand(0).pixels; 1631 final float[][] Gb = input.getBand(1).pixels; 1632 final float[][] Bb = input.getBand(2).pixels; 1633 1634 final float[][] Yb = out.getBand(0).pixels; 1635 final float[][] Ub = out.getBand(1).pixels; 1636 final float[][] Vb = out.getBand(2).pixels; 1637 1638 final double Wr = 0.299; 1639 final double Wb = 0.114; 1640 final double Wg = 0.587; 1641 final double Umax = 0.436; 1642 final double Vmax = 0.615; 1643 1644 final double deltaU = norm ? -Umax : 0; 1645 final double deltaV = norm ? -Vmax : 0; 1646 final double Unorm = norm ? 2 * Umax : 1; 1647 final double Vnorm = norm ? 2 * Vmax : 1; 1648 1649 for (int y = 0; y < height; y++) { 1650 for (int x = 0; x < width; x++) { 1651 final double R = Rb[y][x]; 1652 final double G = Gb[y][x]; 1653 final double B = Bb[y][x]; 1654 1655 final double Y = Wr * R + Wg * G + Wb * B; 1656 double U = Umax * ((B - Y) / (1.0 - Wb)); 1657 double V = Vmax * ((R - Y) / (1.0 - Wr)); 1658 1659 U = (U - deltaU) / Unorm; 1660 V = (V - deltaV) / Vnorm; 1661 1662 Yb[y][x] = (float) Y; 1663 Ub[y][x] = (float) U; 1664 Vb[y][x] = (float) V; 1665 } 1666 } 1667 1668 return out; 1669 } 1670 1671 /** 1672 * Convert from normalised YUV to RGB. 1673 * 1674 * @param input 1675 * input normalised YUV image 1676 * @return transformed RGB image 1677 */ 1678 public static MBFImage YUVNormalised_TO_RGB(final MBFImage input) { 1679 return Transforms.YUV_TO_RGB(input, false, true); 1680 } 1681 1682 /** 1683 * Convert from YUV to RGB. 1684 * 1685 * @param input 1686 * input YUV image 1687 * @return transformed RGB image 1688 */ 1689 public static MBFImage YUV_TO_RGB(final MBFImage input) { 1690 return Transforms.YUV_TO_RGB(input, false, false); 1691 } 1692 1693 /** 1694 * Convert from YUV to RGB. Conversion can be either in place or in a new 1695 * image. The U and V components are either in [0, 1] (norm = true) or 1696 * alternatively take the standard ranges (norm = false): U in [-0.436, 1697 * 0.436] and V in [-0.615, 0.615]. 1698 * 1699 * @param input 1700 * input RGB image 1701 * @param inPlace 1702 * if true the output will overwrite the input; otherwise a new 1703 * image is created. 1704 * @param norm 1705 * true if the U and V values should be normalised to [0, 1]. 1706 * @return transformed YUV image 1707 */ 1708 public static MBFImage YUV_TO_RGB(final MBFImage input, final boolean inPlace, final boolean norm) { 1709 final int width = input.getWidth(); 1710 final int height = input.getHeight(); 1711 MBFImage out = null; 1712 1713 if (inPlace) { 1714 out = input; 1715 out.colourSpace = ColourSpace.RGB; 1716 } else { 1717 out = new MBFImage(width, height, ColourSpace.RGB); 1718 } 1719 1720 final float[][] Yb = input.getBand(0).pixels; 1721 final float[][] Ub = input.getBand(1).pixels; 1722 final float[][] Vb = input.getBand(2).pixels; 1723 1724 final float[][] Rb = out.getBand(0).pixels; 1725 final float[][] Gb = out.getBand(1).pixels; 1726 final float[][] Bb = out.getBand(2).pixels; 1727 1728 final double Wr = 0.299; 1729 final double Wb = 0.114; 1730 final double Wg = 0.587; 1731 final double Umax = 0.436; 1732 final double Vmax = 0.615; 1733 1734 final double deltaU = norm ? -Umax : 0; 1735 final double deltaV = norm ? -Vmax : 0; 1736 final double Unorm = norm ? 2 * Umax : 1; 1737 final double Vnorm = norm ? 2 * Vmax : 1; 1738 1739 for (int y = 0; y < height; y++) { 1740 for (int x = 0; x < width; x++) { 1741 final double Y = Yb[y][x]; 1742 final double U = (Ub[y][x] * Unorm) + deltaU; 1743 final double V = (Vb[y][x] * Vnorm) + deltaV; 1744 1745 final double R = Y + V * ((1 - Wr) / Vmax); 1746 final double G = Y - U * ((Wb * (1 - Wb)) / (Umax * Wg)) - V * ((Wr * (1 - Wr)) / (Vmax * Wg)); 1747 final double B = Y + U * ((1 - Wb) / Umax); 1748 1749 Rb[y][x] = (float) R; 1750 Gb[y][x] = (float) G; 1751 Bb[y][x] = (float) B; 1752 } 1753 } 1754 1755 return out; 1756 } 1757 1758 /** 1759 * Converts a Kelvin colour temperature the RGB black body equivalent. Note 1760 * that the output values are in the range 0..1. 1761 * 1762 * @see "http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/" 1763 * 1764 * @param temperature 1765 * the colour temperature. 1766 * @return RGB value (0..1) 1767 */ 1768 public static double[] kelvinToRGB(double temperature) 1769 { 1770 // Start with a temperature, in Kelvin, somewhere between 1000 and 1771 // 40000. 1772 // (Other values may work, but I can't make any promises about the 1773 // quality 1774 // of the algorithm's estimates above 40000 K.) Note also that the 1775 // temperature 1776 // and color variables need to be declared as floating-point. 1777 1778 temperature = temperature / 100d; 1779 1780 double r = 0; 1781 if (temperature <= 66) 1782 r = 255; 1783 else 1784 { 1785 r = temperature - 60; 1786 r = 329.698727446 * Math.pow(r, -0.1332047592); 1787 if (r < 0) 1788 r = 0; 1789 if (r > 255) 1790 r = 255; 1791 } 1792 1793 double g = 0; 1794 if (temperature <= 66) 1795 { 1796 g = temperature; 1797 g = 99.4708025861 * Math.log(g) - 161.1195681661; 1798 if (g < 0) 1799 g = 0; 1800 if (g > 255) 1801 g = 255; 1802 } 1803 else 1804 { 1805 g = temperature - 60; 1806 g = 288.1221695283 * Math.pow(g, -0.0755148492); 1807 if (g < 0) 1808 g = 0; 1809 if (g > 255) 1810 g = 255; 1811 } 1812 1813 double b = 0; 1814 if (temperature >= 66) 1815 b = 255; 1816 else 1817 { 1818 if (temperature <= 19) 1819 b = 0; 1820 else 1821 { 1822 b = temperature - 10; 1823 b = 138.5177312231 * Math.log(b) - 305.0447927307; 1824 if (b < 0) 1825 b = 0; 1826 if (b > 255) 1827 b = 255; 1828 } 1829 } 1830 1831 return new double[] { r / 255d, g / 255d, b / 255d }; 1832 } 1833 1834 /** 1835 * Correct colour temperature using the method documented at Tanner Helland. 1836 * Calculated the black body colour for the given temperature and alpha 1837 * blends that with a constant luminance with the original image at the 1838 * given strength. Only works on RGB images. Side affects the incoming 1839 * image. 1840 * 1841 * @param image 1842 * @param colourTemperature 1843 * The colour temperature. 1844 * @param strength 1845 * The strength of colour correction 1846 * 1847 * @see "http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/" 1848 * @return The affected image. 1849 */ 1850 public static MBFImage colourTemperatureCorrection(final MBFImage image, 1851 final double colourTemperature, final double strength) 1852 { 1853 if (image.colourSpace != ColourSpace.RGB) 1854 throw new IllegalArgumentException("Colour correction only available for RGB images. " + 1855 "Try using the colour transforms to convert to RGB."); 1856 1857 // Get the black body colour for the given temperature. 1858 final double rgb[] = Transforms.kelvinToRGB(colourTemperature); 1859 1860 final float[] pix = new float[image.numBands()]; 1861 final float[] oPix = new float[image.numBands()]; 1862 for (int y = 0; y < image.getHeight(); y++) 1863 { 1864 for (int x = 0; x < image.getWidth(); x++) 1865 { 1866 for (int b = 0; b < image.numBands(); b++) 1867 { 1868 float f = image.getBand(b).pixels[y][x]; 1869 oPix[b] = f; 1870 f = (float) (f * strength + rgb[b] * (1f - strength)); 1871 pix[b] = f; 1872 } 1873 1874 // pix contains the alpha blended original pixels. Calculate HSL 1875 Transforms.RGB_TO_HSL(pix, pix); 1876 1877 // subsititue the luminance of original pixels 1878 pix[2] = (ArrayUtils.maxValue(oPix) + ArrayUtils.minValue(oPix)) / 2f; 1879 1880 // Transform back to RGB 1881 Transforms.HSL_TO_RGB(pix, pix); 1882 1883 for (int b = 0; b < image.numBands(); b++) 1884 image.getBand(b).pixels[y][x] = pix[b]; 1885 } 1886 } 1887 1888 return image; 1889 } 1890}