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 & b values are
567         * in -127..127 inclusive.
568         */
569        CIE_Lab {
570                @Override
571                public MBFImage convertFromRGB(final MBFImage input) {
572                        return Transforms.RGB_TO_CIELab(input);
573                }
574
575                @Override
576                public MBFImage convertToRGB(final MBFImage input) {
577                        return Transforms.CIELab_TO_RGB(input);
578                }
579
580                @Override
581                public int getNumBands() {
582                        return 3;
583                }
584
585                @Override
586                public float computeIntensity(float[] colour) {
587                        return colour[0];
588                }
589        },
590        /**
591         * Normalised CIE_Lab color space, using the same transform as in OpenCV,
592         * which in turn came from: <a href=
593         * "http://www.cica.indiana.edu/cica/faq/color_spaces/color.spaces.html">
594         * http://www.cica.indiana.edu/cica/faq/color_spaces/color.spaces.html</a>
595         * <p>
596         * The L, a & b values are normalised to 0..1.
597         */
598        CIE_Lab_Norm {
599                @Override
600                public MBFImage convertFromRGB(final MBFImage input) {
601                        return Transforms.RGB_TO_CIELabNormalised(input);
602                }
603
604                @Override
605                public MBFImage convertToRGB(final MBFImage input) {
606                        return Transforms.CIELabNormalised_TO_RGB(input);
607                }
608
609                @Override
610                public int getNumBands() {
611                        return 3;
612                }
613
614                @Override
615                public float computeIntensity(float[] colour) {
616                        return colour[0];
617                }
618        },
619        /**
620         * CIE L*u*v* color space (CIE 1976).
621         * <p>
622         * The resultant L values are in the range 0-100, and the u & v values are
623         * in -100..100 inclusive.
624         */
625        CIE_Luv {
626
627                @Override
628                public MBFImage convertFromRGB(final MBFImage input) {
629                        return Transforms.RGB_TO_CIELUV(input);
630                }
631
632                @Override
633                public MBFImage convertToRGB(final MBFImage input) {
634                        return Transforms.CIELUV_TO_RGB(input);
635                }
636
637                @Override
638                public int getNumBands() {
639                        return 3;
640                }
641
642                @Override
643                public float computeIntensity(float[] colour) {
644                        return colour[0];
645                }
646        },
647        /**
648         * YUV
649         * <p>
650         * The resultant Y is in the range [0, 1]; U is [-0.436, 0.436] and V is
651         * [-0.615, 0.615].
652         */
653        YUV {
654                @Override
655                public MBFImage convertFromRGB(final MBFImage input) {
656                        return Transforms.RGB_TO_YUV(input);
657                }
658
659                @Override
660                public MBFImage convertToRGB(final MBFImage input) {
661                        return Transforms.YUV_TO_RGB(input);
662                }
663
664                @Override
665                public int getNumBands() {
666                        return 3;
667                }
668
669                @Override
670                public float computeIntensity(float[] colour) {
671                        return colour[2];
672                }
673        },
674        /**
675         * Normalised YUV.
676         * <p>
677         * Each of the Y, U and V values are in [0, 1].
678         * 
679         */
680        YUV_Norm {
681                @Override
682                public MBFImage convertFromRGB(final MBFImage input) {
683                        return Transforms.RGB_TO_YUVNormalised(input);
684                }
685
686                @Override
687                public MBFImage convertToRGB(final MBFImage input) {
688                        return Transforms.YUVNormalised_TO_RGB(input);
689                }
690
691                @Override
692                public int getNumBands() {
693                        return 3;
694                }
695
696                @Override
697                public float computeIntensity(float[] colour) {
698                        return colour[2];
699                }
700        },
701        /**
702         * Modified Opponent colour-space as used in <code>vlfeat</code>. Intensity
703         * is computed using the NTSC conversion. The intensity is also is added
704         * back to the other two components with a small multiplier for
705         * monochromatic regions.
706         * <p>
707         * The channel order is Intensity, O1 (r-g), O2 (r + g - 2b).
708         * 
709         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
710         */
711        MODIFIED_OPPONENT {
712                @Override
713                public MBFImage convertFromRGB(MBFImage input) {
714                        final FImage intensity = Transforms.calculateIntensityNTSC(input);
715
716                        final float alpha = 0.01f;
717                        final FImage rg = new FImage(input.getWidth(), input.getHeight());
718                        final FImage rb = new FImage(input.getWidth(), input.getHeight());
719
720                        final float[][] r = input.bands.get(0).pixels;
721                        final float[][] g = input.bands.get(1).pixels;
722                        final float[][] b = input.bands.get(2).pixels;
723
724                        for (int y = 0; y < input.getHeight(); y++) {
725                                for (int x = 0; x < input.getWidth(); x++) {
726                                        rg.pixels[y][x] = (float) (r[y][x] - g[y][x] / Math.sqrt(2) + alpha * intensity.pixels[y][x]);
727                                        rb.pixels[y][x] = (float) ((r[y][x] + g[y][x] - 2 * b[y][x]) / Math.sqrt(6) + alpha
728                                                        * intensity.pixels[y][x]);
729                                }
730                        }
731
732                        return new MBFImage(ColourSpace.MODIFIED_OPPONENT, intensity, rg, rb);
733                }
734
735                @Override
736                public MBFImage convertToRGB(MBFImage input) {
737                        throw new UnsupportedOperationException("Not supported (yet)");
738                }
739
740                @Override
741                public int getNumBands() {
742                        return 3;
743                }
744
745                @Override
746                public float computeIntensity(float[] colour) {
747                        return colour[0];
748                }
749        },
750        /**
751         * Basic opponent colour-space. Intensity is the mean of r, g and b.
752         * <p>
753         * The channel order is Intensity, O1 (r-g), O2 (r + g - 2b).
754         * 
755         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
756         */
757        OPPONENT {
758                @Override
759                public MBFImage convertFromRGB(MBFImage input) {
760                        final FImage intensity = Transforms.calculateIntensity(input);
761
762                        final FImage o1 = new FImage(input.getWidth(), input.getHeight());
763                        final FImage o2 = new FImage(input.getWidth(), input.getHeight());
764
765                        final float[][] r = input.bands.get(0).pixels;
766                        final float[][] g = input.bands.get(1).pixels;
767                        final float[][] b = input.bands.get(2).pixels;
768
769                        for (int y = 0; y < input.getHeight(); y++) {
770                                for (int x = 0; x < input.getWidth(); x++) {
771                                        o1.pixels[y][x] = (float) (r[y][x] - g[y][x] / Math.sqrt(2));
772                                        o2.pixels[y][x] = (float) ((r[y][x] + g[y][x] - 2 * b[y][x]) / Math.sqrt(6));
773                                }
774                        }
775
776                        return new MBFImage(ColourSpace.MODIFIED_OPPONENT, intensity, o1, o2);
777                }
778
779                @Override
780                public MBFImage convertToRGB(MBFImage input) {
781                        throw new UnsupportedOperationException("Not supported (yet)");
782                }
783
784                @Override
785                public int getNumBands() {
786                        return 3;
787                }
788
789                @Override
790                public float computeIntensity(float[] colour) {
791                        return colour[0];
792                }
793        };
794        /**
795         * Convert the given RGB image to the current colour space
796         * 
797         * @param input
798         *            RGB image
799         * @return image in the current colour space
800         */
801        public abstract MBFImage convertFromRGB(MBFImage input);
802        
803        /**
804         * Convert the given RGB image to the current colour space
805         * 
806         * @param input
807         *            RGB image
808         * @return image in the current colour space
809         */
810        public Float[] convertFromRGB(Float[] input){
811                MBFImage singlePixel = new MBFImage(1,1,ColourSpace.RGB);
812                singlePixel.setPixel(0, 0, input);
813                return convertFromRGB(singlePixel).getPixel(0,0);
814        };
815        
816        /**
817         * Convert the given RGB image to the current colour space
818         * 
819         * @param input
820         *            RGB image
821         * @return image in the current colour space
822         */
823        public Float[] convertToRGB(Float[] input){
824                MBFImage singlePixel = new MBFImage(1,1,this);
825                singlePixel.setPixel(0, 0, input);
826                return convertToRGB(singlePixel).getPixel(0,0);
827        };
828
829        /**
830         * Convert the image in this color space to RGB
831         * 
832         * @param input
833         *            image in this colour space
834         * @return RGB image
835         */
836        public abstract MBFImage convertToRGB(MBFImage input);
837
838        /**
839         * Convert the image to this colour space
840         * 
841         * @param input
842         *            an image
843         * @return image in this colour space
844         */
845        public MBFImage convert(final MBFImage input) {
846                return this.convertFromRGB(input.getColourSpace().convertToRGB(input));
847        }
848
849        /**
850         * Convert the image to the given colour space
851         * 
852         * @param image
853         *            the image
854         * @param cs
855         *            the target colour space
856         * @return the converted image
857         */
858        public static MBFImage convert(final MBFImage image, final ColourSpace cs) {
859                return cs.convertFromRGB(image.colourSpace.convertToRGB(image));
860        }
861
862        /**
863         * Get the number of bands required by this colour space
864         * 
865         * @return the number of bands
866         */
867        public abstract int getNumBands();
868
869        /**
870         * Compute the intensity of the given pixel in this colourspace. In
871         * colourspaces where intensity cannot be calculated, this should just
872         * return 0.
873         * 
874         * @param colour
875         *            the colour to extract the intensity from
876         * 
877         * @return the number of bands
878         */
879        public abstract float computeIntensity(float[] colour);
880
881        /**
882         * Sanitise the given colour array to fit the colour space format. It uses a
883         * number of heuristics that are as follows:
884         * 
885         * - if the colour has the same or more bands than the colour space, then
886         * the colour is returned unchanged. - if the colour has just one band, then
887         * it is duplicated by the same number of bands as required by the colour
888         * space - otherwise, the colour is duplicated and padded with 1s.
889         * 
890         * Example: RGBA colour space, RGB colour [1.0, 0.2, 0.4] the result will be
891         * padded with 1s: [1.0, 0.2, 0.4, 1]
892         * 
893         * Example: HSV colour space, single band colour [0.3] the result will be
894         * duplicated: [0.3, 0.3, 0.3]
895         * 
896         * @param colour
897         *            The colour to sanitise
898         * @return The sanitised colour
899         */
900        public Float[] sanitise(final Float[] colour)
901        {
902                // If the colour is longer than the required number
903                // of bands, then we'll return as is. We needn't
904                // truncate as the extra bands will be ignored by
905                // any renderers.
906                if (colour.length >= this.getNumBands())
907                        return colour;
908
909                // If the colour is a singleton, we'll duplicate it up
910                // to the correct number of bands.
911                if (colour.length == 1)
912                {
913                        final Float[] newColour = new Float[this.getNumBands()];
914                        for (int i = 0; i < newColour.length; i++)
915                                newColour[i] = colour[0];
916                        return newColour;
917                }
918
919                // If it's neither of the above, then we copy the current colour
920                // into the new return colour, and pad with 1s.
921                final Float[] newColour = new Float[this.getNumBands()];
922
923                // Copy the current colour
924                for (int i = 0; i < colour.length; i++)
925                        newColour[i] = colour[i];
926
927                // Pad with 1s
928                for (int i = colour.length; i < newColour.length; i++)
929                        newColour[i] = 1f;
930
931                return newColour;
932        }
933}