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.lut;
020
021import icy.common.CollapsibleEvent;
022import icy.common.UpdateEventHandler;
023import icy.common.listener.ChangeListener;
024import icy.file.xml.XMLPersistent;
025import icy.image.colormap.IcyColorMap;
026import icy.image.colormodel.IcyColorModel;
027import icy.image.colorspace.IcyColorSpace;
028import icy.image.colorspace.IcyColorSpaceEvent;
029import icy.image.colorspace.IcyColorSpaceListener;
030import icy.image.lut.LUT.LUTChannelEvent.LUTChannelEventType;
031import icy.image.lut.LUTEvent.LUTEventType;
032import icy.math.Scaler;
033import icy.math.ScalerEvent;
034import icy.math.ScalerListener;
035import icy.type.DataType;
036import icy.util.XMLUtil;
037
038import java.util.ArrayList;
039import java.util.EventListener;
040import java.util.List;
041
042import org.w3c.dom.Node;
043
044public class LUT implements IcyColorSpaceListener, ScalerListener, ChangeListener, XMLPersistent
045{
046    private final static String ID_NUM_CHANNEL = "numChannel";
047    private final static String ID_SCALER = "scaler";
048    private final static String ID_COLORMAP = "colormap";
049
050    public static interface LUTChannelListener extends EventListener
051    {
052        public void lutChannelChanged(LUTChannelEvent e);
053    }
054
055    public static class LUTChannelEvent
056    {
057        public enum LUTChannelEventType
058        {
059            SCALER_CHANGED, COLORMAP_CHANGED
060        }
061
062        private final LUTChannel lutChannel;
063        private final LUTChannelEventType type;
064
065        public LUTChannelEvent(LUTChannel lutChannel, LUTChannelEventType type)
066        {
067            super();
068
069            this.lutChannel = lutChannel;
070            this.type = type;
071        }
072
073        /**
074         * @return the lutChannel
075         */
076        public LUTChannel getLutChannel()
077        {
078            return lutChannel;
079        }
080
081        /**
082         * @return the type
083         */
084        public LUTChannelEventType getType()
085        {
086            return type;
087        }
088    }
089
090    public class LUTChannel
091    {
092        /**
093         * band index
094         */
095        private final int channel;
096
097        /**
098         * listeners
099         */
100        private final List<LUTChannelListener> channelListeners;
101
102        public LUTChannel(int channel)
103        {
104            this.channel = channel;
105
106            channelListeners = new ArrayList<LUT.LUTChannelListener>();
107        }
108
109        public LUT getLut()
110        {
111            return LUT.this;
112        }
113
114        /**
115         * Copy the colormap and scaler data from the specified source {@link LUTChannel}
116         */
117        public void copyFrom(LUTChannel source)
118        {
119            setColorMap(source.getColorMap(), true);
120            setScaler(source.getScaler());
121        }
122
123        public Scaler getScaler()
124        {
125            return getScalers()[channel];
126        }
127
128        /**
129         * Set the specified scaler (do a copy).
130         * 
131         * @param source
132         *        source scaler to copy from
133         */
134        public void setScaler(Scaler source)
135        {
136            final Scaler scaler = getScaler();
137
138            scaler.beginUpdate();
139            try
140            {
141                scaler.setAbsLeftRightIn(source.getAbsLeftIn(), source.getAbsRightIn());
142                scaler.setLeftRightIn(source.getLeftIn(), source.getRightIn());
143                scaler.setLeftRightOut(source.getLeftOut(), source.getRightOut());
144            }
145            finally
146            {
147                scaler.endUpdate();
148            }
149        }
150
151        public IcyColorMap getColorMap()
152        {
153            return getColorSpace().getColorMap(channel);
154        }
155
156        /**
157         * Set the specified colormap (do a copy).
158         * 
159         * @param colorMap
160         *        source colorspace to copy
161         * @param setAlpha
162         *        also set the alpha information
163         */
164        public void setColorMap(IcyColorMap colorMap, boolean setAlpha)
165        {
166            getColorSpace().setColorMap(channel, colorMap, setAlpha);
167        }
168
169        /**
170         * @deprecated Use {@link #setColorMap(IcyColorMap, boolean)} instead.
171         */
172        @Deprecated
173        public void setColorMap(IcyColorMap colorMap)
174        {
175            setColorMap(colorMap, true);
176        }
177
178        /**
179         * @deprecated Use {@link #setColorMap(IcyColorMap, boolean)} instead.
180         */
181        @Deprecated
182        public void copyColorMap(IcyColorMap colorMap)
183        {
184            setColorMap(colorMap, true);
185        }
186
187        public double getMin()
188        {
189            return getScaler().getLeftIn();
190        }
191
192        public void setMin(double value)
193        {
194            getScaler().setLeftIn(value);
195        }
196
197        public double getMax()
198        {
199            return getScaler().getRightIn();
200        }
201
202        public void setMax(double value)
203        {
204            getScaler().setRightIn(value);
205        }
206
207        public void setMinMax(double min, double max)
208        {
209            getScaler().setLeftRightIn(min, max);
210        }
211
212        public double getMinBound()
213        {
214            return getScaler().getAbsLeftIn();
215        }
216
217        public double getMaxBound()
218        {
219            return getScaler().getAbsRightIn();
220        }
221
222        public void setMinBound(double value)
223        {
224            getScaler().setAbsLeftIn(value);
225        }
226
227        public void setMaxBound(double value)
228        {
229            getScaler().setAbsRightIn(value);
230        }
231
232        /**
233         * Returns the <i>enabled</i> state of this channel LUT
234         */
235        public boolean isEnabled()
236        {
237            return getColorMap().isEnabled();
238        }
239
240        /**
241         * Enable/disable specified channel LUT
242         */
243        public void setEnabled(boolean value)
244        {
245            getColorMap().setEnabled(value);
246        }
247
248        /**
249         * @deprecated Use {@link #getChannel()} instead.
250         */
251        @Deprecated
252        public int getComponent()
253        {
254            return getChannel();
255        }
256
257        /**
258         * @return the component
259         */
260        public int getChannel()
261        {
262            return channel;
263        }
264
265        /**
266         * Add a listener.
267         */
268        public void addListener(LUTChannelListener listener)
269        {
270            channelListeners.add(listener);
271        }
272
273        /**
274         * Remove a listener.
275         */
276        public void removeListener(LUTChannelListener listener)
277        {
278            channelListeners.remove(listener);
279        }
280
281        /**
282         * Fire change event.
283         */
284        public void fireEvent(LUTChannelEvent e)
285        {
286            for (LUTChannelListener listener : new ArrayList<LUTChannelListener>(channelListeners))
287                listener.lutChannelChanged(e);
288        }
289    }
290
291    private List<LUTChannel> lutChannels = new ArrayList<LUTChannel>();
292
293    final IcyColorSpace colorSpace;
294    final Scaler[] scalers;
295    final int numChannel;
296
297    private boolean enabled = true;
298
299    /**
300     * listeners
301     */
302    private final List<LUTListener> listeners;
303
304    /**
305     * internal updater
306     */
307    private final UpdateEventHandler updater;
308
309    public LUT(IcyColorModel cm)
310    {
311        colorSpace = cm.getIcyColorSpace();
312        scalers = cm.getColormapScalers();
313        numChannel = colorSpace.getNumComponents();
314
315        if (scalers.length != numChannel)
316        {
317            throw new IllegalArgumentException(
318                    "Incorrect size for scalers : " + scalers.length + ".  Expected : " + numChannel);
319        }
320
321        final DataType dataType = cm.getDataType_();
322
323        for (int channel = 0; channel < numChannel; channel++)
324        {
325            // BYTE data type --> fix bounds to data type bounds
326            if (dataType == DataType.UBYTE)
327                scalers[channel].setLeftRightIn(dataType.getMinValue(), dataType.getMaxValue());
328
329            lutChannels.add(new LUTChannel(channel));
330        }
331
332        listeners = new ArrayList<LUTListener>();
333        updater = new UpdateEventHandler(this, false);
334
335        // add listener
336        for (Scaler scaler : scalers)
337            scaler.addListener(this);
338        colorSpace.addListener(this);
339
340    }
341
342    protected int indexOf(Scaler scaler)
343    {
344        for (int i = 0; i < scalers.length; i++)
345            if (scalers[i] == scaler)
346                return i;
347
348        return -1;
349    }
350
351    public IcyColorSpace getColorSpace()
352    {
353        return colorSpace;
354    }
355
356    public Scaler[] getScalers()
357    {
358        return scalers;
359    }
360
361    public boolean isEnabled()
362    {
363        return enabled;
364    }
365
366    public void setEnabled(boolean enabled)
367    {
368        this.enabled = enabled;
369    }
370
371    public ArrayList<LUTChannel> getLutChannels()
372    {
373        return new ArrayList<LUTChannel>(lutChannels);
374    }
375
376    /**
377     * Return the {@link LUTChannel} for specified channel index.
378     */
379    public LUTChannel getLutChannel(int channel)
380    {
381        return lutChannels.get(channel);
382    }
383
384    /**
385     * @deprecated Use {@link #getLutChannels()} instead.
386     */
387    @Deprecated
388    public ArrayList<LUTBand> getLutBands()
389    {
390        final ArrayList<LUTBand> result = new ArrayList<LUTBand>();
391
392        for (LUTChannel lutChannel : lutChannels)
393            result.add(new LUTBand(this, lutChannel.getChannel()));
394
395        return result;
396    }
397
398    /**
399     * @deprecated Use {@link #getLutChannel(int)} instead.
400     */
401    @Deprecated
402    public LUTBand getLutBand(int band)
403    {
404        return getLutBands().get(band);
405    }
406
407    /**
408     * @return the number of channel.
409     */
410    public int getNumChannel()
411    {
412        return numChannel;
413    }
414
415    /**
416     * @deprecated Use {@link #getNumChannel()} instead.
417     */
418    @Deprecated
419    public int getNumComponents()
420    {
421        return getNumChannel();
422    }
423
424    /**
425     * Copy LUT from the specified source lut
426     */
427    public void copyFrom(LUT lut)
428    {
429        beginUpdate();
430        try
431        {
432            setColorMaps(lut, true);
433            setScalers(lut);
434        }
435        finally
436        {
437            endUpdate();
438        }
439    }
440
441    /**
442     * Set the scalers from the specified source lut (do a copy)
443     */
444    public void setScalers(LUT lut)
445    {
446        final Scaler[] srcScalers = lut.getScalers();
447        final int len = Math.min(scalers.length, srcScalers.length);
448
449        beginUpdate();
450        try
451        {
452            for (int i = 0; i < len; i++)
453            {
454                final Scaler src = srcScalers[i];
455                final Scaler dst = scalers[i];
456
457                dst.setAbsLeftRightIn(src.getAbsLeftIn(), src.getAbsRightIn());
458                dst.setLeftRightIn(src.getLeftIn(), src.getRightIn());
459                dst.setLeftRightOut(src.getLeftOut(), src.getRightOut());
460            }
461        }
462        finally
463        {
464            endUpdate();
465        }
466    }
467
468    /**
469     * @deprecated Use {@link #setScalers(LUT)} instead.
470     */
471    @Deprecated
472    public void copyScalers(LUT lut)
473    {
474        setScalers(lut);
475    }
476
477    /**
478     * Set colormaps from the specified source lut (do a copy).
479     * 
480     * @param lut
481     *        source lut to use
482     * @param setAlpha
483     *        also set the alpha information
484     */
485    public void setColorMaps(LUT lut, boolean setAlpha)
486    {
487        getColorSpace().setColorMaps(lut.getColorSpace(), setAlpha);
488    }
489
490    /**
491     * @deprecated USe {@link #setColorMaps(LUT, boolean)} instead.
492     */
493    @Deprecated
494    public void setColormaps(LUT lut)
495    {
496        setColorMaps(lut, true);
497    }
498
499    /**
500     * @deprecated Use {@link #setColorMaps(LUT, boolean)} instead.
501     */
502    @Deprecated
503    public void copyColormaps(LUT lut)
504    {
505        setColorMaps(lut, true);
506    }
507
508    /**
509     * Set the alpha channel to full opaque for all LUT channel
510     */
511    public void setAlphaToOpaque()
512    {
513        beginUpdate();
514        try
515        {
516            for (LUTChannel lutChannel : getLutChannels())
517                if (!lutChannel.getColorMap().isAlpha())
518                    lutChannel.getColorMap().setAlphaToOpaque();
519        }
520        finally
521        {
522            endUpdate();
523        }
524    }
525
526    /**
527     * Set the alpha channel to linear opacity (0 to 1) for all LUT channel
528     */
529    public void setAlphaToLinear()
530    {
531        beginUpdate();
532        try
533        {
534            for (LUTChannel lutChannel : getLutChannels())
535                lutChannel.getColorMap().setAlphaToLinear();
536        }
537        finally
538        {
539            endUpdate();
540        }
541    }
542
543    /**
544     * Set the alpha channel to an optimized linear transparency for 3D volume display on all LUT
545     * channel
546     */
547    public void setAlphaToLinear3D()
548    {
549        beginUpdate();
550        try
551        {
552            for (LUTChannel lutChannel : getLutChannels())
553                lutChannel.getColorMap().setAlphaToLinear3D();
554        }
555        finally
556        {
557            endUpdate();
558        }
559    }
560
561    /**
562     * Return true if LUT is compatible with specified ColorModel.<br>
563     * (Same number of channels with same data type)
564     */
565    public boolean isCompatible(LUT lut)
566    {
567        if (numChannel != lut.getNumChannel())
568            return false;
569
570        final Scaler[] cmScalers = lut.getScalers();
571
572        // check that data type is compatible
573        for (int channel = 0; channel < numChannel; channel++)
574            if (scalers[channel].isIntegerData() != cmScalers[channel].isIntegerData())
575                return false;
576
577        return true;
578    }
579
580    /**
581     * Return true if LUT is compatible with specified ColorModel.<br>
582     * (Same number of channels with same data type)
583     */
584    public boolean isCompatible(IcyColorModel colorModel)
585    {
586        if (numChannel != colorModel.getNumComponents())
587            return false;
588
589        final Scaler[] cmScalers = colorModel.getColormapScalers();
590
591        // check that data type is compatible
592        for (int comp = 0; comp < numChannel; comp++)
593            if (scalers[comp].isIntegerData() != cmScalers[comp].isIntegerData())
594                return false;
595
596        return true;
597    }
598
599    /**
600     * Add a listener
601     * 
602     * @param listener
603     */
604    public void addListener(LUTListener listener)
605    {
606        listeners.add(listener);
607    }
608
609    /**
610     * Remove a listener
611     * 
612     * @param listener
613     */
614    public void removeListener(LUTListener listener)
615    {
616        listeners.remove(listener);
617    }
618
619    public void fireLUTChanged(LUTEvent e)
620    {
621        for (LUTListener lutListener : new ArrayList<LUTListener>(listeners))
622            lutListener.lutChanged(e);
623    }
624
625    @Override
626    public void onChanged(CollapsibleEvent compare)
627    {
628        final LUTEvent event = (LUTEvent) compare;
629
630        // notify listener we have changed
631        fireLUTChanged(event);
632
633        // propagate event to LUTChannel
634        final int channel = event.getComponent();
635        final LUTChannelEventType type = (event.getType() == LUTEventType.COLORMAP_CHANGED)
636                ? LUTChannelEventType.COLORMAP_CHANGED
637                : LUTChannelEventType.SCALER_CHANGED;
638
639        if (channel == -1)
640        {
641            for (LUTChannel lutChannel : lutChannels)
642                lutChannel.fireEvent(new LUTChannelEvent(lutChannel, type));
643        }
644        else
645        {
646            final LUTChannel lutChannel = getLutChannel(channel);
647            lutChannel.fireEvent(new LUTChannelEvent(lutChannel, type));
648        }
649    }
650
651    @Override
652    public void colorSpaceChanged(IcyColorSpaceEvent e)
653    {
654        // notify LUT colormap changed
655        updater.changed(new LUTEvent(this, e.getComponent(), LUTEventType.COLORMAP_CHANGED));
656    }
657
658    @Override
659    public void scalerChanged(ScalerEvent e)
660    {
661        // notify LUTBand changed
662        updater.changed(new LUTEvent(this, indexOf(e.getScaler()), LUTEventType.SCALER_CHANGED));
663    }
664
665    public void beginUpdate()
666    {
667        updater.beginUpdate();
668    }
669
670    public void endUpdate()
671    {
672        updater.endUpdate();
673    }
674
675    public boolean isUpdating()
676    {
677        return updater.isUpdating();
678    }
679
680    @Override
681    public boolean loadFromXML(Node node)
682    {
683        if (node == null)
684            return false;
685
686        // different channel number --> exit
687        if (numChannel != XMLUtil.getElementIntValue(node, ID_NUM_CHANNEL, 1))
688            return false;
689
690        beginUpdate();
691        try
692        {
693            for (int ch = 0; ch < numChannel; ch++)
694            {
695                Node n;
696
697                n = XMLUtil.getElement(node, ID_SCALER + ch);
698                if (n != null)
699                    scalers[ch].loadFromXML(n);
700                n = XMLUtil.getElement(node, ID_COLORMAP + ch);
701                if (n != null)
702                    colorSpace.getColorMap(ch).loadFromXML(n);
703            }
704        }
705        finally
706        {
707            endUpdate();
708        }
709
710        return true;
711    }
712
713    @Override
714    public boolean saveToXML(Node node)
715    {
716        if (node == null)
717            return false;
718
719        XMLUtil.setElementIntValue(node, ID_NUM_CHANNEL, numChannel);
720
721        for (int ch = 0; ch < numChannel; ch++)
722        {
723            Node n;
724
725            n = XMLUtil.setElement(node, ID_SCALER + ch);
726            if (n != null)
727                scalers[ch].saveToXML(n);
728            n = XMLUtil.setElement(node, ID_COLORMAP + ch);
729            if (n != null)
730                colorSpace.getColorMap(ch).saveToXML(n);
731        }
732
733        return true;
734    }
735}