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.colormap;
020
021import icy.common.CollapsibleEvent;
022import icy.common.UpdateEventHandler;
023import icy.common.listener.ChangeListener;
024import icy.file.FileUtil;
025import icy.file.xml.XMLPersistent;
026import icy.file.xml.XMLPersistentHelper;
027import icy.image.colormap.IcyColorMapEvent.IcyColorMapEventType;
028import icy.util.ColorUtil;
029import icy.util.XMLUtil;
030
031import java.awt.Color;
032import java.io.File;
033import java.util.ArrayList;
034import java.util.List;
035
036import javax.swing.event.EventListenerList;
037
038import org.w3c.dom.Node;
039
040/**
041 * @author stephane
042 */
043public class IcyColorMap implements ChangeListener, XMLPersistent
044{
045    public enum IcyColorMapType
046    {
047        RGB, GRAY, ALPHA
048    };
049
050    private static final String ID_TYPE = "type";
051    private static final String ID_NAME = "name";
052    private static final String ID_ENABLED = "enabled";
053    private static final String ID_RED = "red";
054    private static final String ID_GREEN = "green";
055    private static final String ID_BLUE = "blue";
056    private static final String ID_GRAY = "gray";
057    private static final String ID_ALPHA = "alpha";
058
059    /**
060     * define the wanted colormap bits resolution (never change it)
061     */
062    public static final int COLORMAP_BITS = 8;
063    public static final int MAX_LEVEL = (1 << COLORMAP_BITS) - 1;
064
065    /**
066     * define colormap size
067     */
068    public static final int SIZE = 256;
069    public static final int MAX_INDEX = SIZE - 1;
070
071    /**
072     * default colormap directory
073     */
074    public static final String DEFAULT_COLORMAP_DIR = "colormap";
075
076    /**
077     * Custom (user) colormap
078     */
079    private static List<IcyColorMap> customMaps = null;
080
081    /**
082     * Returns the list of default linear colormap:<br/>
083     * GRAY, [GRAY_INV,] RED, GREEN, BLUE, MAGENTA, YELLOW, CYAN, [ALPHA]
084     * 
085     * @param wantGrayInverse
086     *        specify if we want the gray inverse colormap
087     * @param wantAlpha
088     *        specify if we want the alpha colormap
089     */
090    public static List<IcyColorMap> getLinearColorMaps(boolean wantGrayInverse, boolean wantAlpha)
091    {
092        final List<IcyColorMap> result = new ArrayList<IcyColorMap>();
093
094        result.add(LinearColorMap.gray_);
095        if (wantGrayInverse)
096            result.add(LinearColorMap.gray_inv_);
097        result.add(LinearColorMap.red_);
098        result.add(LinearColorMap.green_);
099        result.add(LinearColorMap.blue_);
100        result.add(LinearColorMap.magenta_);
101        result.add(LinearColorMap.yellow_);
102        result.add(LinearColorMap.cyan_);
103        if (wantAlpha)
104            result.add(LinearColorMap.alpha_);
105
106        return result;
107    }
108
109    /**
110     * Returns the list of special colormap:<br/>
111     * ICE, FIRE, HSV, JET, GLOW
112     */
113    public static List<IcyColorMap> getSpecialColorMaps()
114    {
115        final List<IcyColorMap> result = new ArrayList<IcyColorMap>();
116
117        result.add(new IceColorMap());
118        result.add(new FireColorMap());
119        result.add(new HSVColorMap());
120        result.add(new JETColorMap());
121        result.add(new GlowColorMap(true));
122
123        return result;
124    }
125
126    /**
127     * Returns the list of custom colormap available in the Icy "colormap" folder
128     */
129    public static synchronized List<IcyColorMap> getCustomColorMaps()
130    {
131        if (customMaps == null)
132        {
133            // load custom maps
134            customMaps = new ArrayList<IcyColorMap>();
135
136            // add saved colormap
137            for (File f : FileUtil.getFiles(new File(DEFAULT_COLORMAP_DIR), null, false, false, false))
138            {
139                final IcyColorMap map = new IcyColorMap();
140                if (XMLPersistentHelper.loadFromXML(map, f))
141                    customMaps.add(map);
142            }
143        }
144
145        return new ArrayList<IcyColorMap>(customMaps);
146    }
147
148    /**
149     * Returns the list of all available colormaps.<br/>
150     * The order of returned colormap map is Linear, Special and Custom.
151     * 
152     * @param wantGrayInverse
153     *        specify if we want the gray inverse colormap
154     * @param wantAlpha
155     *        specify if we want the alpha colormap
156     */
157    public static synchronized List<IcyColorMap> getAllColorMaps(boolean wantGrayInverse, boolean wantAlpha)
158    {
159        final List<IcyColorMap> result = new ArrayList<IcyColorMap>();
160
161        result.addAll(getLinearColorMaps(false, false));
162        result.addAll(getSpecialColorMaps());
163        result.addAll(getCustomColorMaps());
164
165        return result;
166    }
167
168    /**
169     * colormap name
170     */
171    private String name;
172
173    /**
174     * enabled flag
175     */
176    private boolean enabled;
177
178    /**
179     * RED band
180     */
181    public final IcyColorMapComponent red;
182    /**
183     * GREEN band
184     */
185    public final IcyColorMapComponent green;
186    /**
187     * BLUE band
188     */
189    public final IcyColorMapComponent blue;
190    /**
191     * GRAY band
192     */
193    public final IcyColorMapComponent gray;
194    /**
195     * ALPHA band
196     */
197    public final IcyColorMapComponent alpha;
198
199    /**
200     * colormap type
201     */
202    private IcyColorMapType type;
203
204    /**
205     * pre-multiplied RGB caches
206     */
207    private final int premulRGB[][];
208    private final float premulRGBNorm[][];
209
210    /**
211     * listeners
212     */
213    private final EventListenerList listeners;
214
215    /**
216     * internal updater
217     */
218    private final UpdateEventHandler updater;
219
220    public IcyColorMap(String name, IcyColorMapType type)
221    {
222        this.name = name;
223        enabled = true;
224
225        listeners = new EventListenerList();
226        updater = new UpdateEventHandler(this, false);
227
228        // colormap band
229        red = createColorMapBand((short) 0);
230        green = createColorMapBand((short) 0);
231        blue = createColorMapBand((short) 0);
232        gray = createColorMapBand((short) 0);
233        alpha = createColorMapBand((short) 255);
234
235        this.type = type;
236
237        // allocating and init RGB cache
238        premulRGB = new int[IcyColorMap.SIZE][3];
239        premulRGBNorm = new float[IcyColorMap.SIZE][3];
240    }
241
242    public IcyColorMap(String name)
243    {
244        this(name, IcyColorMapType.RGB);
245    }
246
247    public IcyColorMap(String name, Object maps)
248    {
249        this(name, IcyColorMapType.RGB);
250
251        if (maps instanceof byte[][])
252            copyFrom((byte[][]) maps);
253        else if (maps instanceof short[][])
254            copyFrom((short[][]) maps);
255
256        // try to define color map type from data
257        setTypeFromData(false);
258    }
259
260    /**
261     * Create a copy of specified colormap.
262     */
263    public IcyColorMap(IcyColorMap colormap)
264    {
265        this(colormap.name, colormap.type);
266
267        copyFrom(colormap);
268    }
269
270    public IcyColorMap()
271    {
272        this("");
273    }
274
275    protected IcyColorMapComponent createColorMapBand(short initValue)
276    {
277        return new IcyColorMapComponent(IcyColorMap.this, initValue);
278    }
279
280    /**
281     * Return true if this color map is RGB type
282     */
283    public boolean isRGB()
284    {
285        return type == IcyColorMapType.RGB;
286    }
287
288    /**
289     * Return true if this color map is GRAY type
290     */
291    public boolean isGray()
292    {
293        return type == IcyColorMapType.GRAY;
294    }
295
296    /**
297     * Return true if this color map is ALPHA type
298     */
299    public boolean isAlpha()
300    {
301        return type == IcyColorMapType.ALPHA;
302    }
303
304    /**
305     * @return the type
306     */
307    public IcyColorMapType getType()
308    {
309        return type;
310    }
311
312    /**
313     * @param value
314     *        the type to set
315     */
316    public void setType(IcyColorMapType value)
317    {
318        if (type != value)
319        {
320            type = value;
321
322            changed(IcyColorMapEventType.TYPE_CHANGED);
323        }
324    }
325
326    /**
327     * @see IcyColorMap#setTypeFromData()
328     */
329    public void setTypeFromData(boolean notifyChange)
330    {
331        boolean grayColor = true;
332        boolean noColor = true;
333        boolean hasAlpha = false;
334        IcyColorMapType cmType;
335
336        for (int i = 0; i < MAX_INDEX; i++)
337        {
338            final short r = red.map[i];
339            final short g = green.map[i];
340            final short b = blue.map[i];
341            final short a = alpha.map[i];
342
343            grayColor &= (r == g) && (r == b);
344            noColor &= (r == 0) && (g == 0) && (b == 0);
345            hasAlpha |= (a != MAX_LEVEL);
346        }
347
348        if (noColor && hasAlpha)
349            cmType = IcyColorMapType.ALPHA;
350        else if (grayColor && !noColor)
351        {
352            // set gray map
353            gray.copyFrom(red.map, 0);
354            cmType = IcyColorMapType.GRAY;
355        }
356        else
357            cmType = IcyColorMapType.RGB;
358
359        if (notifyChange)
360            setType(cmType);
361        else
362            type = cmType;
363    }
364
365    /**
366     * Define the type of color map depending its RGBA data.<br>
367     * If map contains only alpha information then type = <code>IcyColorMapType.ALPHA</code><br>
368     * If map contains only grey level then type = <code>IcyColorMapType.GRAY</code><br>
369     * else type = <code>IcyColorMapType.RGB</code>
370     */
371    public void setTypeFromData()
372    {
373        setTypeFromData(true);
374    }
375
376    /**
377     * Set a red control point to specified index and value
378     */
379    public void setRedControlPoint(int index, int value)
380    {
381        // set control point
382        red.setControlPoint(index, value);
383    }
384
385    /**
386     * Set a green control point to specified index and value
387     */
388    public void setGreenControlPoint(int index, int value)
389    {
390        green.setControlPoint(index, value);
391    }
392
393    /**
394     * Set a blue control point to specified index and value
395     */
396    public void setBlueControlPoint(int index, int value)
397    {
398        blue.setControlPoint(index, value);
399    }
400
401    /**
402     * Set a gray control point to specified index and value
403     */
404    public void setGrayControlPoint(int index, int value)
405    {
406        gray.setControlPoint(index, value);
407    }
408
409    /**
410     * Set a alpha control point to specified index and value
411     */
412    public void setAlphaControlPoint(int index, int value)
413    {
414        alpha.setControlPoint(index, value);
415    }
416
417    /**
418     * Set RGB control point values to specified index
419     */
420    public void setRGBControlPoint(int index, Color value)
421    {
422        red.setControlPoint(index, (short) value.getRed());
423        green.setControlPoint(index, (short) value.getGreen());
424        blue.setControlPoint(index, (short) value.getBlue());
425        gray.setControlPoint(index, (short) ColorUtil.getGrayMix(value));
426    }
427
428    /**
429     * Set ARGB control point values to specified index
430     */
431    public void setARGBControlPoint(int index, Color value)
432    {
433        alpha.setControlPoint(index, (short) value.getAlpha());
434        red.setControlPoint(index, (short) value.getRed());
435        green.setControlPoint(index, (short) value.getGreen());
436        blue.setControlPoint(index, (short) value.getBlue());
437        gray.setControlPoint(index, (short) ColorUtil.getGrayMix(value));
438    }
439
440    /**
441     * Returns the blue component map.<br>
442     * If the color map type is {@link IcyColorMapType#GRAY} then it returns the gray map instead.
443     * If the color map type is {@link IcyColorMapType#ALPHA} then it returns <code>null</code>.
444     */
445    public short[] getBlueMap()
446    {
447        if (type == IcyColorMapType.RGB)
448            return blue.map;
449        if (type == IcyColorMapType.GRAY)
450            return gray.map;
451
452        return null;
453    }
454
455    /**
456     * Returns the green component map.<br>
457     * If the color map type is {@link IcyColorMapType#GRAY} then it returns the gray map instead.
458     * If the color map type is {@link IcyColorMapType#ALPHA} then it returns <code>null</code>.
459     */
460    public short[] getGreenMap()
461    {
462        if (type == IcyColorMapType.RGB)
463            return green.map;
464        if (type == IcyColorMapType.GRAY)
465            return gray.map;
466
467        return null;
468    }
469
470    /**
471     * Returns the red component map.<br>
472     * If the color map type is {@link IcyColorMapType#GRAY} then it returns the gray map instead.
473     * If the color map type is {@link IcyColorMapType#ALPHA} then it returns <code>null</code>.
474     */
475    public short[] getRedMap()
476    {
477        if (type == IcyColorMapType.RGB)
478            return red.map;
479        if (type == IcyColorMapType.GRAY)
480            return gray.map;
481
482        return null;
483    }
484
485    /**
486     * Returns the alpha component map.
487     */
488    public short[] getAlphaMap()
489    {
490        return alpha.map;
491    }
492
493    /**
494     * Returns the normalized blue component map.<br>
495     * If the color map type is {@link IcyColorMapType#GRAY} then it returns the gray map instead.
496     * If the color map type is {@link IcyColorMapType#ALPHA} then it returns <code>null</code>.
497     */
498    public float[] getNormalizedBlueMap()
499    {
500        if (type == IcyColorMapType.RGB)
501            return blue.mapf;
502        if (type == IcyColorMapType.GRAY)
503            return gray.mapf;
504
505        return null;
506    }
507
508    /**
509     * Returns the normalized green component map.<br>
510     * If the color map type is {@link IcyColorMapType#GRAY} then it returns the gray map instead.
511     * If the color map type is {@link IcyColorMapType#ALPHA} then it returns <code>null</code>.
512     */
513    public float[] getNormalizedGreenMap()
514    {
515        if (type == IcyColorMapType.RGB)
516            return green.mapf;
517        if (type == IcyColorMapType.GRAY)
518            return gray.mapf;
519
520        return null;
521    }
522
523    /**
524     * Returns the normalized red component map.<br>
525     * If the color map type is {@link IcyColorMapType#GRAY} then it returns the gray map instead.
526     * If the color map type is {@link IcyColorMapType#ALPHA} then it returns <code>null</code>.
527     */
528    public float[] getNormalizedRedMap()
529    {
530        if (type == IcyColorMapType.RGB)
531            return red.mapf;
532        if (type == IcyColorMapType.GRAY)
533            return gray.mapf;
534
535        return null;
536    }
537
538    /**
539     * Returns the normalized alpha component map.
540     */
541    public float[] getNormalizedAlphaMap()
542    {
543        return alpha.mapf;
544    }
545
546    /**
547     * Get blue intensity from an input index
548     * 
549     * @param index
550     * @return blue intensity ([0..255] range)
551     */
552    public short getBlue(int index)
553    {
554        if (type == IcyColorMapType.RGB)
555            return blue.map[index];
556        if (type == IcyColorMapType.GRAY)
557            return gray.map[index];
558
559        return 0;
560    }
561
562    /**
563     * Get green intensity from an input index
564     * 
565     * @param index
566     * @return green intensity ([0..255] range)
567     */
568    public short getGreen(int index)
569    {
570        if (type == IcyColorMapType.RGB)
571            return green.map[index];
572        if (type == IcyColorMapType.GRAY)
573            return gray.map[index];
574
575        return 0;
576    }
577
578    /**
579     * Get red intensity from an input index
580     * 
581     * @param index
582     * @return red intensity ([0..255] range)
583     */
584    public short getRed(int index)
585    {
586        if (type == IcyColorMapType.RGB)
587            return red.map[index];
588        if (type == IcyColorMapType.GRAY)
589            return gray.map[index];
590
591        return 0;
592    }
593
594    /**
595     * Get alpha intensity from an input index
596     * 
597     * @param index
598     * @return alpha intensity ([0..255] range)
599     */
600    public short getAlpha(int index)
601    {
602        return alpha.map[index];
603    }
604
605    /**
606     * Get normalized blue intensity from an input index
607     * 
608     * @param index
609     * @return normalized blue intensity
610     */
611    public float getNormalizedBlue(int index)
612    {
613        if (type == IcyColorMapType.RGB)
614            return blue.mapf[index];
615        if (type == IcyColorMapType.GRAY)
616            return gray.mapf[index];
617
618        return 0;
619    }
620
621    /**
622     * Get normalized green intensity from an input index
623     * 
624     * @param index
625     * @return normalized green intensity
626     */
627    public float getNormalizedGreen(int index)
628    {
629        if (type == IcyColorMapType.RGB)
630            return green.mapf[index];
631        if (type == IcyColorMapType.GRAY)
632            return gray.mapf[index];
633
634        return 0;
635    }
636
637    /**
638     * Get normalized red intensity from an input index
639     * 
640     * @param index
641     * @return normalized red intensity
642     */
643    public float getNormalizedRed(int index)
644    {
645        if (type == IcyColorMapType.RGB)
646            return red.mapf[index];
647        if (type == IcyColorMapType.GRAY)
648            return gray.mapf[index];
649
650        return 0;
651    }
652
653    /**
654     * Get alpha normalized intensity from an input index
655     * 
656     * @param index
657     * @return normalized alpha intensity
658     */
659    public float getNormalizedAlpha(int index)
660    {
661        return alpha.mapf[index];
662    }
663
664    /**
665     * Get blue intensity from a normalized input index
666     * 
667     * @param index
668     * @return blue intensity ([0..255] range)
669     */
670    public short getBlue(float index)
671    {
672        return getBlue((int) (index * MAX_INDEX));
673    }
674
675    /**
676     * Get green intensity from a normalized input index
677     * 
678     * @param index
679     * @return green intensity ([0..255] range)
680     */
681    public short getGreen(float index)
682    {
683        return getGreen((int) (index * MAX_INDEX));
684    }
685
686    /**
687     * Get red intensity from a normalized input index
688     * 
689     * @param index
690     * @return red intensity ([0..255] range)
691     */
692    public short getRed(float index)
693    {
694        return getRed((int) (index * MAX_INDEX));
695    }
696
697    /**
698     * Get alpha intensity from a normalized input index
699     * 
700     * @param index
701     * @return alpha intensity ([0..255] range)
702     */
703    public short getAlpha(float index)
704    {
705        return getAlpha((int) (index * MAX_INDEX));
706    }
707
708    /**
709     * Get normalized blue intensity from a normalized input index
710     * 
711     * @param index
712     * @return normalized blue intensity
713     */
714    public float getNormalizedBlue(float index)
715    {
716        return getNormalizedBlue((int) (index * MAX_INDEX));
717    }
718
719    /**
720     * Get normalized green intensity from a normalized input index
721     * 
722     * @param index
723     * @return normalized green intensity
724     */
725    public float getNormalizedGreen(float index)
726    {
727        return getNormalizedGreen((int) (index * MAX_INDEX));
728    }
729
730    /**
731     * Get normalized red intensity from a normalized input index
732     * 
733     * @param index
734     * @return normalized red intensity
735     */
736    public float getNormalizedRed(float index)
737    {
738        return getNormalizedRed((int) (index * MAX_INDEX));
739    }
740
741    /**
742     * Get normalized alpha intensity from a normalized input index
743     * 
744     * @param index
745     * @return normalized alpha intensity
746     */
747    public float getNormalizedAlpha(float index)
748    {
749        return getNormalizedAlpha((int) (index * MAX_INDEX));
750    }
751
752    /**
753     * Set red intensity to specified index
754     */
755    public void setRed(int index, short value)
756    {
757        red.setValue(index, value);
758    }
759
760    /**
761     * Set green intensity to specified index
762     */
763    public void setGreen(int index, short value)
764    {
765        green.setValue(index, value);
766    }
767
768    /**
769     * Set blue intensity to specified index
770     */
771    public void setBlue(int index, short value)
772    {
773        blue.setValue(index, value);
774    }
775
776    /**
777     * Set gray intensity to specified index
778     */
779    public void setGray(int index, short value)
780    {
781        gray.setValue(index, value);
782    }
783
784    /**
785     * Set alpha intensity to specified index
786     */
787    public void setAlpha(int index, short value)
788    {
789        alpha.setValue(index, value);
790    }
791
792    /**
793     * Set red intensity (normalized) to specified index
794     */
795    public void setNormalizedRed(int index, float value)
796    {
797        red.setNormalizedValue(index, value);
798    }
799
800    /**
801     * Set green intensity (normalized) to specified index
802     */
803    public void setNormalizedGreen(int index, float value)
804    {
805        green.setNormalizedValue(index, value);
806    }
807
808    /**
809     * Set blue intensity (normalized) to specified index
810     */
811    public void setNormalizedBlue(int index, float value)
812    {
813        blue.setNormalizedValue(index, value);
814    }
815
816    /**
817     * Set gray intensity (normalized) to specified index
818     */
819    public void setNormalizedGray(int index, float value)
820    {
821        gray.setNormalizedValue(index, value);
822    }
823
824    /**
825     * Set alpha intensity (normalized) to specified index
826     */
827    public void setNormalizedAlpha(int index, float value)
828    {
829        alpha.setNormalizedValue(index, value);
830    }
831
832    /**
833     * Set RGB color to specified index
834     */
835    public void setRGB(int index, int rgb)
836    {
837        alpha.setValue(index, MAX_LEVEL);
838        red.setValue(index, (rgb >> 16) & 0xFF);
839        green.setValue(index, (rgb >> 8) & 0xFF);
840        blue.setValue(index, (rgb >> 0) & 0xFF);
841        gray.setValue(index, ColorUtil.getGrayMix(rgb));
842    }
843
844    /**
845     * Set RGB color to specified index
846     */
847    public void setRGB(int index, Color value)
848    {
849        setRGB(index, value.getRGB());
850    }
851
852    /**
853     * Set ARGB color to specified index
854     */
855    public void setARGB(int index, int argb)
856    {
857        alpha.setValue(index, (argb >> 24) & 0xFF);
858        red.setValue(index, (argb >> 16) & 0xFF);
859        green.setValue(index, (argb >> 8) & 0xFF);
860        blue.setValue(index, (argb >> 0) & 0xFF);
861        gray.setValue(index, ColorUtil.getGrayMix(argb));
862    }
863
864    /**
865     * Set ARGB color to specified index
866     */
867    public void setARGB(int index, Color value)
868    {
869        setARGB(index, value.getRGB());
870    }
871
872    /**
873     * Set the alpha channel to opaque
874     */
875    public void setAlphaToOpaque()
876    {
877        alpha.beginUpdate();
878        try
879        {
880            alpha.removeAllControlPoint();
881            alpha.setControlPoint(0, 1f);
882            alpha.setControlPoint(255, 1f);
883        }
884        finally
885        {
886            alpha.endUpdate();
887        }
888    }
889
890    /**
891     * Set the alpha channel to linear opacity (0 to 1)
892     */
893    public void setAlphaToLinear()
894    {
895        alpha.beginUpdate();
896        try
897        {
898            alpha.removeAllControlPoint();
899            alpha.setControlPoint(0, 0f);
900            alpha.setControlPoint(255, 1f);
901        }
902        finally
903        {
904            alpha.endUpdate();
905        }
906    }
907
908    /**
909     * Set the alpha channel to an optimized linear transparency for 3D volume display
910     */
911    public void setAlphaToLinear3D()
912    {
913        alpha.beginUpdate();
914        try
915        {
916            alpha.removeAllControlPoint();
917            alpha.setControlPoint(0, 0f);
918            alpha.setControlPoint(32, 0f);
919            alpha.setControlPoint(255, 0.4f);
920        }
921        finally
922        {
923            alpha.endUpdate();
924        }
925    }
926
927    /**
928     * @deprecated Use {@link #setAlphaToLinear3D()} instead
929     */
930    @Deprecated
931    public void setDefaultAlphaFor3D()
932    {
933        setAlphaToLinear3D();
934    }
935
936    /**
937     * @return the name
938     */
939    public String getName()
940    {
941        return name;
942    }
943
944    /**
945     * @param name
946     *        the name to set
947     */
948    public void setName(String name)
949    {
950        this.name = name;
951    }
952
953    /**
954     * @return the enabled
955     */
956    public boolean isEnabled()
957    {
958        return enabled;
959    }
960
961    /**
962     * Set enabled flag.<br>
963     * This flag is used to test if the color map is enabled or not.<br>
964     * It is up to the developer to implement it or not.
965     * 
966     * @param enabled
967     *        the enabled to set
968     */
969    public void setEnabled(boolean enabled)
970    {
971        if (this.enabled != enabled)
972        {
973            this.enabled = enabled;
974            changed(IcyColorMapEventType.ENABLED_CHANGED);
975        }
976    }
977
978    /**
979     * Gets a Color object representing the color at the specified index
980     * 
981     * @param index
982     *        the index of the color map to retrieve
983     * @return a Color object
984     */
985    public Color getColor(int index)
986    {
987        switch (type)
988        {
989            case RGB:
990                return new Color(red.map[index], green.map[index], blue.map[index], alpha.map[index]);
991            case GRAY:
992                return new Color(gray.map[index], gray.map[index], gray.map[index], alpha.map[index]);
993            case ALPHA:
994                return new Color(0, 0, 0, alpha.map[index]);
995        }
996
997        return Color.black;
998    }
999
1000    /**
1001     * Return the pre-multiplied RGB cache
1002     */
1003    public int[][] getPremulRGB()
1004    {
1005        return premulRGB;
1006    }
1007
1008    /**
1009     * Return the pre-multiplied RGB cache (normalized)
1010     */
1011    public float[][] getPremulRGBNorm()
1012    {
1013        return premulRGBNorm;
1014    }
1015
1016    /**
1017     * Copy data from specified source colormap.
1018     * 
1019     * @param copyAlpha
1020     *        Also copy the alpha information.
1021     */
1022    public void copyFrom(IcyColorMap srcColorMap, boolean copyAlpha)
1023    {
1024        beginUpdate();
1025        try
1026        {
1027            // copy colormap band
1028            red.copyFrom(srcColorMap.red);
1029            green.copyFrom(srcColorMap.green);
1030            blue.copyFrom(srcColorMap.blue);
1031            gray.copyFrom(srcColorMap.gray);
1032            // copy alpha information for alpha type colormap
1033            if (copyAlpha || isAlpha())
1034                alpha.copyFrom(srcColorMap.alpha);
1035            // copy type
1036            setType(srcColorMap.type);
1037            // copy name
1038            setName(srcColorMap.getName());
1039        }
1040        finally
1041        {
1042            endUpdate();
1043        }
1044    }
1045
1046    /**
1047     * Copy data from specified source colormap
1048     */
1049    public void copyFrom(IcyColorMap srcColorMap)
1050    {
1051        copyFrom(srcColorMap, true);
1052    }
1053
1054    /**
1055     * Copy data from specified 2D byte array.
1056     * 
1057     * @param copyAlpha
1058     *        Also copy the alpha information.
1059     */
1060    public void copyFrom(byte[][] maps, boolean copyAlpha)
1061    {
1062        final int len = maps.length;
1063
1064        beginUpdate();
1065        try
1066        {
1067            // red component
1068            if (len > 0)
1069                red.copyFrom(maps[0]);
1070            if (len > 1)
1071                green.copyFrom(maps[1]);
1072            if (len > 2)
1073                blue.copyFrom(maps[2]);
1074            if (copyAlpha && (len > 3))
1075                alpha.copyFrom(maps[3]);
1076        }
1077        finally
1078        {
1079            endUpdate();
1080        }
1081    }
1082
1083    /**
1084     * Copy data from specified 2D byte array
1085     */
1086    public void copyFrom(byte[][] maps)
1087    {
1088        copyFrom(maps, true);
1089    }
1090
1091    /**
1092     * Copy data from specified 2D short array.
1093     * 
1094     * @param copyAlpha
1095     *        Also copy the alpha information.
1096     */
1097    public void copyFrom(short[][] maps, boolean copyAlpha)
1098    {
1099        final int len = maps.length;
1100
1101        beginUpdate();
1102        try
1103        {
1104            // red component
1105            if (len > 0)
1106                red.copyFrom(maps[0], 8);
1107            if (len > 1)
1108                green.copyFrom(maps[1], 8);
1109            if (len > 2)
1110                blue.copyFrom(maps[2], 8);
1111            if (copyAlpha && (len > 3))
1112                alpha.copyFrom(maps[3], 8);
1113        }
1114        finally
1115        {
1116            endUpdate();
1117        }
1118    }
1119
1120    /**
1121     * Copy data from specified 2D short array.
1122     */
1123    public void copyFrom(short[][] maps)
1124    {
1125        copyFrom(maps, true);
1126    }
1127
1128    /**
1129     * Return true if this is a linear type colormap.<br>
1130     * Linear colormap are used to display plain gray or color image.<br>
1131     * A non linear colormap means you usually have an indexed color image or
1132     * you want to enhance contrast/color in display.
1133     */
1134    public boolean isLinear()
1135    {
1136        switch (type)
1137        {
1138            default:
1139                return red.isLinear() && green.isLinear() && blue.isLinear();
1140            case GRAY:
1141                return gray.isLinear();
1142            case ALPHA:
1143                return alpha.isLinear();
1144        }
1145    }
1146
1147    /**
1148     * Return true if this is a total black colormap.
1149     */
1150    public boolean isBlack()
1151    {
1152        switch (type)
1153        {
1154            case RGB:
1155                for (int i = 0; i < MAX_INDEX; i++)
1156                    if ((red.map[i] | green.map[i] | blue.map[i]) != 0)
1157                        return false;
1158                return true;
1159
1160            case GRAY:
1161                for (int i = 0; i < MAX_INDEX; i++)
1162                    if (gray.map[i] != 0)
1163                        return false;
1164                return true;
1165
1166            default:
1167                return false;
1168        }
1169    }
1170
1171    /**
1172     * Returns the dominant color of this colormap.<br>
1173     * Warning: this need sometime to compute.
1174     */
1175    public Color getDominantColor()
1176    {
1177        final Color colors[] = new Color[SIZE];
1178
1179        for (int i = 0; i < colors.length; i++)
1180            colors[i] = getColor(i);
1181
1182        return ColorUtil.getDominantColor(colors);
1183    }
1184
1185    /**
1186     * Update internal RGB cache
1187     */
1188    private void updateRGBCache()
1189    {
1190        for (int i = 0; i < SIZE; i++)
1191        {
1192            final float af = alpha.mapf[i];
1193            final float rgbn[] = premulRGBNorm[i];
1194
1195            switch (type)
1196            {
1197                case GRAY:
1198                    final float grayValue = gray.mapf[i] * af;
1199                    rgbn[0] = grayValue;
1200                    rgbn[1] = grayValue;
1201                    rgbn[2] = grayValue;
1202                    break;
1203
1204                case RGB:
1205                    rgbn[0] = blue.mapf[i] * af;
1206                    rgbn[1] = green.mapf[i] * af;
1207                    rgbn[2] = red.mapf[i] * af;
1208                    break;
1209
1210                default:
1211                    rgbn[0] = 0f;
1212                    rgbn[1] = 0f;
1213                    rgbn[2] = 0f;
1214                    break;
1215            }
1216
1217            final int rgb[] = premulRGB[i];
1218
1219            rgb[0] = (int) (rgbn[0] * MAX_LEVEL);
1220            rgb[1] = (int) (rgbn[1] * MAX_LEVEL);
1221            rgb[2] = (int) (rgbn[2] * MAX_LEVEL);
1222        }
1223    }
1224
1225    /**
1226     * Add a listener
1227     * 
1228     * @param listener
1229     */
1230    public void addListener(IcyColorMapListener listener)
1231    {
1232        listeners.add(IcyColorMapListener.class, listener);
1233    }
1234
1235    /**
1236     * Remove a listener
1237     * 
1238     * @param listener
1239     */
1240    public void removeListener(IcyColorMapListener listener)
1241    {
1242        listeners.remove(IcyColorMapListener.class, listener);
1243    }
1244
1245    /**
1246     * fire event
1247     */
1248    public void fireEvent(IcyColorMapEvent e)
1249    {
1250        for (IcyColorMapListener listener : listeners.getListeners(IcyColorMapListener.class))
1251            listener.colorMapChanged(e);
1252    }
1253
1254    /**
1255     * called when colormap data changed
1256     */
1257    public void changed()
1258    {
1259        changed(IcyColorMapEventType.MAP_CHANGED);
1260    }
1261
1262    /**
1263     * called when colormap changed
1264     */
1265    private void changed(IcyColorMapEventType type)
1266    {
1267        // handle changed via updater object
1268        updater.changed(new IcyColorMapEvent(this, type));
1269    }
1270
1271    @Override
1272    public void onChanged(CollapsibleEvent e)
1273    {
1274        final IcyColorMapEvent event = (IcyColorMapEvent) e;
1275
1276        switch (event.getType())
1277        {
1278            // refresh RGB cache
1279            case MAP_CHANGED:
1280            case TYPE_CHANGED:
1281                updateRGBCache();
1282                break;
1283
1284            default:
1285                break;
1286        }
1287
1288        // notify listener we have changed
1289        fireEvent(event);
1290    }
1291
1292    /**
1293     * @see icy.common.UpdateEventHandler#beginUpdate()
1294     */
1295    public void beginUpdate()
1296    {
1297        updater.beginUpdate();
1298
1299        red.beginUpdate();
1300        green.beginUpdate();
1301        blue.beginUpdate();
1302        gray.beginUpdate();
1303        alpha.beginUpdate();
1304    }
1305
1306    /**
1307     * @see icy.common.UpdateEventHandler#endUpdate()
1308     */
1309    public void endUpdate()
1310    {
1311        alpha.endUpdate();
1312        gray.endUpdate();
1313        blue.endUpdate();
1314        green.endUpdate();
1315        red.endUpdate();
1316
1317        updater.endUpdate();
1318    }
1319
1320    /**
1321     * @see icy.common.UpdateEventHandler#isUpdating()
1322     */
1323    public boolean isUpdating()
1324    {
1325        return updater.isUpdating();
1326    }
1327
1328    @Override
1329    public String toString()
1330    {
1331        return name;
1332    }
1333
1334    /**
1335     * Return true if the colormap has the same type and same color intensities than specified one.
1336     */
1337    @Override
1338    public boolean equals(Object obj)
1339    {
1340        if (obj == this)
1341            return true;
1342
1343        if (obj instanceof IcyColorMap)
1344        {
1345            final IcyColorMap colormap = (IcyColorMap) obj;
1346
1347            if (colormap.getType() != type)
1348                return false;
1349
1350            if (!red.equals(colormap.red))
1351                return false;
1352            if (!green.equals(colormap.green))
1353                return false;
1354            if (!blue.equals(colormap.blue))
1355                return false;
1356            if (!gray.equals(colormap.gray))
1357                return false;
1358            if (!alpha.equals(colormap.alpha))
1359                return false;
1360
1361            return true;
1362        }
1363
1364        return super.equals(obj);
1365    }
1366
1367    @Override
1368    public int hashCode()
1369    {
1370        return red.map.hashCode() ^ green.map.hashCode() ^ blue.map.hashCode() ^ gray.map.hashCode()
1371                ^ alpha.map.hashCode() ^ type.ordinal();
1372    }
1373
1374    @Override
1375    public boolean loadFromXML(Node node)
1376    {
1377        if (node == null)
1378            return false;
1379
1380        beginUpdate();
1381        try
1382        {
1383            setName(XMLUtil.getElementValue(node, ID_NAME, ""));
1384            setEnabled(XMLUtil.getElementBooleanValue(node, ID_ENABLED, true));
1385            setType(IcyColorMapType.valueOf(XMLUtil.getElementValue(node, ID_TYPE, IcyColorMapType.RGB.toString())));
1386
1387            boolean result = true;
1388
1389            result = result && red.loadFromXML(XMLUtil.getElement(node, ID_RED));
1390            result = result && green.loadFromXML(XMLUtil.getElement(node, ID_GREEN));
1391            result = result && blue.loadFromXML(XMLUtil.getElement(node, ID_BLUE));
1392            result = result && gray.loadFromXML(XMLUtil.getElement(node, ID_GRAY));
1393            result = result && alpha.loadFromXML(XMLUtil.getElement(node, ID_ALPHA));
1394
1395            return result;
1396        }
1397        finally
1398        {
1399            endUpdate();
1400        }
1401    }
1402
1403    @Override
1404    public boolean saveToXML(Node node)
1405    {
1406        if (node == null)
1407            return false;
1408
1409        XMLUtil.setElementValue(node, ID_NAME, getName());
1410        XMLUtil.setElementBooleanValue(node, ID_ENABLED, isEnabled());
1411        XMLUtil.setElementValue(node, ID_TYPE, getType().toString());
1412
1413        boolean result = true;
1414
1415        result = result && red.saveToXML(XMLUtil.setElement(node, ID_RED));
1416        result = result && green.saveToXML(XMLUtil.setElement(node, ID_GREEN));
1417        result = result && blue.saveToXML(XMLUtil.setElement(node, ID_BLUE));
1418        result = result && gray.saveToXML(XMLUtil.setElement(node, ID_GRAY));
1419        result = result && alpha.saveToXML(XMLUtil.setElement(node, ID_ALPHA));
1420
1421        return result;
1422    }
1423
1424}