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-&gt;XYZ-&gt;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-&gt;XYZ-&gt;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 &amp; 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 &amp; 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-&gt;XYZ-&gt;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-&gt;XYZ-&gt;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-&gt;XYZ-&gt;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}