001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.image.colorspace;
020
021import icy.common.CollapsibleEvent;
022import icy.common.UpdateEventHandler;
023import icy.common.listener.ChangeListener;
024import icy.image.colormap.FromRGBColorMap;
025import icy.image.colormap.IcyColorMap;
026import icy.image.colormap.IcyColorMap.IcyColorMapType;
027import icy.image.colormap.IcyColorMapEvent;
028import icy.image.colormap.IcyColorMapListener;
029import icy.image.colormap.LinearColorMap;
030import icy.image.colormodel.IcyColorModel;
031import icy.type.DataType;
032import icy.type.collection.array.ArrayUtil;
033import icy.util.ColorUtil;
034
035import java.awt.color.ColorSpace;
036import java.awt.image.ColorModel;
037import java.util.ArrayList;
038import java.util.List;
039
040/**
041 * @author stephane
042 */
043public class IcyColorSpace extends ColorSpace implements ChangeListener, IcyColorMapListener
044{
045    /**
046     * 
047     */
048    private static final long serialVersionUID = 6413334779215415163L;
049
050    /**
051     * toRGB colormaps
052     */
053    private final IcyColorMap[] toRGBmaps;
054    /**
055     * fromRGB colormaps
056     */
057    private final FromRGBColorMap[] fromRGBmaps;
058
059    /**
060     * use alpha
061     */
062    // private boolean alphaEnabled;
063
064    /**
065     * listeners
066     */
067    private final List<IcyColorSpaceListener> listeners;
068
069    /**
070     * internal updater
071     */
072    private final UpdateEventHandler updater;
073
074    /**
075     * Create an icy colorspace object
076     * 
077     * @param numComponents
078     *        number of color component
079     */
080    public IcyColorSpace(int numComponents)
081    {
082        super((numComponents > 1) ? 10 + numComponents : ColorSpace.TYPE_GRAY, numComponents);
083
084        if (numComponents == 0)
085            throw new IllegalArgumentException("numComponents must be > 0");
086
087        // allocating toRGB colormaps
088        toRGBmaps = new IcyColorMap[numComponents];
089        for (int i = 0; i < numComponents; i++)
090        {
091            final IcyColorMap colormap = new IcyColorMap("component " + i);
092            toRGBmaps[i] = colormap;
093            // add listener
094            colormap.addListener(this);
095        }
096
097        // allocating fromRGB colormaps
098        fromRGBmaps = new FromRGBColorMap[4];
099        for (int i = 0; i < 4; i++)
100            fromRGBmaps[i] = new FromRGBColorMap(numComponents);
101
102        listeners = new ArrayList<IcyColorSpaceListener>();
103        updater = new UpdateEventHandler(this, false);
104
105        beginUpdate();
106        try
107        {
108            // single component: gray colormap
109            if (numComponents == 1)
110                setColorMap(0, LinearColorMap.white_, true);
111            else
112            {
113                // define default colormaps depending the number of component
114                for (int i = 0; i < numComponents; i++)
115                {
116                    switch (i % 7)
117                    {
118                        case 0:
119                            setColorMap(i, LinearColorMap.red_, true);
120                            break;
121                        case 1:
122                            setColorMap(i, LinearColorMap.green_, true);
123                            break;
124                        case 2:
125                            setColorMap(i, LinearColorMap.blue_, true);
126                            break;
127                        case 3:
128                            setColorMap(i, LinearColorMap.cyan_, true);
129                            break;
130                        case 4:
131                            setColorMap(i, LinearColorMap.magenta_, true);
132                            break;
133                        case 5:
134                            setColorMap(i, LinearColorMap.yellow_, true);
135                            break;
136                        case 6:
137                            setColorMap(i, LinearColorMap.white_, true);
138                            break;
139                    }
140                }
141            }
142        }
143        finally
144        {
145            endUpdate();
146        }
147
148        // generate fromRGB maps
149        generateFromRGBColorMaps();
150    }
151
152    /**
153     * Return true if the colorspace's colormap contains an alpha component<br>
154     * This is different from the isAlphaEnabled flag
155     */
156    public boolean hasAlphaComponent()
157    {
158        for (int comp = 0; comp < toRGBmaps.length; comp++)
159            if (toRGBmaps[comp].getType() == IcyColorMapType.ALPHA)
160                return true;
161
162        return false;
163    }
164
165    /**
166     * Generate FromRGB colormaps from ToRGB ones
167     */
168    private void generateFromRGBColorMaps()
169    {
170        for (int comp = 0; comp < toRGBmaps.length; comp++)
171        {
172            final IcyColorMap toRGBmap = toRGBmaps[comp];
173            final float step = 1.0f / FromRGBColorMap.COLORMAP_MAX;
174
175            for (float intensity = 0.0f; intensity <= 1.0f; intensity += step)
176            {
177                // blue
178                fromRGBmaps[0].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedBlue(intensity));
179                // green
180                fromRGBmaps[1].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedGreen(intensity));
181                // red
182                fromRGBmaps[2].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedRed(intensity));
183                // alpha
184                fromRGBmaps[3].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedAlpha(intensity));
185            }
186        }
187    }
188
189    /**
190     * @see java.awt.color.ColorSpace#fromCIEXYZ(float[])
191     */
192    @Override
193    public float[] fromCIEXYZ(float[] colorvalue)
194    {
195        return fromRGB(ColorUtil.sRGB.fromCIEXYZ(colorvalue));
196    }
197
198    /**
199     * @see java.awt.color.ColorSpace#toCIEXYZ(float[])
200     */
201    @Override
202    public float[] toCIEXYZ(float[] colorvalue)
203    {
204        return ColorUtil.sRGB.toCIEXYZ(toRGB(colorvalue));
205    }
206
207    /**
208     * @see java.awt.color.ColorSpace#fromRGB(float[])
209     */
210    @Override
211    public float[] fromRGB(float[] rgb)
212    {
213        final int numComponents = getNumComponents();
214        final float[] result = new float[numComponents];
215
216        final FromRGBColorMap blueMap = fromRGBmaps[0];
217        final FromRGBColorMap greenMap = fromRGBmaps[1];
218        final FromRGBColorMap redMap = fromRGBmaps[2];
219        final FromRGBColorMap alphaMap = fromRGBmaps[3];
220
221        final float blue = rgb[0];
222        final float green = rgb[1];
223        final float red = rgb[2];
224        final float alpha;
225        if (rgb.length > 3)
226            alpha = rgb[3];
227        else
228            alpha = 1.0f;
229
230        for (int comp = 0; comp < numComponents; comp++)
231        {
232            result[comp] = blueMap.getFromRGBColor(comp, blue);
233            result[comp] += greenMap.getFromRGBColor(comp, green);
234            result[comp] += redMap.getFromRGBColor(comp, red);
235            result[comp] *= alphaMap.getFromRGBColor(comp, alpha);
236            // limit
237            if (result[comp] > 1.0f)
238                result[comp] = 1.0f;
239        }
240
241        return result;
242    }
243
244    /**
245     * return normalized component values from ARGB packed integer values
246     * 
247     * @param rgb
248     * @return float[]
249     */
250    public float[] fromRGB(int rgb)
251    {
252        final int numComponents = getNumComponents();
253        final float[] result = new float[numComponents];
254
255        final FromRGBColorMap blueMap = fromRGBmaps[0];
256        final FromRGBColorMap greenMap = fromRGBmaps[1];
257        final FromRGBColorMap redMap = fromRGBmaps[2];
258        final FromRGBColorMap alphaMap = fromRGBmaps[3];
259
260        final int red, grn, blu, alp;
261        alp = (rgb >> 24) & 0xFF;
262        red = (rgb >> 16) & 0xFF;
263        grn = (rgb >> 8) & 0xFF;
264        blu = rgb & 0xFF;
265
266        for (int comp = 0; comp < numComponents; comp++)
267        {
268            result[comp] = blueMap.maps[comp][blu];
269            result[comp] += greenMap.maps[comp][grn];
270            result[comp] += redMap.maps[comp][red];
271            result[comp] *= alphaMap.maps[comp][alp];
272            // limit
273            if (result[comp] > 1.0f)
274                result[comp] = 1.0f;
275        }
276
277        return result;
278    }
279
280    /**
281     * return unnormalized ARGB values from colorMap scaled components values
282     * 
283     * @param colorvalue
284     * @return ARGB as int
285     */
286    public int toRGBUnnorm(final int[] colorvalue)
287    {
288        final int numComponents = Math.min(getNumComponents(), colorvalue.length);
289
290        // default alpha
291        float alpha = 1f;
292        // default max local alpha
293        float maxLocalAlpha = 0f;
294        // default RGB
295        int r = 0, g = 0, b = 0;
296
297        for (int comp = 0; comp < numComponents; comp++)
298        {
299            final IcyColorMap cm = toRGBmaps[comp];
300
301            if (cm.isEnabled())
302            {
303                final int value = colorvalue[comp];
304
305                final float alphaValue = cm.alpha.mapf[value];
306
307                // alpha channel ?
308                if (cm.getType() == IcyColorMapType.ALPHA)
309                    alpha = alphaValue;
310                else if (alphaValue > maxLocalAlpha)
311                    maxLocalAlpha = alphaValue;
312
313                final int premulRGB[] = cm.getPremulRGB()[value];
314
315                b += premulRGB[0];
316                g += premulRGB[1];
317                r += premulRGB[2];
318            }
319        }
320
321        // final alpha = alpha component value * maximum local alpha value
322        final int a = (int) (alpha * maxLocalAlpha * IcyColorMap.MAX_LEVEL);
323
324        if (a != 0)
325        {
326            final int inv = (1 << (IcyColorMap.COLORMAP_BITS + 8)) / a;
327
328            // normalize on alpha
329            b = (b * inv) >> 8;
330            g = (g * inv) >> 8;
331            r = (r * inv) >> 8;
332        }
333
334        return ((b > IcyColorMap.MAX_LEVEL) ? IcyColorMap.MAX_LEVEL : b)
335                | (((g > IcyColorMap.MAX_LEVEL) ? IcyColorMap.MAX_LEVEL : g) << 8)
336                | (((r > IcyColorMap.MAX_LEVEL) ? IcyColorMap.MAX_LEVEL : r) << 16) | (a << 24);
337    }
338
339    @Override
340    public float[] toRGB(float[] colorvalue)
341    {
342        final float[] result = new float[4];
343        final int numComponents = Math.min(getNumComponents(), colorvalue.length);
344
345        // default alpha
346        float alpha = 1f;
347        // default max local alpha
348        float maxLocalAlpha = 0f;
349        // default RGB
350        float rf = 0f, gf = 0f, bf = 0f;
351
352        for (int comp = 0; comp < numComponents; comp++)
353        {
354            final IcyColorMap cm = toRGBmaps[comp];
355
356            if (cm.isEnabled())
357            {
358                final int value = (int) (colorvalue[comp] * IcyColorMap.MAX_INDEX);
359                final float alphaValue = cm.alpha.mapf[value];
360
361                // alpha channel ?
362                if (cm.getType() == IcyColorMapType.ALPHA)
363                    alpha = alphaValue;
364                else if (alphaValue > maxLocalAlpha)
365                    maxLocalAlpha = alphaValue;
366
367                final float premulRGBNorm[] = cm.getPremulRGBNorm()[value];
368
369                bf += premulRGBNorm[0];
370                gf += premulRGBNorm[1];
371                rf += premulRGBNorm[2];
372            }
373        }
374
375        // final alpha = alpha component value * maximum local alpha value
376        final float af = alpha * maxLocalAlpha;
377
378        if (af != 0f)
379        {
380            final float inv = 1f / af;
381
382            // normalize on alpha
383            bf *= inv;
384            gf *= inv;
385            rf *= inv;
386        }
387
388        result[0] = (bf > 1f) ? 1f : bf;
389        result[1] = (gf > 1f) ? 1f : gf;
390        result[2] = (rf > 1f) ? 1f : rf;
391        result[3] = af;
392
393        return result;
394    }
395
396    /**
397     * Set 8 bit ARGB data in an ARGB buffer from a scaled input buffer
398     * 
399     * @param unnormSrc
400     *        source buffer containing unnormalized values ([0..255] range) for each component
401     * @param dest
402     *        ARGB components buffer
403     */
404    public void fillARGBBuffer(int[][] unnormSrc, int[] dest, int offset, int length)
405    {
406        final int numComponents = getNumComponents();
407
408        if (numComponents > 0)
409        {
410            final int[] input = new int[numComponents];
411
412            for (int i = 0; i < length; i++)
413            {
414                // get data value
415                for (int comp = 0; comp < numComponents; comp++)
416                    input[comp] = unnormSrc[comp][i];
417
418                // convert to RGBA
419                dest[offset + i] = toRGBUnnorm(input);
420            }
421        }
422    }
423
424    /**
425     * Set 8 bit ARGB data in an ARGB buffer from a scaled input buffer
426     * 
427     * @param unnormSrc
428     *        source buffer containing unnormalized values ([0..255] range) for each component
429     * @param dest
430     *        ARGB components buffer
431     */
432    public void fillARGBBuffer(int[][] unnormSrc, int[] dest)
433    {
434        if ((unnormSrc == null) || (dest == null))
435        {
436            throw new IllegalArgumentException("Parameters 'unnormSrc' and 'destARGB' should not be null !");
437        }
438
439        final int numComponents = getNumComponents();
440
441        if (unnormSrc.length != numComponents)
442        {
443            throw new IllegalArgumentException("Parameters 'unnormSrc' size is [" + unnormSrc.length + "][..] where ["
444                    + numComponents + "][..] is expected !");
445        }
446
447        if (numComponents > 0)
448        {
449            final int size = unnormSrc[0].length;
450            final int[] input = new int[numComponents];
451
452            for (int i = 0; i < size; i++)
453            {
454                // get data value
455                for (int comp = 0; comp < numComponents; comp++)
456                    input[comp] = unnormSrc[comp][i];
457
458                // convert to RGBA
459                dest[i] = toRGBUnnorm(input);
460            }
461        }
462    }
463
464    /**
465     * Return the number of component of colorSpace
466     */
467    @Override
468    public int getNumComponents()
469    {
470        return toRGBmaps.length;
471    }
472
473    /**
474     * Return the colormap of the specified component.
475     */
476    public IcyColorMap getColorMap(int component)
477    {
478        return toRGBmaps[component];
479    }
480
481    /**
482     * @deprecated Use {@link #getColorMap(int)} instead (different case).
483     */
484    @Deprecated
485    public IcyColorMap getColormap(int component)
486    {
487        return getColorMap(component);
488    }
489
490    /**
491     * Set the colormap for the specified component (actually copy the content of source colormap).
492     * 
493     * @param component
494     *        component we want to set the colormap
495     * @param colorMap
496     *        source colorMap
497     * @param setAlpha
498     *        also set the alpha information
499     */
500    public void setColorMap(int component, IcyColorMap colorMap, boolean setAlpha)
501    {
502        toRGBmaps[component].copyFrom(colorMap, setAlpha);
503    }
504
505    /**
506     * @deprecated Use {@link #setColorMap(int, IcyColorMap, boolean)} instead.
507     */
508    @Deprecated
509    public void setColormap(int component, IcyColorMap map)
510    {
511        setColorMap(component, map, true);
512    }
513
514    /**
515     * @deprecated Use <code>setColormap(channel, map)</code> instead.
516     */
517    @Deprecated
518    public void copyColormap(int component, IcyColorMap map, boolean copyName, boolean copyAlpha)
519    {
520        setColorMap(component, map, copyAlpha);
521
522        if (copyName)
523            toRGBmaps[component].setName(map.getName());
524    }
525
526    /**
527     * @deprecated Use <code>setColormap(channel, map)</code> instead.
528     */
529    @Deprecated
530    public void copyColormap(int component, IcyColorMap map, boolean copyName)
531    {
532        setColorMap(component, map, true);
533
534        if (copyName)
535            toRGBmaps[component].setName(map.getName());
536    }
537
538    /**
539     * @deprecated Use <code>setColormap(channel, map)</code> instead.
540     */
541    @Deprecated
542    public void copyColormap(int component, IcyColorMap map)
543    {
544        setColorMap(component, map, true);
545    }
546
547    /**
548     * Return the RGB inverse colormap for specified RGB component.
549     */
550    public FromRGBColorMap getFromRGBMap(int component)
551    {
552        return fromRGBmaps[component];
553    }
554
555    /**
556     * Set the RGB colormaps from a compatible colorModel.
557     */
558    public void setColorMaps(ColorModel cm)
559    {
560        if (cm instanceof IcyColorModel)
561            setColorMaps((IcyColorSpace) cm.getColorSpace(), true);
562        else
563        {
564            // get datatype and numComponent of source colorModel
565            final Object srcElem = cm.getDataElements(0x0, null);
566            final DataType srcDataType = ArrayUtil.getDataType(srcElem);
567            final int srcNumComponents = ArrayUtil.getLength(srcElem);
568            final DataType dataType = DataType.getDataTypeFromDataBufferType(cm.getTransferType());
569            final int numComponents = getNumComponents();
570
571            // can't recover colormap if we have different dataType or numComponents
572            if ((srcNumComponents != numComponents) || (srcDataType != dataType))
573                return;
574
575            final boolean hasAlpha = cm.hasAlpha();
576
577            for (int comp = 0; comp < numComponents; comp++)
578            {
579                final IcyColorMap map = toRGBmaps[comp];
580
581                map.beginUpdate();
582                try
583                {
584                    // set type
585                    if (hasAlpha && (comp == (numComponents - 1)))
586                        map.setType(IcyColorMapType.ALPHA);
587                    else
588                        map.setType(IcyColorMapType.RGB);
589
590                    for (int index = 0; index < IcyColorMap.SIZE; index++)
591                    {
592                        switch (dataType.getJavaType())
593                        {
594                            case BYTE:
595                            {
596                                final byte bvalues[] = new byte[numComponents];
597
598                                // build an pixel element
599                                for (int i = 0; i < numComponents; i++)
600                                {
601                                    if (i == comp)
602                                        bvalues[i] = (byte) (index * (1 << 8) / IcyColorMap.SIZE);
603                                    else if (hasAlpha && (i == (numComponents - 1)))
604                                        bvalues[i] = (byte) (IcyColorMap.MAX_INDEX * (1 << 8) / IcyColorMap.SIZE);
605                                    else
606                                        bvalues[i] = 0;
607                                }
608
609                                // set colormap data
610                                map.setAlpha(index, (short) cm.getAlpha(bvalues));
611                                map.setRed(index, (short) cm.getRed(bvalues));
612                                map.setGreen(index, (short) cm.getGreen(bvalues));
613                                map.setBlue(index, (short) cm.getBlue(bvalues));
614                                break;
615                            }
616
617                            case SHORT:
618                            {
619                                final short svalues[] = new short[numComponents];
620
621                                // build an pixel element
622                                for (int i = 0; i < numComponents; i++)
623                                {
624                                    if (i == comp)
625                                        svalues[i] = (short) (index * (1 << 16) / IcyColorMap.SIZE);
626                                    else if (hasAlpha && (i == (numComponents - 1)))
627                                        svalues[i] = (short) (IcyColorMap.MAX_INDEX * (1 << 16) / IcyColorMap.SIZE);
628                                    else
629                                        svalues[i] = 0;
630                                }
631
632                                // set colormap data
633                                map.setAlpha(index, (short) cm.getAlpha(svalues));
634                                map.setRed(index, (short) cm.getRed(svalues));
635                                map.setGreen(index, (short) cm.getGreen(svalues));
636                                map.setBlue(index, (short) cm.getBlue(svalues));
637                                break;
638                            }
639
640                            case INT:
641                            {
642                                final int ivalues[] = new int[numComponents];
643
644                                // build an pixel element
645                                for (int i = 0; i < numComponents; i++)
646                                {
647                                    if (i == comp)
648                                        ivalues[i] = (index * (1 << 32) / IcyColorMap.SIZE);
649                                    else if (hasAlpha && (i == (numComponents - 1)))
650                                        ivalues[i] = (IcyColorMap.MAX_INDEX * (1 << 32) / IcyColorMap.SIZE);
651                                    else
652                                        ivalues[i] = 0;
653                                }
654
655                                // set colormap data
656                                map.setAlpha(index, (short) cm.getAlpha(ivalues));
657                                map.setRed(index, (short) cm.getRed(ivalues));
658                                map.setGreen(index, (short) cm.getGreen(ivalues));
659                                map.setBlue(index, (short) cm.getBlue(ivalues));
660                                break;
661                            }
662
663                            case LONG:
664                            {
665                                final long lvalues[] = new long[numComponents];
666
667                                // build an pixel element
668                                for (int i = 0; i < numComponents; i++)
669                                {
670                                    if (i == comp)
671                                        lvalues[i] = (index * (1 << 32) / IcyColorMap.SIZE);
672                                    else if (hasAlpha && (i == (numComponents - 1)))
673                                        lvalues[i] = (IcyColorMap.MAX_INDEX * (1 << 32) / IcyColorMap.SIZE);
674                                    else
675                                        lvalues[i] = 0;
676                                }
677
678                                // set colormap data
679                                map.setAlpha(index, (short) cm.getAlpha(lvalues));
680                                map.setRed(index, (short) cm.getRed(lvalues));
681                                map.setGreen(index, (short) cm.getGreen(lvalues));
682                                map.setBlue(index, (short) cm.getBlue(lvalues));
683                                break;
684                            }
685
686                            case FLOAT:
687                            {
688                                final float fvalues[] = new float[numComponents];
689
690                                // build an pixel element
691                                for (int i = 0; i < numComponents; i++)
692                                {
693                                    if (i == comp)
694                                        fvalues[i] = (float) index / (float) IcyColorMap.SIZE;
695                                    else if (hasAlpha && (i == (numComponents - 1)))
696                                        fvalues[i] = (IcyColorMap.MAX_INDEX / (float) IcyColorMap.SIZE);
697                                    else
698                                        fvalues[i] = 0;
699                                }
700
701                                // set colormap data
702                                map.setAlpha(index, (short) cm.getAlpha(fvalues));
703                                map.setRed(index, (short) cm.getRed(fvalues));
704                                map.setGreen(index, (short) cm.getGreen(fvalues));
705                                map.setBlue(index, (short) cm.getBlue(fvalues));
706                                break;
707                            }
708
709                            case DOUBLE:
710                            {
711                                final double dvalues[] = new double[numComponents];
712
713                                // build an pixel element
714                                for (int i = 0; i < numComponents; i++)
715                                {
716                                    if (i == comp)
717                                        dvalues[i] = (double) index / (double) IcyColorMap.SIZE;
718                                    else if (hasAlpha && (i == (numComponents - 1)))
719                                        dvalues[i] = (IcyColorMap.MAX_INDEX / (double) IcyColorMap.SIZE);
720                                    else
721                                        dvalues[i] = 0;
722                                }
723
724                                // set colormap data
725                                map.setAlpha(index, (short) cm.getAlpha(dvalues));
726                                map.setRed(index, (short) cm.getRed(dvalues));
727                                map.setGreen(index, (short) cm.getGreen(dvalues));
728                                map.setBlue(index, (short) cm.getBlue(dvalues));
729                                break;
730                            }
731
732                            default:
733                                break;
734                        }
735                    }
736                }
737                finally
738                {
739                    map.endUpdate();
740                }
741            }
742        }
743    }
744
745    /**
746     * @deprecated Use {@link #setColorMaps(ColorModel)} instead (different case).
747     */
748    @Deprecated
749    public void setColormaps(ColorModel cm)
750    {
751        setColorMaps(cm);
752    }
753
754    /**
755     * @deprecated Use {@link #setColorMaps(ColorModel)} instead.
756     */
757    @Deprecated
758    public void copyColormaps(ColorModel cm)
759    {
760        setColorMaps(cm);
761    }
762
763    /**
764     * Set colormaps from specified colorSpace (do a copy).
765     * 
766     * @param source
767     *        source colorspace to copy the colormaps from
768     * @param setAlpha
769     *        also set the alpha information
770     */
771    public void setColorMaps(IcyColorSpace source, boolean setAlpha)
772    {
773        final int numComponents = Math.min(source.getNumComponents(), getNumComponents());
774
775        beginUpdate();
776        try
777        {
778            // copy colormap
779            for (int comp = 0; comp < numComponents; comp++)
780                setColorMap(comp, source.getColorMap(comp), setAlpha);
781        }
782        finally
783        {
784            endUpdate();
785        }
786    }
787
788    /**
789     * @deprecated Use {@link #setColorMaps(IcyColorSpace, boolean)} instead.
790     */
791    @Deprecated
792    public void setColormaps(IcyColorSpace source)
793    {
794        setColorMaps(source, true);
795    }
796
797    /**
798     * @deprecated Use {@link #setColorMaps(IcyColorSpace, boolean)} instead.
799     */
800    @Deprecated
801    public void copyColormaps(IcyColorSpace source)
802    {
803        setColorMaps(source, true);
804    }
805
806    /**
807     * get index of the specified colormap
808     * 
809     * @param colormap
810     * @return index
811     */
812    public int indexOfColorMap(IcyColorMap colormap)
813    {
814        for (int i = 0; i < toRGBmaps.length; i++)
815            if (toRGBmaps[i].equals(colormap))
816                return i;
817
818        return -1;
819    }
820
821    @Override
822    public String getName(int idx)
823    {
824        // TODO: should get name from metadata
825        return "Component #" + idx;
826    }
827
828    /**
829     * Add a listener
830     * 
831     * @param listener
832     */
833    public void addListener(IcyColorSpaceListener listener)
834    {
835        listeners.add(listener);
836    }
837
838    /**
839     * Remove a listener
840     * 
841     * @param listener
842     */
843    public void removeListener(IcyColorSpaceListener listener)
844    {
845        listeners.remove(listener);
846    }
847
848    /**
849     * fire event
850     */
851    public void fireEvent(IcyColorSpaceEvent e)
852    {
853        for (IcyColorSpaceListener listener : new ArrayList<IcyColorSpaceListener>(listeners))
854            listener.colorSpaceChanged(e);
855    }
856
857    /**
858     * called when colorspace has changed (afaik when a colormap has changed)
859     */
860    private void changed(int component)
861    {
862        final IcyColorMap colorMap = getColorMap(component);
863
864        // we can have only 1 alpha colormap
865        if (colorMap != null)
866        {
867            // alpha type colormap ?
868            if (colorMap.getType() == IcyColorMapType.ALPHA)
869            {
870                // check that others colormap are non alpha
871                for (IcyColorMap map : toRGBmaps)
872                {
873                    if (map != colorMap)
874                    {
875                        // we have another ALPHA colormap ?
876                        if (map.getType() == IcyColorMapType.ALPHA)
877                            // set it to RGB
878                            map.setType(IcyColorMapType.RGB);
879                    }
880                }
881            }
882        }
883
884        // handle changed via updater object
885        updater.changed(new IcyColorSpaceEvent(this, component));
886    }
887
888    /**
889     * process on colorspace change
890     */
891    @Override
892    public void onChanged(CollapsibleEvent compare)
893    {
894        final IcyColorSpaceEvent event = (IcyColorSpaceEvent) compare;
895
896        // recalculate fromRGB colormaps
897        generateFromRGBColorMaps();
898
899        // notify listener we have changed
900        fireEvent(event);
901    }
902
903    @Override
904    public void colorMapChanged(IcyColorMapEvent e)
905    {
906        final int index = indexOfColorMap(e.getColormap());
907
908        // colormap found ? raise a "changed" event
909        if (index != -1)
910            changed(index);
911    }
912
913    /**
914     * @see icy.common.UpdateEventHandler#beginUpdate()
915     */
916    public void beginUpdate()
917    {
918        updater.beginUpdate();
919    }
920
921    /**
922     * @see icy.common.UpdateEventHandler#endUpdate()
923     */
924    public void endUpdate()
925    {
926        updater.endUpdate();
927    }
928
929    /**
930     * @see icy.common.UpdateEventHandler#isUpdating()
931     */
932    public boolean isUpdating()
933    {
934        return updater.isUpdating();
935    }
936
937}