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.MBFImage;
034
035/**
036 * Different colour space types with conversion methods.
037 * 
038 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
039 */
040public enum ColourSpace {
041        /**
042         * RGB colour space
043         */
044        RGB {
045                @Override
046                public MBFImage convertFromRGB(final MBFImage input) {
047                        return input;
048                }
049
050                @Override
051                public int getNumBands() {
052                        return 3;
053                }
054
055                @Override
056                public MBFImage convertToRGB(final MBFImage input) {
057                        return input;
058                }
059
060                @Override
061                public float computeIntensity(float[] colour) {
062                        return (colour[0] + colour[1] + colour[2]) / 3f;
063                }
064        },
065        /**
066         * HSV colour space
067         */
068        HSV {
069                @Override
070                public MBFImage convertFromRGB(final MBFImage input) {
071                        return Transforms.RGB_TO_HSV(input);
072                }
073
074                @Override
075                public int getNumBands() {
076                        return 3;
077                }
078
079                @Override
080                public MBFImage convertToRGB(final MBFImage input) {
081                        return Transforms.HSV_TO_RGB(input);
082                }
083
084                @Override
085                public float computeIntensity(float[] colour) {
086                        return colour[2];
087                }
088        },
089        /**
090         * HSI colour space
091         */
092        HSI {
093                @Override
094                public MBFImage convertFromRGB(final MBFImage input) {
095                        return Transforms.RGB_TO_HSI(input);
096                }
097
098                @Override
099                public int getNumBands() {
100                        return 3;
101                }
102
103                @Override
104                public MBFImage convertToRGB(final MBFImage input) {
105                        throw new UnsupportedOperationException("colour transform not implemented");
106                }
107
108                @Override
109                public float computeIntensity(float[] colour) {
110                        return colour[2];
111                }
112        },
113        /**
114         * H2SV colour space
115         * 
116         * @see Transforms#RGB_TO_H2SV
117         */
118        H2SV {
119                @Override
120                public MBFImage convertFromRGB(final MBFImage input) {
121                        return Transforms.RGB_TO_H2SV(input);
122                }
123
124                @Override
125                public int getNumBands() {
126                        return 4;
127                }
128
129                @Override
130                public MBFImage convertToRGB(final MBFImage input) {
131                        return Transforms.HSV_TO_RGB(Transforms.H2SV_TO_HSV_Simple(input));
132                }
133
134                @Override
135                public float computeIntensity(float[] colour) {
136                        return colour[3];
137                }
138        },
139        /**
140         * H2SV_2 colour space
141         * 
142         * @see Transforms#RGB_TO_H2SV_2
143         */
144        H2SV_2 {
145                @Override
146                public MBFImage convertFromRGB(final MBFImage input) {
147                        return Transforms.RGB_TO_H2SV_2(input);
148                }
149
150                @Override
151                public int getNumBands() {
152                        return 4;
153                }
154
155                @Override
156                public MBFImage convertToRGB(final MBFImage input) {
157                        return Transforms.HSV_TO_RGB(Transforms.H2SV2_TO_HSV_Simple(input));
158                }
159
160                @Override
161                public float computeIntensity(float[] colour) {
162                        return colour[3];
163                }
164        },
165        /**
166         * H2S colour space
167         * 
168         * @see Transforms#RGB_TO_H2S
169         */
170        H2S {
171                @Override
172                public MBFImage convertFromRGB(final MBFImage input) {
173                        return Transforms.RGB_TO_H2S(input);
174                }
175
176                @Override
177                public int getNumBands() {
178                        return 3;
179                }
180
181                @Override
182                public MBFImage convertToRGB(final MBFImage input) {
183                        throw new UnsupportedOperationException("colour transform not implemented");
184                }
185
186                @Override
187                public float computeIntensity(float[] colour) {
188                        return 0;
189                }
190        },
191        /**
192         * H2S_2 colour space
193         * 
194         * @see Transforms#RGB_TO_H2S_2
195         */
196        H2S_2 {
197                @Override
198                public MBFImage convertFromRGB(final MBFImage input) {
199                        return Transforms.RGB_TO_H2S_2(input);
200                }
201
202                @Override
203                public int getNumBands() {
204                        return 3;
205                }
206
207                @Override
208                public MBFImage convertToRGB(final MBFImage input) {
209                        throw new UnsupportedOperationException("colour transform not implemented");
210                }
211
212                @Override
213                public float computeIntensity(float[] colour) {
214                        return 0;
215                }
216        },
217        /**
218         * LUMINANCE colour space from averaging RGB
219         */
220        LUMINANCE_AVG {
221                @Override
222                public MBFImage convertFromRGB(final MBFImage input) {
223                        return new MBFImage(this, Transforms.calculateIntensity(input));
224                }
225
226                @Override
227                public int getNumBands() {
228                        return 1;
229                }
230
231                @Override
232                public MBFImage convertToRGB(final MBFImage input) {
233                        return new MBFImage(input.bands.get(0).clone(), input.bands.get(0).clone(), input.bands.get(0).clone());
234                }
235
236                @Override
237                public float computeIntensity(float[] colour) {
238                        return colour[0];
239                }
240        },
241        /**
242         * LUMINANCE colour space using NTSC perceptual weightings
243         */
244        LUMINANCE_NTSC {
245                @Override
246                public MBFImage convertFromRGB(final MBFImage input) {
247                        return new MBFImage(this, Transforms.calculateIntensityNTSC(input));
248                }
249
250                @Override
251                public int getNumBands() {
252                        return 1;
253                }
254
255                @Override
256                public MBFImage convertToRGB(final MBFImage input) {
257                        return new MBFImage(input.bands.get(0).clone(), input.bands.get(0).clone(), input.bands.get(0).clone());
258                }
259
260                @Override
261                public float computeIntensity(float[] colour) {
262                        return colour[0];
263                }
264        },
265        /**
266         * Hue colour space
267         */
268        HUE {
269                @Override
270                public MBFImage convertFromRGB(final MBFImage input) {
271                        return new MBFImage(this, Transforms.calculateHue(input));
272                }
273
274                @Override
275                public int getNumBands() {
276                        return 1;
277                }
278
279                @Override
280                public MBFImage convertToRGB(final MBFImage input) {
281                        return new MBFImage(input.bands.get(0).clone(), input.bands.get(0).clone(), input.bands.get(0).clone());
282                }
283
284                @Override
285                public float computeIntensity(float[] colour) {
286                        return 0;
287                }
288        },
289        /**
290         * Saturation colour space
291         */
292        SATURATION {
293                @Override
294                public MBFImage convertFromRGB(final MBFImage input) {
295                        return new MBFImage(this, Transforms.calculateSaturation(input));
296                }
297
298                @Override
299                public int getNumBands() {
300                        return 1;
301                }
302
303                @Override
304                public MBFImage convertToRGB(final MBFImage input) {
305                        return new MBFImage(input.bands.get(0).clone(), input.bands.get(0).clone(), input.bands.get(0).clone());
306                }
307
308                @Override
309                public float computeIntensity(float[] colour) {
310                        return 0;
311                }
312        },
313        /**
314         * Intensity normalised RGB colour space using normalisation
315         */
316        RGB_INTENSITY_NORMALISED {
317                @Override
318                public MBFImage convertFromRGB(final MBFImage input) {
319                        return Transforms.RGB_TO_RGB_NORMALISED(input);
320                }
321
322                @Override
323                public int getNumBands() {
324                        return 3;
325                }
326
327                @Override
328                public MBFImage convertToRGB(final MBFImage input) {
329                        return input;
330                }
331
332                @Override
333                public float computeIntensity(float[] colour) {
334                        return (colour[0] + colour[1] + colour[2]) / 3f;
335                }
336        },
337        /**
338         * A custom (unknown) colour space
339         */
340        CUSTOM {
341                @Override
342                public MBFImage convertFromRGB(final MBFImage input) {
343                        throw new UnsupportedOperationException("Cannot convert to the custom color-space");
344                }
345
346                @Override
347                public int getNumBands() {
348                        return 1;
349                }
350
351                @Override
352                public MBFImage convertToRGB(final MBFImage input) {
353                        throw new UnsupportedOperationException("colour transform not implemented");
354                }
355
356                @Override
357                public float computeIntensity(float[] colour) {
358                        return 0;
359                }
360        },
361        /**
362         * RGB with alpha colour space
363         */
364        RGBA {
365                @Override
366                public MBFImage convertFromRGB(final MBFImage input) {
367                        return new MBFImage(input.bands.get(0), input.bands.get(1), input.bands.get(2), new FImage(
368                                        input.bands.get(0).width, input.bands.get(0).height).addInplace(1.0f));
369                }
370
371                @Override
372                public int getNumBands() {
373                        return 4;
374                }
375
376                @Override
377                public MBFImage convertToRGB(final MBFImage input) {
378                        return new MBFImage(input.bands.get(0).clone(), input.bands.get(1).clone(), input.bands.get(2).clone());
379                }
380
381                @Override
382                public float computeIntensity(float[] colour) {
383                        return (colour[0] + colour[1] + colour[2]) / 3f;
384                }
385        },
386        /**
387         * HSL colour space
388         */
389        HSL {
390                @Override
391                public MBFImage convertFromRGB(final MBFImage input) {
392                        return Transforms.RGB_TO_HSL(input);
393                }
394
395                @Override
396                public MBFImage convertToRGB(final MBFImage input) {
397                        throw new UnsupportedOperationException("colour transform not implemented");
398                }
399
400                @Override
401                public int getNumBands() {
402                        return 3;
403                }
404
405                @Override
406                public float computeIntensity(float[] colour) {
407                        return colour[2];
408                }
409        },
410        /**
411         * HSY colour space
412         */
413        HSY {
414                @Override
415                public MBFImage convertFromRGB(final MBFImage input) {
416                        return Transforms.RGB_TO_HSY(input);
417                }
418
419                @Override
420                public MBFImage convertToRGB(final MBFImage input) {
421                        throw new UnsupportedOperationException("colour transform not implemented");
422                }
423
424                @Override
425                public int getNumBands() {
426                        return 3;
427                }
428
429                @Override
430                public float computeIntensity(float[] colour) {
431                        return colour[2];
432                }
433        },
434        /**
435         * HS colour space
436         */
437        HS {
438                @Override
439                public MBFImage convertFromRGB(final MBFImage input) {
440                        return Transforms.RGB_TO_HS(input);
441                }
442
443                @Override
444                public MBFImage convertToRGB(final MBFImage input) {
445                        throw new UnsupportedOperationException("colour transform not implemented");
446                }
447
448                @Override
449                public int getNumBands() {
450                        return 2;
451                }
452
453                @Override
454                public float computeIntensity(float[] colour) {
455                        return 0;
456                }
457        },
458        /**
459         * HS_2 colour space
460         */
461        HS_2 {
462                @Override
463                public MBFImage convertFromRGB(final MBFImage input) {
464                        return Transforms.RGB_TO_HS_2(input);
465                }
466
467                @Override
468                public MBFImage convertToRGB(final MBFImage input) {
469                        throw new UnsupportedOperationException("colour transform not implemented");
470                }
471
472                @Override
473                public int getNumBands() {
474                        return 2;
475                }
476
477                @Override
478                public float computeIntensity(float[] colour) {
479                        return 0;
480                }
481        },
482        /**
483         * H1H2 colour space (two component hue)
484         * 
485         * @see Transforms#H_TO_H1H2
486         */
487        H1H2 {
488                @Override
489                public MBFImage convertFromRGB(final MBFImage input) {
490                        return Transforms.H_TO_H1H2(Transforms.calculateHue(input));
491                }
492
493                @Override
494                public MBFImage convertToRGB(final MBFImage input) {
495                        throw new UnsupportedOperationException("colour transform not implemented");
496                }
497
498                @Override
499                public int getNumBands() {
500                        return 2;
501                }
502
503                @Override
504                public float computeIntensity(float[] colour) {
505                        return 0;
506                }
507        },
508        /**
509         * H1H2_2 colour space (two component hue)
510         * 
511         * @see Transforms#H_TO_H1H2_2
512         */
513        H1H2_2 {
514                @Override
515                public MBFImage convertFromRGB(final MBFImage input) {
516                        return Transforms.H_TO_H1H2_2(Transforms.calculateHue(input));
517                }
518
519                @Override
520                public MBFImage convertToRGB(final MBFImage input) {
521                        throw new UnsupportedOperationException("colour transform not implemented");
522                }
523
524                @Override
525                public int getNumBands() {
526                        return 2;
527                }
528
529                @Override
530                public float computeIntensity(float[] colour) {
531                        return 0;
532                }
533        },
534        /**
535         * CIE_XYZ color space, using the same transform as in OpenCV, which in turn
536         * came from:
537         * http://www.cica.indiana.edu/cica/faq/color_spaces/color.spaces.html
538         */
539        CIE_XYZ {
540                @Override
541                public MBFImage convertFromRGB(final MBFImage input) {
542                        return Transforms.RGB_TO_CIEXYZ(input);
543                }
544
545                @Override
546                public MBFImage convertToRGB(final MBFImage input) {
547                        return Transforms.CIEXYZ_TO_RGB(input);
548                }
549
550                @Override
551                public int getNumBands() {
552                        return 3;
553                }
554
555                @Override
556                public float computeIntensity(float[] colour) {
557                        return colour[1];
558                }
559        },
560        /**
561         * CIE_Lab color space, using the same transform as in OpenCV, which in turn
562         * came from: <a href=
563         * "http://www.cica.indiana.edu/cica/faq/color_spaces/color.spaces.html">
564         * http://www.cica.indiana.edu/cica/faq/color_spaces/color.spaces.html</a>
565         * <p>
566         * The resultant L values are in the range 0-100, and the a &amp; b values are
567         * in -127..127 inclusive.
568         * </p>
569         */
570        CIE_Lab {
571                @Override
572                public MBFImage convertFromRGB(final MBFImage input) {
573                        return Transforms.RGB_TO_CIELab(input);
574                }
575
576                @Override
577                public MBFImage convertToRGB(final MBFImage input) {
578                        return Transforms.CIELab_TO_RGB(input);
579                }
580
581                @Override
582                public int getNumBands() {
583                        return 3;
584                }
585
586                @Override
587                public float computeIntensity(float[] colour) {
588                        return colour[0];
589                }
590        },
591        /**
592         * Normalised CIE_Lab color space, using the same transform as in OpenCV,
593         * which in turn came from: <a href=
594         * "http://www.cica.indiana.edu/cica/faq/color_spaces/color.spaces.html">
595         * http://www.cica.indiana.edu/cica/faq/color_spaces/color.spaces.html</a>
596         * <p>
597         * The L, &amp; b values are normalised to 0..1.
598         * </p>
599         */
600        CIE_Lab_Norm {
601                @Override
602                public MBFImage convertFromRGB(final MBFImage input) {
603                        return Transforms.RGB_TO_CIELabNormalised(input);
604                }
605
606                @Override
607                public MBFImage convertToRGB(final MBFImage input) {
608                        return Transforms.CIELabNormalised_TO_RGB(input);
609                }
610
611                @Override
612                public int getNumBands() {
613                        return 3;
614                }
615
616                @Override
617                public float computeIntensity(float[] colour) {
618                        return colour[0];
619                }
620        },
621        /**
622         * CIE L*u*v* color space (CIE 1976).
623         * <p>
624         * The resultant L values are in the range 0-100, and the u &amp; v values are
625         * in -100..100 inclusive.
626         * </p>
627         */
628        CIE_Luv {
629
630                @Override
631                public MBFImage convertFromRGB(final MBFImage input) {
632                        return Transforms.RGB_TO_CIELUV(input);
633                }
634
635                @Override
636                public MBFImage convertToRGB(final MBFImage input) {
637                        return Transforms.CIELUV_TO_RGB(input);
638                }
639
640                @Override
641                public int getNumBands() {
642                        return 3;
643                }
644
645                @Override
646                public float computeIntensity(float[] colour) {
647                        return colour[0];
648                }
649        },
650        /**
651         * YUV
652         * <p>
653         * The resultant Y is in the range [0, 1]; U is [-0.436, 0.436] and V is
654         * [-0.615, 0.615].
655         */
656        YUV {
657                @Override
658                public MBFImage convertFromRGB(final MBFImage input) {
659                        return Transforms.RGB_TO_YUV(input);
660                }
661
662                @Override
663                public MBFImage convertToRGB(final MBFImage input) {
664                        return Transforms.YUV_TO_RGB(input);
665                }
666
667                @Override
668                public int getNumBands() {
669                        return 3;
670                }
671
672                @Override
673                public float computeIntensity(float[] colour) {
674                        return colour[2];
675                }
676        },
677        /**
678         * Normalised YUV.
679         * <p>
680         * Each of the Y, U and V values are in [0, 1].
681         * 
682         */
683        YUV_Norm {
684                @Override
685                public MBFImage convertFromRGB(final MBFImage input) {
686                        return Transforms.RGB_TO_YUVNormalised(input);
687                }
688
689                @Override
690                public MBFImage convertToRGB(final MBFImage input) {
691                        return Transforms.YUVNormalised_TO_RGB(input);
692                }
693
694                @Override
695                public int getNumBands() {
696                        return 3;
697                }
698
699                @Override
700                public float computeIntensity(float[] colour) {
701                        return colour[2];
702                }
703        },
704        /**
705         * Modified Opponent colour-space as used in <code>vlfeat</code>. Intensity
706         * is computed using the NTSC conversion. The intensity is also is added
707         * back to the other two components with a small multiplier for
708         * monochromatic regions.
709         * <p>
710         * The channel order is Intensity, O1 (r-g), O2 (r + g - 2b).
711         * 
712         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
713         */
714        MODIFIED_OPPONENT {
715                @Override
716                public MBFImage convertFromRGB(MBFImage input) {
717                        final FImage intensity = Transforms.calculateIntensityNTSC(input);
718
719                        final float alpha = 0.01f;
720                        final FImage rg = new FImage(input.getWidth(), input.getHeight());
721                        final FImage rb = new FImage(input.getWidth(), input.getHeight());
722
723                        final float[][] r = input.bands.get(0).pixels;
724                        final float[][] g = input.bands.get(1).pixels;
725                        final float[][] b = input.bands.get(2).pixels;
726
727                        for (int y = 0; y < input.getHeight(); y++) {
728                                for (int x = 0; x < input.getWidth(); x++) {
729                                        rg.pixels[y][x] = (float) (r[y][x] - g[y][x] / Math.sqrt(2) + alpha * intensity.pixels[y][x]);
730                                        rb.pixels[y][x] = (float) ((r[y][x] + g[y][x] - 2 * b[y][x]) / Math.sqrt(6) + alpha
731                                                        * intensity.pixels[y][x]);
732                                }
733                        }
734
735                        return new MBFImage(ColourSpace.MODIFIED_OPPONENT, intensity, rg, rb);
736                }
737
738                @Override
739                public MBFImage convertToRGB(MBFImage input) {
740                        throw new UnsupportedOperationException("Not supported (yet)");
741                }
742
743                @Override
744                public int getNumBands() {
745                        return 3;
746                }
747
748                @Override
749                public float computeIntensity(float[] colour) {
750                        return colour[0];
751                }
752        },
753        /**
754         * Basic opponent colour-space. Intensity is the mean of r, g and b.
755         * <p>
756         * The channel order is Intensity, O1 (r-g), O2 (r + g - 2b).
757         * 
758         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
759         */
760        OPPONENT {
761                @Override
762                public MBFImage convertFromRGB(MBFImage input) {
763                        final FImage intensity = Transforms.calculateIntensity(input);
764
765                        final FImage o1 = new FImage(input.getWidth(), input.getHeight());
766                        final FImage o2 = new FImage(input.getWidth(), input.getHeight());
767
768                        final float[][] r = input.bands.get(0).pixels;
769                        final float[][] g = input.bands.get(1).pixels;
770                        final float[][] b = input.bands.get(2).pixels;
771
772                        for (int y = 0; y < input.getHeight(); y++) {
773                                for (int x = 0; x < input.getWidth(); x++) {
774                                        o1.pixels[y][x] = (float) (r[y][x] - g[y][x] / Math.sqrt(2));
775                                        o2.pixels[y][x] = (float) ((r[y][x] + g[y][x] - 2 * b[y][x]) / Math.sqrt(6));
776                                }
777                        }
778
779                        return new MBFImage(ColourSpace.MODIFIED_OPPONENT, intensity, o1, o2);
780                }
781
782                @Override
783                public MBFImage convertToRGB(MBFImage input) {
784                        throw new UnsupportedOperationException("Not supported (yet)");
785                }
786
787                @Override
788                public int getNumBands() {
789                        return 3;
790                }
791
792                @Override
793                public float computeIntensity(float[] colour) {
794                        return colour[0];
795                }
796        };
797        /**
798         * Convert the given RGB image to the current colour space
799         * 
800         * @param input
801         *            RGB image
802         * @return image in the current colour space
803         */
804        public abstract MBFImage convertFromRGB(MBFImage input);
805        
806        /**
807         * Convert the given RGB image to the current colour space
808         * 
809         * @param input
810         *            RGB image
811         * @return image in the current colour space
812         */
813        public Float[] convertFromRGB(Float[] input){
814                MBFImage singlePixel = new MBFImage(1,1,ColourSpace.RGB);
815                singlePixel.setPixel(0, 0, input);
816                return convertFromRGB(singlePixel).getPixel(0,0);
817        };
818        
819        /**
820         * Convert the given RGB image to the current colour space
821         * 
822         * @param input
823         *            RGB image
824         * @return image in the current colour space
825         */
826        public Float[] convertToRGB(Float[] input){
827                MBFImage singlePixel = new MBFImage(1,1,this);
828                singlePixel.setPixel(0, 0, input);
829                return convertToRGB(singlePixel).getPixel(0,0);
830        };
831
832        /**
833         * Convert the image in this color space to RGB
834         * 
835         * @param input
836         *            image in this colour space
837         * @return RGB image
838         */
839        public abstract MBFImage convertToRGB(MBFImage input);
840
841        /**
842         * Convert the image to this colour space
843         * 
844         * @param input
845         *            an image
846         * @return image in this colour space
847         */
848        public MBFImage convert(final MBFImage input) {
849                return this.convertFromRGB(input.getColourSpace().convertToRGB(input));
850        }
851
852        /**
853         * Convert the image to the given colour space
854         * 
855         * @param image
856         *            the image
857         * @param cs
858         *            the target colour space
859         * @return the converted image
860         */
861        public static MBFImage convert(final MBFImage image, final ColourSpace cs) {
862                return cs.convertFromRGB(image.colourSpace.convertToRGB(image));
863        }
864
865        /**
866         * Get the number of bands required by this colour space
867         * 
868         * @return the number of bands
869         */
870        public abstract int getNumBands();
871
872        /**
873         * Compute the intensity of the given pixel in this colourspace. In
874         * colourspaces where intensity cannot be calculated, this should just
875         * return 0.
876         * 
877         * @param colour
878         *            the colour to extract the intensity from
879         * 
880         * @return the number of bands
881         */
882        public abstract float computeIntensity(float[] colour);
883
884        /**
885         * Sanitise the given colour array to fit the colour space format. It uses a
886         * number of heuristics that are as follows:
887         * 
888         * - if the colour has the same or more bands than the colour space, then
889         * the colour is returned unchanged. - if the colour has just one band, then
890         * it is duplicated by the same number of bands as required by the colour
891         * space - otherwise, the colour is duplicated and padded with 1s.
892         * 
893         * Example: RGBA colour space, RGB colour [1.0, 0.2, 0.4] the result will be
894         * padded with 1s: [1.0, 0.2, 0.4, 1]
895         * 
896         * Example: HSV colour space, single band colour [0.3] the result will be
897         * duplicated: [0.3, 0.3, 0.3]
898         * 
899         * @param colour
900         *            The colour to sanitise
901         * @return The sanitised colour
902         */
903        public Float[] sanitise(final Float[] colour)
904        {
905                // If the colour is longer than the required number
906                // of bands, then we'll return as is. We needn't
907                // truncate as the extra bands will be ignored by
908                // any renderers.
909                if (colour.length >= this.getNumBands())
910                        return colour;
911
912                // If the colour is a singleton, we'll duplicate it up
913                // to the correct number of bands.
914                if (colour.length == 1)
915                {
916                        final Float[] newColour = new Float[this.getNumBands()];
917                        for (int i = 0; i < newColour.length; i++)
918                                newColour[i] = colour[0];
919                        return newColour;
920                }
921
922                // If it's neither of the above, then we copy the current colour
923                // into the new return colour, and pad with 1s.
924                final Float[] newColour = new Float[this.getNumBands()];
925
926                // Copy the current colour
927                for (int i = 0; i < colour.length; i++)
928                        newColour[i] = colour[i];
929
930                // Pad with 1s
931                for (int i = colour.length; i < newColour.length; i++)
932                        newColour[i] = 1f;
933
934                return newColour;
935        }
936}