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.colormodel;
020
021import java.awt.image.BandedSampleModel;
022import java.awt.image.ColorModel;
023import java.awt.image.ComponentSampleModel;
024import java.awt.image.DataBufferByte;
025import java.awt.image.DataBufferDouble;
026import java.awt.image.DataBufferFloat;
027import java.awt.image.DataBufferInt;
028import java.awt.image.DataBufferShort;
029import java.awt.image.DataBufferUShort;
030import java.awt.image.Raster;
031import java.awt.image.SampleModel;
032import java.awt.image.WritableRaster;
033import java.lang.reflect.Field;
034import java.util.ArrayList;
035import java.util.List;
036
037import icy.common.CollapsibleEvent;
038import icy.common.UpdateEventHandler;
039import icy.common.listener.ChangeListener;
040import icy.image.colormap.IcyColorMap;
041import icy.image.colormodel.IcyColorModelEvent.IcyColorModelEventType;
042import icy.image.colorspace.IcyColorSpace;
043import icy.image.colorspace.IcyColorSpaceEvent;
044import icy.image.colorspace.IcyColorSpaceListener;
045import icy.image.lut.LUT;
046import icy.math.Scaler;
047import icy.math.ScalerEvent;
048import icy.math.ScalerListener;
049import icy.type.DataType;
050import icy.type.TypeUtil;
051import icy.type.collection.array.Array1DUtil;
052import icy.type.collection.array.ArrayUtil;
053import icy.util.ReflectionUtil;
054
055/**
056 * @author stephane
057 */
058public abstract class IcyColorModel extends ColorModel implements ScalerListener, IcyColorSpaceListener, ChangeListener
059{
060    /**
061     * scalers for normalization
062     */
063    protected final Scaler[] normalScalers;
064    /**
065     * scalers for colorMap
066     */
067    protected final Scaler[] colormapScalers;
068
069    /**
070     * data type
071     */
072    protected final DataType dataType;
073
074    /**
075     * overridden variables
076     */
077    protected final int numComponents;
078
079    /**
080     * listeners
081     */
082    private final List<IcyColorModelListener> listeners;
083
084    /**
085     * internal updater
086     */
087    private final UpdateEventHandler updater;
088
089    /**
090     * Default constructor
091     */
092    IcyColorModel(int numComponents, DataType dataType, int[] bits)
093    {
094        super(dataType.getBitSize(), bits, new IcyColorSpace(numComponents), true, false, TRANSLUCENT,
095                dataType.toDataBufferType());
096
097        if (numComponents == 0)
098            throw new IllegalArgumentException("Number of components should be > 0");
099
100        // overridden variable
101        this.numComponents = numComponents;
102
103        listeners = new ArrayList<IcyColorModelListener>();
104        updater = new UpdateEventHandler(this, false);
105
106        // data type information
107        this.dataType = dataType;
108
109        // get default min and max for datatype
110        final double[] defaultBounds = dataType.getDefaultBounds();
111        final double min = defaultBounds[0];
112        final double max = defaultBounds[1];
113        // float type flag
114        final boolean isFloat = dataType.isFloat();
115
116        // allocating scalers
117        normalScalers = new Scaler[numComponents];
118        colormapScalers = new Scaler[numComponents];
119        // defining scalers
120        for (int i = 0; i < numComponents; i++)
121        {
122            // scale for normalization
123            normalScalers[i] = new Scaler(min, max, 0f, 1f, !isFloat);
124            // scale for colormap
125            colormapScalers[i] = new Scaler(min, max, 0f, IcyColorMap.MAX_INDEX, !isFloat);
126            // add listener to the colormap scalers only
127            colormapScalers[i].addListener(this);
128        }
129
130        // add the listener to colorSpace
131        getIcyColorSpace().addListener(this);
132    }
133
134    /**
135     * @deprecated use {@link #IcyColorModel(int, DataType, int[])} instead
136     */
137    @Deprecated
138    IcyColorModel(int numComponents, int dataType, boolean signed, int[] bits)
139    {
140        this(numComponents, DataType.getDataType(dataType, signed), bits);
141    }
142
143    /**
144     * Creates a new ColorModel with the given color component and image data type
145     * 
146     * @param numComponents
147     *        number of component
148     * @param dataType
149     *        the type of image data (see {@link DataType})
150     * @return a IcyColorModel object
151     */
152    public static IcyColorModel createInstance(int numComponents, DataType dataType)
153    {
154        // define bits size
155        final int bits = dataType.getBitSize();
156        // we have to fake one more extra component for alpha in ColorModel class
157        final int numComponentFixed = numComponents + 1;
158        final int[] componentBits = new int[numComponentFixed];
159
160        for (int i = 0; i < numComponentFixed; i++)
161            componentBits[i] = bits;
162
163        switch (dataType)
164        {
165            case UBYTE:
166                return new UByteColorModel(numComponents, componentBits);
167            case BYTE:
168                return new ByteColorModel(numComponents, componentBits);
169            case USHORT:
170                return new UShortColorModel(numComponents, componentBits);
171            case SHORT:
172                return new ShortColorModel(numComponents, componentBits);
173            case UINT:
174                return new UIntColorModel(numComponents, componentBits);
175            case INT:
176                return new IntColorModel(numComponents, componentBits);
177            case ULONG:
178                return new ULongColorModel(numComponents, componentBits);
179            case LONG:
180                return new LongColorModel(numComponents, componentBits);
181            case FLOAT:
182                return new FloatColorModel(numComponents, componentBits);
183            case DOUBLE:
184                return new DoubleColorModel(numComponents, componentBits);
185            default:
186                throw new IllegalArgumentException("Unsupported data type !");
187        }
188    }
189
190    /**
191     * @deprecated use {@link #createInstance(int, DataType)} instead
192     */
193    @Deprecated
194    public static IcyColorModel createInstance(int numComponents, int dataType, boolean signed)
195    {
196        return createInstance(numComponents, DataType.getDataType(dataType, signed));
197    }
198
199    /**
200     * Creates a new ColorModel from a given icyColorModel
201     * 
202     * @param colorModel
203     *        icyColorModel
204     * @param copyColormap
205     *        flag to indicate if we want to copy colormaps from the given icyColorModel
206     * @param copyBounds
207     *        flag to indicate if we want to copy bounds from the given icyColorModel
208     * @return a IcyColorModel object
209     */
210    public static IcyColorModel createInstance(IcyColorModel colorModel, boolean copyColormap, boolean copyBounds)
211    {
212        final IcyColorModel result = IcyColorModel.createInstance(colorModel.getNumComponents(),
213                colorModel.getDataType_());
214
215        result.beginUpdate();
216        try
217        {
218            // copy colormaps from colorModel ?
219            if (copyColormap)
220                result.setColorMaps(colorModel);
221            // copy bounds from colorModel ?
222            if (copyBounds)
223                result.setBounds(colorModel);
224        }
225        finally
226        {
227            result.endUpdate();
228        }
229
230        return result;
231    }
232
233    /**
234     * Create default ColorModel : 4 components, unsigned byte data type
235     */
236    public static IcyColorModel createInstance()
237    {
238        return createInstance(4, DataType.UBYTE);
239    }
240
241    public static BandedSampleModel createCompatibleSampleModel(int transferType, int w, int h, int numComponent)
242    {
243        return new BandedSampleModel(transferType, w, h, numComponent);
244    }
245
246    /**
247     * Create a writable raster from specified data and size.<br>
248     * The Object data is internally a 2D array [][]
249     */
250    public static WritableRaster createWritableRaster(Object data, int w, int h)
251    {
252        if (ArrayUtil.getDim(data) != 2)
253            throw new IllegalArgumentException(
254                    "IcyColorModel.createWritableRaster(..) error: 'data' argument should be a 2D array !");
255
256        final DataType dataType = ArrayUtil.getDataType(data);
257        final int sizeC = ArrayUtil.getLength(data);
258
259        final SampleModel sm = createCompatibleSampleModel(dataType.toDataBufferType(), w, h, sizeC);
260
261        switch (dataType)
262        {
263            case UBYTE:
264            case BYTE:
265                return Raster.createWritableRaster(sm, new DataBufferByte((byte[][]) data, w * h), null);
266            case SHORT:
267                return Raster.createWritableRaster(sm, new DataBufferShort((short[][]) data, w * h), null);
268            case USHORT:
269                return Raster.createWritableRaster(sm, new DataBufferUShort((short[][]) data, w * h), null);
270            case UINT:
271            case INT:
272                return Raster.createWritableRaster(sm, new DataBufferInt((int[][]) data, w * h), null);
273            case FLOAT:
274                return Raster.createWritableRaster(sm, new DataBufferFloat((float[][]) data, w * h), null);
275            case DOUBLE:
276                return Raster.createWritableRaster(sm, new DataBufferDouble((double[][]) data, w * h), null);
277            default:
278                throw new IllegalArgumentException(
279                        "IcyColorModel.createWritableRaster(..) error: unsupported data type : " + dataType);
280        }
281    }
282
283    @Override
284    public SampleModel createCompatibleSampleModel(int w, int h)
285    {
286        return createCompatibleSampleModel(transferType, w, h, getNumComponents());
287    }
288
289    @Override
290    public WritableRaster createCompatibleWritableRaster(int w, int h)
291    {
292        final SampleModel sm = createCompatibleSampleModel(w, h);
293
294        return Raster.createWritableRaster(sm, sm.createDataBuffer(), null);
295    }
296
297    /**
298     * Create a writable raster from specified data and size.<br>
299     */
300    public WritableRaster createWritableRaster(Object[] data, int w, int h)
301    {
302        final SampleModel sm = createCompatibleSampleModel(w, h);
303
304        switch (dataType)
305        {
306            case UBYTE:
307            case BYTE:
308                return Raster.createWritableRaster(sm, new DataBufferByte((byte[][]) data, w * h), null);
309            case SHORT:
310                return Raster.createWritableRaster(sm, new DataBufferShort((short[][]) data, w * h), null);
311            case USHORT:
312                return Raster.createWritableRaster(sm, new DataBufferUShort((short[][]) data, w * h), null);
313            case UINT:
314            case INT:
315                return Raster.createWritableRaster(sm, new DataBufferInt((int[][]) data, w * h), null);
316            case FLOAT:
317                return Raster.createWritableRaster(sm, new DataBufferFloat((float[][]) data, w * h), null);
318            case DOUBLE:
319                return Raster.createWritableRaster(sm, new DataBufferDouble((double[][]) data, w * h), null);
320            default:
321                throw new IllegalArgumentException(
322                        "IcyColorModel.createWritableRaster(..) error : unsupported data type : " + dataType);
323        }
324    }
325
326    /**
327     * Set bounds from specified {@link IcyColorModel}
328     */
329    public void setBounds(IcyColorModel source)
330    {
331        beginUpdate();
332        try
333        {
334            for (int i = 0; i < numComponents; i++)
335            {
336                final Scaler srcNormalScaler = source.getNormalScalers()[i];
337                final Scaler dstNormalScaler = normalScalers[i];
338                final Scaler srcColorMapScaler = source.getColormapScalers()[i];
339                final Scaler dstColorMapScaler = colormapScalers[i];
340
341                dstNormalScaler.beginUpdate();
342                try
343                {
344                    dstNormalScaler.setAbsLeftRightIn(srcNormalScaler.getAbsLeftIn(), srcNormalScaler.getAbsRightIn());
345                    dstNormalScaler.setLeftRightIn(srcNormalScaler.getLeftIn(), srcNormalScaler.getRightIn());
346                    dstNormalScaler.setLeftRightOut(srcNormalScaler.getLeftOut(), srcNormalScaler.getRightOut());
347                }
348                finally
349                {
350                    dstNormalScaler.endUpdate();
351                }
352
353                dstColorMapScaler.beginUpdate();
354                try
355                {
356                    dstColorMapScaler.setAbsLeftRightIn(srcColorMapScaler.getAbsLeftIn(),
357                            srcColorMapScaler.getAbsRightIn());
358                    dstColorMapScaler.setLeftRightIn(srcColorMapScaler.getLeftIn(), srcColorMapScaler.getRightIn());
359                    dstColorMapScaler.setLeftRightOut(srcColorMapScaler.getLeftOut(), srcColorMapScaler.getRightOut());
360                }
361                finally
362                {
363                    dstColorMapScaler.endUpdate();
364                }
365            }
366        }
367        finally
368        {
369            endUpdate();
370        }
371    }
372
373    /**
374     * @deprecated Use {@link #setBounds(IcyColorModel)} instead.
375     */
376    @Deprecated
377    public void copyBounds(IcyColorModel source)
378    {
379        setBounds(source);
380    }
381
382    /**
383     * Return the toRGB colormap of specified RGB component
384     */
385    public IcyColorMap getColorMap(int component)
386    {
387        return getIcyColorSpace().getColorMap(component);
388    }
389
390    /**
391     * @deprecated Use {@link #getColorMap(int)} instead (different case).
392     */
393    @Deprecated
394    public IcyColorMap getColormap(int component)
395    {
396        return getColorMap(component);
397    }
398
399    /**
400     * Set the toRGB colormaps from a compatible colorModel.
401     * 
402     * @param source
403     *        source ColorModel to copy colormap from
404     */
405    public void setColorMaps(ColorModel source)
406    {
407        getIcyColorSpace().setColorMaps(source);
408    }
409
410    /**
411     * @deprecated Use {@link #setColorMaps(ColorModel)} instead (different case).
412     */
413    @Deprecated
414    public void setColormaps(ColorModel source)
415    {
416        setColorMaps(source);
417    }
418
419    /**
420     * @deprecated Use {@link #setColorMaps(ColorModel)} instead.
421     */
422    @Deprecated
423    public void copyColormap(ColorModel source)
424    {
425        setColorMaps(source);
426    }
427
428    /**
429     * Set the toRGB colormap of specified component (actually copy the content).
430     * 
431     * @param component
432     *        component we want to set the colormap
433     * @param map
434     *        source colormap to copy
435     * @param setAlpha
436     *        also set the alpha information
437     */
438    public void setColorMap(int component, IcyColorMap map, boolean setAlpha)
439    {
440        getIcyColorSpace().setColorMap(component, map, setAlpha);
441    }
442
443    /**
444     * @deprecated Use {@link #setColorMap(int, IcyColorMap, boolean)} instead.
445     */
446    @Deprecated
447    public void setColormap(int component, IcyColorMap map)
448    {
449        setColorMap(component, map, true);
450    }
451
452    /**
453     * @see java.awt.image.ColorModel#getAlpha(int)
454     */
455    @Override
456    public int getAlpha(int pixel)
457    {
458        throw new IllegalArgumentException("Argument type not supported for this color model");
459    }
460
461    /**
462     * @see java.awt.image.ColorModel#getBlue(int)
463     */
464    @Override
465    public int getBlue(int pixel)
466    {
467        throw new IllegalArgumentException("Argument type not supported for this color model");
468    }
469
470    /**
471     * @see java.awt.image.ColorModel#getGreen(int)
472     */
473    @Override
474    public int getGreen(int pixel)
475    {
476        throw new IllegalArgumentException("Argument type not supported for this color model");
477    }
478
479    /**
480     * @see java.awt.image.ColorModel#getRed(int)
481     */
482    @Override
483    public int getRed(int pixel)
484    {
485        throw new IllegalArgumentException("Argument type not supported for this color model");
486    }
487
488    @Override
489    public abstract int getRGB(Object inData);
490
491    public abstract int getRGB(Object pixel, LUT lut);
492
493    /**
494     * 
495     */
496    @Override
497    public int getBlue(Object pixel)
498    {
499        return getRGB(pixel) & 0xFF;
500    }
501
502    /**
503     * 
504     */
505    @Override
506    public int getGreen(Object pixel)
507    {
508        return (getRGB(pixel) >> 8) & 0xFF;
509    }
510
511    /**
512     * 
513     */
514    @Override
515    public int getRed(Object pixel)
516    {
517        return (getRGB(pixel) >> 16) & 0xFF;
518    }
519
520    /**
521     * 
522     */
523    @Override
524    public int getAlpha(Object pixel)
525    {
526        return (getRGB(pixel) >> 24) & 0xFF;
527    }
528
529    /**
530     * @see java.awt.image.ColorModel#getComponents(int, int[], int)
531     */
532    @Override
533    public int[] getComponents(int pixel, int[] components, int offset)
534    {
535        throw new IllegalArgumentException("Not supported in this ColorModel");
536    }
537
538    /**
539     * @see java.awt.image.ColorModel#getComponents(Object, int[], int)
540     */
541    @Override
542    public abstract int[] getComponents(Object pixel, int[] components, int offset);
543
544    /**
545     * @see java.awt.image.ColorModel#getNormalizedComponents(Object, float[], int)
546     */
547    @Override
548    public abstract float[] getNormalizedComponents(Object pixel, float[] normComponents, int normOffset);
549
550    /**
551     * @see java.awt.image.ColorModel#getNormalizedComponents(int[], int, float[], int)
552     */
553    @Override
554    public float[] getNormalizedComponents(int[] components, int offset, float[] normComponents, int normOffset)
555    {
556        if ((components.length - offset) < numComponents)
557            throw new IllegalArgumentException("Incorrect number of components.  Expecting " + numComponents);
558
559        final float[] result = Array1DUtil.allocIfNull(normComponents, numComponents + normOffset);
560
561        for (int i = 0; i < numComponents; i++)
562            result[normOffset + i] = (float) normalScalers[i].scale(components[offset + i]);
563
564        return result;
565    }
566
567    /**
568     * @see java.awt.image.ColorModel#getUnnormalizedComponents(float[], int, int[], int)
569     */
570    @Override
571    public int[] getUnnormalizedComponents(float[] normComponents, int normOffset, int[] components, int offset)
572    {
573        if ((normComponents.length - normOffset) < numComponents)
574            throw new IllegalArgumentException("Incorrect number of components.  Expecting " + numComponents);
575
576        final int[] result = Array1DUtil.allocIfNull(components, numComponents + offset);
577
578        for (int i = 0; i < numComponents; i++)
579            result[offset + i] = (int) normalScalers[i].unscale(normComponents[normOffset + i]);
580
581        return result;
582    }
583
584    /**
585     * @see java.awt.image.ColorModel#getDataElement(int[], int)
586     */
587    @Override
588    public int getDataElement(int[] components, int offset)
589    {
590        throw new IllegalArgumentException("Not supported in this ColorModel");
591    }
592
593    /**
594     * @see java.awt.image.ColorModel#getDataElement(float[], int)
595     */
596    @Override
597    public int getDataElement(float[] normComponents, int normOffset)
598    {
599        throw new IllegalArgumentException("Not supported in this ColorModel");
600    }
601
602    /**
603     * @see java.awt.image.ColorModel#getDataElements(int[], int, Object)
604     */
605    @Override
606    public abstract Object getDataElements(int[] components, int offset, Object obj);
607
608    /**
609     * @see java.awt.image.ColorModel#getDataElements(int, Object)
610     */
611    @Override
612    public Object getDataElements(int rgb, Object pixel)
613    {
614        return getDataElements(getIcyColorSpace().fromRGB(rgb), 0, pixel);
615    }
616
617    /**
618     * @see java.awt.image.ColorModel#getDataElements(float[], int, Object)
619     */
620    @Override
621    public abstract Object getDataElements(float[] normComponents, int normOffset, Object obj);
622
623    /**
624     * 
625     */
626    @Override
627    public ColorModel coerceData(WritableRaster raster, boolean isAlphaPremultiplied)
628    {
629        // nothing to do
630        return this;
631    }
632
633    /**
634     * Scale input value for colormap indexing
635     */
636    protected double colormapScale(int component, double value)
637    {
638        return colormapScalers[component].scale(value);
639    }
640
641    /**
642     * Tests if the specified <code>Object</code> is an instance of <code>ColorModel</code> and if
643     * it equals this <code>ColorModel</code>.
644     * 
645     * @param obj
646     *        the <code>Object</code> to test for equality
647     * @return <code>true</code> if the specified <code>Object</code> is an instance of <code>ColorModel</code> and
648     *         equals this <code>ColorModel</code>; <code>false</code> otherwise.
649     */
650    @Override
651    public boolean equals(Object obj)
652    {
653        if (obj instanceof IcyColorModel)
654            return isCompatible((IcyColorModel) obj);
655
656        return false;
657    }
658
659    /**
660     * 
661     */
662    public boolean isCompatible(IcyColorModel cm)
663    {
664        return (getNumComponents() == cm.getNumComponents()) && (getDataType_() == cm.getDataType_());
665    }
666
667    /**
668     * 
669     */
670    @Override
671    public boolean isCompatibleRaster(Raster raster)
672    {
673        final SampleModel sm = raster.getSampleModel();
674        final int[] bits = getComponentSize();
675
676        if (sm instanceof ComponentSampleModel)
677        {
678            if (sm.getNumBands() != numComponents)
679                return false;
680
681            for (int i = 0; i < bits.length; i++)
682                if (sm.getSampleSize(i) < bits[i])
683                    return false;
684
685            return (raster.getTransferType() == transferType);
686        }
687
688        return false;
689    }
690
691    /**
692     * 
693     */
694    @Override
695    public boolean isCompatibleSampleModel(SampleModel sm)
696    {
697        // Must have the same number of components
698        if (numComponents != sm.getNumBands())
699            return false;
700        if (sm.getTransferType() != transferType)
701            return false;
702
703        return true;
704    }
705
706    /**
707     * @return the IcyColorSpace
708     */
709    public IcyColorSpace getIcyColorSpace()
710    {
711        return (IcyColorSpace) getColorSpace();
712    }
713
714    /**
715     * Change the colorspace of the color model.<br/>
716     * <b>You should never use this method directly (internal use only)</b>
717     */
718    public void setColorSpace(IcyColorSpace colorSpace)
719    {
720        final IcyColorSpace cs = getIcyColorSpace();
721
722        if (cs != colorSpace)
723        {
724            try
725            {
726                final Field csField = ReflectionUtil.getField(ColorModel.class, "colorSpace", true);
727                // set new colorSpace value
728                csField.set(this, colorSpace);
729
730                cs.removeListener(this);
731                colorSpace.addListener(this);
732            }
733            catch (Exception e)
734            {
735                System.err.println("Warning: Couldn't change colorspace of IcyColorModel...");
736            }
737        }
738
739        // do not notify about the change here as this method is only called internally
740    }
741
742    /**
743     * @return the normalScalers
744     */
745    public Scaler[] getNormalScalers()
746    {
747        return normalScalers;
748    }
749
750    /**
751     * @return the colormapScalers
752     */
753    public Scaler[] getColormapScalers()
754    {
755        return colormapScalers;
756    }
757
758    /**
759     * Returns the number of components in this <code>ColorModel</code>.<br>
760     * Note that alpha is embedded so we always have NumColorComponent = NumComponent
761     * 
762     * @return the number of components in this <code>ColorModel</code>
763     */
764    @Override
765    public int getNumComponents()
766    {
767        return numComponents;
768    }
769
770    /**
771     * @deprecated use {@link #getDataType_()} instead
772     */
773    @Deprecated
774    public int getDataType()
775    {
776        return TypeUtil.dataBufferTypeToDataType(transferType);
777    }
778
779    /**
780     * Return data type for this colormodel
781     * 
782     * @see DataType
783     */
784    public DataType getDataType_()
785    {
786        return dataType;
787    }
788
789    /**
790     * return default component bounds for this colormodel
791     */
792    public double[] getDefaultComponentBounds()
793    {
794        return dataType.getDefaultBounds();
795    }
796
797    /**
798     * Get component absolute minimum value
799     */
800    public double getComponentAbsMinValue(int component)
801    {
802        // use the normal scaler
803        return normalScalers[component].getAbsLeftIn();
804    }
805
806    /**
807     * Get component absolute maximum value
808     */
809    public double getComponentAbsMaxValue(int component)
810    {
811        // use the normal scaler
812        return normalScalers[component].getAbsRightIn();
813    }
814
815    /**
816     * Get component absolute bounds (min and max values)
817     */
818    public double[] getComponentAbsBounds(int component)
819    {
820        final double[] result = new double[2];
821
822        result[0] = getComponentAbsMinValue(component);
823        result[1] = getComponentAbsMaxValue(component);
824
825        return result;
826    }
827
828    /**
829     * Get component user minimum value
830     */
831    public double getComponentUserMinValue(int component)
832    {
833        // use the normal scaler
834        return normalScalers[component].getLeftIn();
835    }
836
837    /**
838     * Get user component user maximum value
839     */
840    public double getComponentUserMaxValue(int component)
841    {
842        // use the normal scaler
843        return normalScalers[component].getRightIn();
844    }
845
846    /**
847     * Get component user bounds (min and max values)
848     */
849    public double[] getComponentUserBounds(int component)
850    {
851        final double[] result = new double[2];
852
853        result[0] = getComponentUserMinValue(component);
854        result[1] = getComponentUserMaxValue(component);
855
856        return result;
857    }
858
859    /**
860     * Set component absolute minimum value
861     */
862    public void setComponentAbsMinValue(int component, double min)
863    {
864        // update both scalers
865        normalScalers[component].setAbsLeftIn(min);
866        colormapScalers[component].setAbsLeftIn(min);
867    }
868
869    /**
870     * Set component absolute maximum value
871     */
872    public void setComponentAbsMaxValue(int component, double max)
873    {
874        // update both scalers
875        normalScalers[component].setAbsRightIn(max);
876        colormapScalers[component].setAbsRightIn(max);
877    }
878
879    /**
880     * Set component absolute bounds (min and max values)
881     */
882    public void setComponentAbsBounds(int component, double[] bounds)
883    {
884        setComponentAbsBounds(component, bounds[0], bounds[1]);
885    }
886
887    /**
888     * Set component absolute bounds (min and max values)
889     */
890    public void setComponentAbsBounds(int component, double min, double max)
891    {
892        // update both scalers
893        normalScalers[component].setAbsLeftRightIn(min, max);
894        colormapScalers[component].setAbsLeftRightIn(min, max);
895    }
896
897    /**
898     * Set component user minimum value
899     */
900    public void setComponentUserMinValue(int component, double min)
901    {
902        // update both scalers
903        normalScalers[component].setLeftIn(min);
904        colormapScalers[component].setLeftIn(min);
905    }
906
907    /**
908     * Set component user maximum value
909     */
910    public void setComponentUserMaxValue(int component, double max)
911    {
912        // update both scalers
913        normalScalers[component].setRightIn(max);
914        colormapScalers[component].setRightIn(max);
915    }
916
917    /**
918     * Set component user bounds (min and max values)
919     */
920    public void setComponentUserBounds(int component, double[] bounds)
921    {
922        setComponentUserBounds(component, bounds[0], bounds[1]);
923    }
924
925    /**
926     * Set component user bounds (min and max values)
927     */
928    public void setComponentUserBounds(int component, double min, double max)
929    {
930        // update both scalers
931        normalScalers[component].setLeftRightIn(min, max);
932        colormapScalers[component].setLeftRightIn(min, max);
933    }
934
935    /**
936     * Set components absolute bounds (min and max values)
937     */
938    public void setComponentsAbsBounds(double[][] bounds)
939    {
940        final int numComponents = getNumComponents();
941
942        if (bounds.length != numComponents)
943            throw new IllegalArgumentException("bounds.length != ColorModel.numComponents");
944
945        for (int component = 0; component < numComponents; component++)
946            setComponentAbsBounds(component, bounds[component]);
947    }
948
949    /**
950     * Set components user bounds (min and max values)
951     */
952    public void setComponentsUserBounds(double[][] bounds)
953    {
954        final int numComponents = getNumComponents();
955
956        if (bounds.length != numComponents)
957            throw new IllegalArgumentException("bounds.length != ColorModel.numComponents");
958
959        for (int component = 0; component < numComponents; component++)
960            setComponentUserBounds(component, bounds[component]);
961    }
962
963    /**
964     * Return true if colorModel is float data type
965     */
966    public boolean isFloatDataType()
967    {
968        return dataType.isFloat();
969    }
970
971    /**
972     * Return true if colorModel data type is signed
973     */
974    public boolean isSignedDataType()
975    {
976        return dataType.isSigned();
977    }
978
979    /**
980     * Return true if color maps associated to this {@link IcyColorModel} are all linear map.
981     * 
982     * @see IcyColorMap#isLinear()
983     */
984    public boolean hasLinearColormaps()
985    {
986        for (int c = 0; c < numComponents; c++)
987            if (!getColorMap(c).isLinear())
988                return false;
989
990        return true;
991    }
992
993    /**
994     * Returns the <code>String</code> representation of the contents of this <code>ColorModel</code>object.
995     * 
996     * @return a <code>String</code> representing the contents of this <code>ColorModel</code> object.
997     */
998    @Override
999    public String toString()
1000    {
1001        return new String("ColorModel: dataType = " + dataType + " numComponents = " + numComponents + " color space = "
1002                + getColorSpace());
1003    }
1004
1005    /**
1006     * Add a listener
1007     * 
1008     * @param listener
1009     */
1010    public void addListener(IcyColorModelListener listener)
1011    {
1012        listeners.add(listener);
1013    }
1014
1015    /**
1016     * Remove a listener
1017     * 
1018     * @param listener
1019     */
1020    public void removeListener(IcyColorModelListener listener)
1021    {
1022        listeners.remove(listener);
1023    }
1024
1025    /**
1026     * fire event
1027     * 
1028     * @param e
1029     */
1030    public void fireEvent(IcyColorModelEvent e)
1031    {
1032        for (IcyColorModelListener listener : new ArrayList<IcyColorModelListener>(listeners))
1033            listener.colorModelChanged(e);
1034    }
1035
1036    /**
1037     * process on colormodel change
1038     */
1039    @Override
1040    public void onChanged(CollapsibleEvent compare)
1041    {
1042        final IcyColorModelEvent event = (IcyColorModelEvent) compare;
1043
1044        // notify listener we have changed
1045        fireEvent(event);
1046    }
1047
1048    @Override
1049    public void scalerChanged(ScalerEvent e)
1050    {
1051        // only listening colormapScalers
1052        final int ind = Scaler.indexOf(colormapScalers, e.getScaler());
1053
1054        // handle changed via updater object
1055        if (ind != -1)
1056            updater.changed(new IcyColorModelEvent(this, IcyColorModelEventType.SCALER_CHANGED, ind));
1057    }
1058
1059    @Override
1060    public void colorSpaceChanged(IcyColorSpaceEvent e)
1061    {
1062        // handle changed via updater object
1063        updater.changed(new IcyColorModelEvent(this, IcyColorModelEventType.COLORMAP_CHANGED, e.getComponent()));
1064    }
1065
1066    public void beginUpdate()
1067    {
1068        updater.beginUpdate();
1069    }
1070
1071    public void endUpdate()
1072    {
1073        updater.endUpdate();
1074    }
1075
1076    public boolean isUpdating()
1077    {
1078        return updater.isUpdating();
1079    }
1080}