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;
020
021import java.awt.Dimension;
022import java.awt.Graphics;
023import java.awt.Point;
024import java.awt.Rectangle;
025import java.awt.image.BufferedImage;
026import java.awt.image.ComponentSampleModel;
027import java.awt.image.DataBuffer;
028import java.awt.image.DataBufferByte;
029import java.awt.image.DataBufferDouble;
030import java.awt.image.DataBufferFloat;
031import java.awt.image.DataBufferInt;
032import java.awt.image.DataBufferShort;
033import java.awt.image.DataBufferUShort;
034import java.awt.image.ImageObserver;
035import java.awt.image.Raster;
036import java.awt.image.SampleModel;
037import java.awt.image.WritableRaster;
038import java.io.IOException;
039import java.lang.ref.WeakReference;
040import java.lang.reflect.Array;
041import java.lang.reflect.Field;
042import java.nio.channels.ClosedByInterruptException;
043import java.util.ArrayList;
044import java.util.HashMap;
045import java.util.List;
046import java.util.Map;
047import java.util.concurrent.BlockingQueue;
048import java.util.concurrent.Callable;
049import java.util.concurrent.ExecutionException;
050import java.util.concurrent.FutureTask;
051import java.util.concurrent.LinkedBlockingQueue;
052import java.util.concurrent.ThreadPoolExecutor;
053import java.util.concurrent.TimeUnit;
054
055import javax.media.jai.PlanarImage;
056
057import icy.common.CollapsibleEvent;
058import icy.common.UpdateEventHandler;
059import icy.common.exception.TooLargeArrayException;
060import icy.common.exception.UnsupportedFormatException;
061import icy.common.listener.ChangeListener;
062import icy.image.IcyBufferedImageEvent.IcyBufferedImageEventType;
063import icy.image.cache.ImageCache;
064import icy.image.colormap.IcyColorMap;
065import icy.image.colormap.LinearColorMap;
066import icy.image.colormodel.IcyColorModel;
067import icy.image.colormodel.IcyColorModelEvent;
068import icy.image.colormodel.IcyColorModelListener;
069import icy.image.lut.LUT;
070import icy.math.ArrayMath;
071import icy.math.MathUtil;
072import icy.math.Scaler;
073import icy.preferences.GeneralPreferences;
074import icy.sequence.Sequence;
075import icy.sequence.SequenceIdImporter;
076import icy.system.SystemUtil;
077import icy.type.DataType;
078import icy.type.TypeUtil;
079import icy.type.collection.array.Array1DUtil;
080import icy.type.collection.array.Array2DUtil;
081import icy.type.collection.array.ArrayUtil;
082import icy.type.collection.array.ByteArrayConvert;
083import icy.util.ReflectionUtil;
084import icy.util.StringUtil;
085import loci.formats.FormatException;
086import loci.formats.IFormatReader;
087import loci.formats.gui.SignedByteBuffer;
088import loci.formats.gui.SignedShortBuffer;
089import loci.formats.gui.UnsignedIntBuffer;
090import plugins.kernel.importer.LociImporterPlugin;
091
092/**
093 * @author stephane
094 */
095public class IcyBufferedImage extends BufferedImage implements IcyColorModelListener, ChangeListener
096{
097    static class WeakIcyBufferedImageReference extends WeakReference<IcyBufferedImage>
098    {
099        final int hc;
100
101        WeakIcyBufferedImageReference(IcyBufferedImage image)
102        {
103            super(image);
104
105            hc = System.identityHashCode(image);
106        }
107
108        @Override
109        public int hashCode()
110        {
111            return hc;
112        }
113
114        @Override
115        public boolean equals(Object obj)
116        {
117            if (obj instanceof WeakIcyBufferedImageReference)
118                return obj.hashCode() == hashCode();
119
120            return super.equals(obj);
121        }
122    }
123
124    public static class ImageSourceInfo
125    {
126        // importer
127        public final SequenceIdImporter imp;
128        // series index
129        public final int series;
130        // resolution
131        public final int resolution;
132        // region
133        public final Rectangle region;
134        // T, Z, C position
135        public final int t;
136        public final int z;
137        public final int c;
138
139        public ImageSourceInfo(SequenceIdImporter imp, int series, int resolution, Rectangle region, int t, int z,
140                int c)
141        {
142            super();
143
144            this.imp = imp;
145            this.series = series;
146            this.resolution = resolution;
147            this.region = region;
148            this.t = t;
149            this.z = z;
150            this.c = c;
151        }
152
153        @Override
154        public String toString()
155        {
156            return imp.toString() + " s=" + series + " r=" + resolution + " t=" + t + " z=" + z + " c=" + c;
157        }
158    }
159
160    private static class ImageDataLoaderWorker implements Callable<Object>
161    {
162        final WeakReference<IcyBufferedImage> imageRef;
163
164        ImageDataLoaderWorker(IcyBufferedImage image)
165        {
166            super();
167
168            this.imageRef = new WeakReference<IcyBufferedImage>(image);
169        }
170
171        @Override
172        public Object call() throws Exception
173        {
174            final IcyBufferedImage image = imageRef.get();
175
176            // image has been released, we probably don't need its data anymore...
177            if (image == null)
178                return null;
179
180            // not null here
181            final ImageSourceInfo imageSourceInfo = image.imageSourceInfo;
182            final SequenceIdImporter imp = imageSourceInfo.imp;
183
184            // importer not opened ? --> cannot load
185            if (StringUtil.isEmpty(imp.getOpened()))
186                throw new IOException("Cannot load image data: Sequence importer is closed.");
187
188            final int sizeC = image.getSizeC();
189            // create the result array (always 2D native type)
190            final Object[] result = Array2DUtil.createArray(image.getDataType_(), sizeC);
191
192            // all channels ?
193            if ((imageSourceInfo.c == -1) && (sizeC > 1))
194            {
195                // better to directly load image
196                final IcyBufferedImage newImage = imp.getImage(imageSourceInfo.series, imageSourceInfo.resolution,
197                        imageSourceInfo.region, imageSourceInfo.z, imageSourceInfo.t);
198                // then get data
199                for (int c = 0; c < sizeC; c++)
200                    result[c] = newImage.getDataXY(c);
201            }
202            else
203            {
204                // all channel for single channel image --> channel 0
205                final int startC = (imageSourceInfo.c == -1) ? 0 : imageSourceInfo.c;
206                // directly load pixel data
207                for (int c = 0; c < sizeC; c++)
208                    result[c] = imp.getPixels(imageSourceInfo.series, imageSourceInfo.resolution,
209                            imageSourceInfo.region, imageSourceInfo.z, imageSourceInfo.t, startC + c);
210            }
211
212            return result;
213        }
214
215        IcyBufferedImage getImage()
216        {
217            return imageRef.get();
218        }
219    }
220
221    private static class ImageDataLoaderTask extends FutureTask<Object>
222    {
223        final ImageDataLoaderWorker worker;
224
225        ImageDataLoaderTask(ImageDataLoaderWorker worker)
226        {
227            super(worker);
228
229            this.worker = worker;
230        }
231
232        IcyBufferedImage getImage()
233        {
234            return worker.getImage();
235        }
236    }
237
238    private static class ImageDataLoader
239    {
240        final ThreadPoolExecutor executor;
241
242        public ImageDataLoader()
243        {
244            super();
245
246            int numWorker = SystemUtil.getNumberOfCPUs();
247            executor = new ThreadPoolExecutor(numWorker, numWorker * 2, 5L, TimeUnit.SECONDS,
248                    new LinkedBlockingQueue<Runnable>());
249        }
250
251        Object loadImageData(IcyBufferedImage image) throws ExecutionException, InterruptedException
252        {
253            final ImageDataLoaderTask task = new ImageDataLoaderTask(new ImageDataLoaderWorker(image));
254
255            executor.execute(task);
256
257            try
258            {
259                return task.get();
260            }
261            catch (InterruptedException e)
262            {
263                // process interrupted ? remove task from executor queue if possible
264                executor.remove(task);
265                // cancel the task (without interrupting current running task as this close the importer)
266                task.cancel(false);
267
268                // re throw interrupt
269                throw e;
270            }
271        }
272
273        void cancelTasks(IcyBufferedImage image)
274        {
275            final List<ImageDataLoaderTask> tasks = new ArrayList<ImageDataLoaderTask>();
276            final BlockingQueue<Runnable> queue = executor.getQueue();
277
278            synchronized (queue)
279            {
280                for (Runnable task : queue)
281                {
282                    final ImageDataLoaderTask imgTask = (ImageDataLoaderTask) task;
283                    final IcyBufferedImage imgImage = imgTask.getImage();
284
285                    if ((imgImage == null) || (imgImage == image))
286                        tasks.add(imgTask);
287                }
288            }
289
290            // remove pending tasks for that image
291            for (ImageDataLoaderTask task : tasks)
292            {
293                executor.remove(task);
294                task.cancel(false);
295            }
296        }
297    }
298
299    /**
300     * Used for image / data loading from importer
301     */
302    static ImageDataLoader imageDataLoader = new ImageDataLoader();
303
304    /**
305     * Used internally to find out an image from its identity hash code
306     */
307    static Map<Integer, WeakIcyBufferedImageReference> images = new HashMap<Integer, WeakIcyBufferedImageReference>();
308    // static Map<Integer, Object> imagesMax = new HashMap<Integer, Object>();
309
310    /**
311     * Retrieve an {@link IcyBufferedImage} from its identity hash code
312     */
313    public static IcyBufferedImage getIcyBufferedImage(Integer idHashCode)
314    {
315        final WeakIcyBufferedImageReference ref;
316
317        synchronized (images)
318        {
319            ref = images.get(idHashCode);
320        }
321
322        if (ref != null)
323            return ref.get();
324
325        return null;
326    }
327
328    /**
329     * Retrieve an {@link IcyBufferedImage} from its identity hash code
330     */
331    public static IcyBufferedImage getIcyBufferedImage(int idHashCode)
332    {
333        return getIcyBufferedImage(Integer.valueOf(idHashCode));
334    }
335
336    /**
337     * @deprecated
338     */
339    @Deprecated
340    public static int TYPE_BYTE = TypeUtil.TYPE_BYTE;
341    /**
342     * @deprecated
343     */
344    @Deprecated
345    public static int TYPE_DOUBLE = TypeUtil.TYPE_DOUBLE;
346    /**
347     * @deprecated
348     */
349    @Deprecated
350    public static int TYPE_FLOAT = TypeUtil.TYPE_FLOAT;
351    /**
352     * @deprecated
353     */
354    @Deprecated
355    public static int TYPE_INT = TypeUtil.TYPE_INT;
356    /**
357     * @deprecated
358     */
359    @Deprecated
360    public static int TYPE_SHORT = TypeUtil.TYPE_SHORT;
361    /**
362     * @deprecated
363     */
364    @Deprecated
365    public static int TYPE_UNDEFINED = TypeUtil.TYPE_UNDEFINED;
366
367    /**
368     * @deprecated Use {@link IcyBufferedImageUtil.FilterType} instead.
369     */
370    @Deprecated
371    public static enum FilterType
372    {
373        NEAREST, BILINEAR, BICUBIC
374    };
375
376    /**
377     * @deprecated
378     */
379    @Deprecated
380    protected static IcyBufferedImageUtil.FilterType getNewFilterType(FilterType ft)
381    {
382        switch (ft)
383        {
384            default:
385            case NEAREST:
386                return IcyBufferedImageUtil.FilterType.NEAREST;
387            case BILINEAR:
388                return IcyBufferedImageUtil.FilterType.BILINEAR;
389            case BICUBIC:
390                return IcyBufferedImageUtil.FilterType.BICUBIC;
391        }
392    }
393
394    /**
395     * Convert a list of BufferedImage to an IcyBufferedImage (multi component).<br>
396     * IMPORTANT : source images can be used as part or as the whole result<br>
397     * so consider them as "lost"
398     * 
399     * @param imageList
400     *        list of {@link BufferedImage}
401     * @return {@link IcyBufferedImage}
402     * @deprecated
403     *             use {@link #createFrom} instead
404     */
405    @Deprecated
406    public static IcyBufferedImage convert(List<BufferedImage> imageList)
407    {
408        return createFrom(imageList);
409    }
410
411    /**
412     * Create an IcyBufferedImage (multi component) from a list of BufferedImage.<br>
413     * IMPORTANT: source images can be used as part or as the whole result so consider them as 'lost'.
414     * 
415     * @param imageList
416     *        list of {@link BufferedImage}
417     * @return {@link IcyBufferedImage}
418     * @throws IllegalArgumentException
419     *         if imageList is empty or contains incompatible images.
420     */
421    public static IcyBufferedImage createFrom(List<? extends BufferedImage> imageList) throws IllegalArgumentException
422    {
423        if (imageList.size() == 0)
424            throw new IllegalArgumentException("imageList should contains at least 1 image");
425
426        final List<IcyBufferedImage> icyImageList = new ArrayList<IcyBufferedImage>();
427
428        // transform images to icy images
429        for (BufferedImage image : imageList)
430            icyImageList.add(IcyBufferedImage.createFrom(image));
431
432        final IcyBufferedImage firstImage = icyImageList.get(0);
433
434        if (icyImageList.size() == 1)
435            return firstImage;
436
437        final DataType dataType = firstImage.getDataType_();
438        final int width = firstImage.getWidth();
439        final int height = firstImage.getHeight();
440
441        // calculate channel number
442        int numChannel = 0;
443        for (IcyBufferedImage image : icyImageList)
444            numChannel += image.getSizeC();
445
446        final Object[] data = Array2DUtil.createArray(dataType, numChannel);
447        final IcyColorMap[] colormaps = new IcyColorMap[numChannel];
448
449        // get data from all images
450        int destC = 0;
451        for (IcyBufferedImage image : icyImageList)
452        {
453            if (dataType != image.getDataType_())
454                throw new IllegalArgumentException("All images contained in imageList should have the same dataType");
455            if ((width != image.getWidth()) || (height != image.getHeight()))
456                throw new IllegalArgumentException("All images contained in imageList should have the same dimension");
457
458            for (int c = 0; c < image.getSizeC(); c++)
459            {
460                data[destC] = image.getDataXY(c);
461                colormaps[destC++] = image.getColorMap(c);
462            }
463        }
464
465        // create result image
466        final IcyBufferedImage result = new IcyBufferedImage(width, height, data, dataType.isSigned());
467
468        // restore colormaps
469        for (int c = 0; c < result.getSizeC(); c++)
470            result.setColorMap(c, colormaps[c], false);
471
472        return result;
473    }
474
475    /**
476     * Convert a BufferedImage to an IcyBufferedImage.<br>
477     * IMPORTANT : source image can be used as part or as the whole result<br>
478     * so consider it as "lost"
479     * 
480     * @param image
481     *        {@link BufferedImage}
482     * @return {@link IcyBufferedImage}
483     * @deprecated
484     *             use {@link #createFrom} instead
485     */
486    @Deprecated
487    public static IcyBufferedImage convert(BufferedImage image)
488    {
489        return createFrom(image);
490    }
491
492    /**
493     * Create an IcyBufferedImage from a {@link PlanarImage}.<br>
494     * IMPORTANT : source image can be used as part or as the whole result<br>
495     * so consider it as lost.
496     * 
497     * @param image
498     *        {@link PlanarImage}
499     * @return {@link IcyBufferedImage}
500     */
501    public static IcyBufferedImage createFrom(PlanarImage image, boolean signedDataType)
502    {
503        final DataBuffer db = image.getData().getDataBuffer();
504        final int w = image.getWidth();
505        final int h = image.getHeight();
506
507        if (db instanceof DataBufferByte)
508            return new IcyBufferedImage(w, h, ((DataBufferByte) db).getBankData(), signedDataType);
509        else if (db instanceof DataBufferShort)
510            return new IcyBufferedImage(w, h, ((DataBufferShort) db).getBankData(), signedDataType);
511        else if (db instanceof DataBufferUShort)
512            return new IcyBufferedImage(w, h, ((DataBufferUShort) db).getBankData(), signedDataType);
513        else if (db instanceof DataBufferInt)
514            return new IcyBufferedImage(w, h, ((DataBufferInt) db).getBankData(), signedDataType);
515        else if (db instanceof DataBufferFloat)
516            return new IcyBufferedImage(w, h, ((DataBufferFloat) db).getBankData(), true);
517        else if (db instanceof javax.media.jai.DataBufferFloat)
518            return new IcyBufferedImage(w, h, ((javax.media.jai.DataBufferFloat) db).getBankData(), true);
519        else if (db instanceof DataBufferDouble)
520            return new IcyBufferedImage(w, h, ((DataBufferDouble) db).getBankData(), true);
521        else if (db instanceof javax.media.jai.DataBufferDouble)
522            return new IcyBufferedImage(w, h, ((javax.media.jai.DataBufferDouble) db).getBankData(), true);
523        else
524            // JAI keep dataType and others stuff in their BufferedImage
525            return IcyBufferedImage.createFrom(image.getAsBufferedImage());
526    }
527
528    /**
529     * Create an IcyBufferedImage from a {@link PlanarImage}.<br>
530     * IMPORTANT : source image can be used as part or as the whole result<br>
531     * so consider it as lost.
532     * 
533     * @param image
534     *        {@link PlanarImage}
535     * @return {@link IcyBufferedImage}
536     */
537    public static IcyBufferedImage createFrom(PlanarImage image)
538    {
539        return createFrom(image, false);
540    }
541
542    /**
543     * Create an IcyBufferedImage from a BufferedImage.<br>
544     * IMPORTANT : source image can be used as part or as the whole result<br>
545     * so consider it as lost.
546     * 
547     * @param image
548     *        {@link BufferedImage}
549     * @return {@link IcyBufferedImage}
550     */
551    public static IcyBufferedImage createFrom(BufferedImage image)
552    {
553        // IcyBufferedImage --> no conversion needed
554        if (image instanceof IcyBufferedImage)
555            return (IcyBufferedImage) image;
556
557        // sort of IcyBufferedImage (JAI can return that type) --> no conversion needed
558        if (image.getColorModel() instanceof IcyColorModel)
559            return new IcyBufferedImage(
560                    IcyColorModel.createInstance((IcyColorModel) image.getColorModel(), false, false),
561                    image.getRaster());
562
563        final int w = image.getWidth();
564        final int h = image.getHeight();
565        final int type = image.getType();
566        final BufferedImage temp;
567        final Graphics g;
568
569        // we first want a component based image
570        switch (type)
571        {
572            case BufferedImage.TYPE_INT_RGB:
573            case BufferedImage.TYPE_INT_BGR:
574            case BufferedImage.TYPE_USHORT_555_RGB:
575            case BufferedImage.TYPE_USHORT_565_RGB:
576                temp = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
577                g = temp.createGraphics();
578                g.drawImage(image, 0, 0, null);
579                g.dispose();
580                break;
581
582            case BufferedImage.TYPE_INT_ARGB:
583            case BufferedImage.TYPE_INT_ARGB_PRE:
584            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
585                temp = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
586                g = temp.createGraphics();
587                g.drawImage(image, 0, 0, null);
588                g.dispose();
589                break;
590
591            case BufferedImage.TYPE_3BYTE_BGR:
592            case BufferedImage.TYPE_4BYTE_ABGR:
593                temp = image;
594                break;
595
596            default:
597                // if we have severals components with an unknown / incompatible sampleModel
598                if ((image.getColorModel().getNumComponents() > 1)
599                        && (!(image.getSampleModel() instanceof ComponentSampleModel)))
600                {
601                    // change it to a basic ABGR components image
602                    temp = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
603                    g = temp.createGraphics();
604                    g.drawImage(image, 0, 0, null);
605                    g.dispose();
606                }
607                else
608                    temp = image;
609                break;
610        }
611
612        // convert initial data type in our data type
613        final DataType dataType = DataType.getDataTypeFromDataBufferType(temp.getColorModel().getTransferType());
614        // get number of components
615        final int numComponents = temp.getRaster().getNumBands();
616
617        // create a compatible image in our format
618        final IcyBufferedImage result = new IcyBufferedImage(w, h, numComponents, dataType);
619
620        // copy data from the source image
621        result.copyData(temp);
622
623        // in some case we want to restore colormaps from source image
624        switch (type)
625        {
626            case BufferedImage.TYPE_BYTE_BINARY:
627            case BufferedImage.TYPE_BYTE_INDEXED:
628                if (numComponents == 2)
629                    result.setColorMaps(image);
630                break;
631
632            case BufferedImage.TYPE_INT_ARGB:
633            case BufferedImage.TYPE_INT_ARGB_PRE:
634            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
635            case BufferedImage.TYPE_4BYTE_ABGR:
636                if (numComponents == 4)
637                    result.setColorMap(3, LinearColorMap.alpha_, true);
638                break;
639        }
640
641        return result;
642    }
643
644    /**
645     * @deprecated Use
646     *             {@link LociImporterPlugin#getThumbnailCompatible(IFormatReader, int, int, int)}
647     *             instead.
648     */
649    @Deprecated
650    public static IcyBufferedImage createCompatibleThumbnailFrom(IFormatReader reader, int z, int t)
651            throws FormatException, IOException
652    {
653        return LociImporterPlugin.getThumbnailCompatible(reader, z, t, -1);
654    }
655
656    /**
657     * @deprecated Use {@link LociImporterPlugin#getThumbnail(IFormatReader, int, int, int)}
658     *             instead.
659     */
660    @Deprecated
661    public static IcyBufferedImage createThumbnailFrom(IFormatReader reader, int z, int t)
662            throws FormatException, IOException
663    {
664        return LociImporterPlugin.getThumbnail(reader, z, t, -1);
665    }
666
667    /**
668     * @deprecated Use {@link LociImporterPlugin#getImage(IFormatReader, Rectangle, int, int, int, int)}
669     *             instead.
670     */
671    @Deprecated
672    public static IcyBufferedImage createFrom(IFormatReader reader, int x, int y, int w, int h, int z, int t, int c)
673            throws FormatException, IOException
674    {
675        return LociImporterPlugin.getImage(reader, new Rectangle(x, y, w, h), z, t, c, 0);
676    }
677
678    /**
679     * @deprecated Use {@link LociImporterPlugin#getImage(IFormatReader, Rectangle, int, int)}
680     *             instead.
681     */
682    @Deprecated
683    public static IcyBufferedImage createFrom(IFormatReader reader, int z, int t) throws FormatException, IOException
684    {
685        return LociImporterPlugin.getImage(reader, null, z, t);
686    }
687
688    /**
689     * @deprecated Use {@link #IcyBufferedImage(int, int, IcyColorModel)} instead.
690     */
691    @Deprecated
692    public static IcyBufferedImage createEmptyImage(int width, int height, IcyColorModel cm)
693    {
694        return new IcyBufferedImage(width, height, cm);
695    }
696
697    /**
698     * Image source information used for delayed image loading
699     */
700    protected ImageSourceInfo imageSourceInfo;
701
702    /**
703     * automatic update of channel bounds
704     */
705    protected boolean autoUpdateChannelBounds;
706
707    /**
708     * required cached field as raster is volatile
709     */
710    protected final int width;
711    protected final int height;
712    protected final int minX;
713    protected final int minY;
714    protected final int offsetX;
715    protected final int offsetY;
716
717    /**
718     * parent <i>raster</i> field (required for volatile data)
719     */
720    protected Field rasterField;
721
722    /**
723     * data initialized state
724     */
725    protected boolean dataInitialized;
726
727    // internal lock counter
728    protected int lockedCount = 0;
729    // internal constructed state (needed for proper data initialization)
730    private boolean constructed = false;
731
732    /**
733     * internal updater
734     */
735    protected final UpdateEventHandler updater;
736    /**
737     * listeners
738     */
739    protected final List<IcyBufferedImageListener> listeners;
740
741    /**
742     * Build an Icy formatted BufferedImage, takes an IcyColorModel and a WritableRaster as input
743     * 
744     * @param cm
745     *        {@link IcyColorModel}
746     * @param wr
747     *        {@link WritableRaster}
748     * @param autoUpdateChannelBounds
749     *        If true then channel bounds are automatically calculated.
750     * @param dataInitialized
751     *        When set to <code>true</code> (default), we assume the image is created with initialized data (stored in the {@link WritableRaster} object)
752     *        otherwise data will be initialized / loaded on first data request (lazy loading).
753     * @param forceVolatileData
754     *        If set to <code>true</code> then image data is volatile regardless of {@link GeneralPreferences#getVirtualMode()} state and can be lost if not
755     *        specifically stored using <code>setDataxx(..)</code> methods.<br>
756     *        Image cache is used to handle data storage and can move data on disk when memory is getting low.<br>
757     *        Note that Default value for this parameter is <code>false</code>.
758     */
759    protected IcyBufferedImage(IcyColorModel cm, WritableRaster wr, boolean autoUpdateChannelBounds,
760            boolean dataInitialized, boolean forceVolatileData)
761    {
762        super(cm, wr, false, null);
763
764        // store it in the hashmap (weak reference)
765        synchronized (images)
766        {
767            images.put(Integer.valueOf(System.identityHashCode(this)), new WeakIcyBufferedImageReference(this));
768        }
769
770        imageSourceInfo = null;
771        width = wr.getWidth();
772        height = wr.getHeight();
773        minX = wr.getMinX();
774        minY = wr.getMinY();
775        offsetX = wr.getSampleModelTranslateX();
776        offsetY = wr.getSampleModelTranslateY();
777
778        // automatic update of channel bounds
779        this.autoUpdateChannelBounds = autoUpdateChannelBounds;
780
781        updater = new UpdateEventHandler(this, false);
782        listeners = new ArrayList<IcyBufferedImageListener>();
783
784        // default
785        rasterField = null;
786
787        // we want volatile data ?
788        if (forceVolatileData || GeneralPreferences.getVirtualMode())
789        {
790            try
791            {
792                // we need access to parent raster field
793                rasterField = ReflectionUtil.getField(BufferedImage.class, "raster", true);
794                // set it to null so it won't retain data
795                if (rasterField != null)
796                    rasterField.set(this, null);
797            }
798            catch (Exception e)
799            {
800                System.out.println(e.getMessage());
801                System.out.println("Warning: cannot get access to internal BufferedImage.raster field.");
802                System.out.println("         Image caching cannot be used for this image.");
803
804                // no access to parent raster...
805                rasterField = null;
806            }
807        }
808
809        this.dataInitialized = dataInitialized;
810
811        // we have initialized data ? --> save them in cache
812        if (dataInitialized)
813        {
814            // save data in cache (for volatile image)
815            saveRasterInCache(wr, true);
816            // update image components bounds
817            if (autoUpdateChannelBounds)
818                updateChannelsBounds();
819        }
820        // else
821        // {
822        // // we want to force loading from Importer if needed so we don't reference data
823        // volatileRaster = null;
824        // volatileData = null;
825        // }
826
827        // add listener to colorModel
828        cm.addListener(this);
829
830        constructed = true;
831    }
832
833    /**
834     * Build an Icy formatted BufferedImage, takes an IcyColorModel and a WritableRaster as input
835     * 
836     * @param cm
837     *        {@link IcyColorModel}
838     * @param wr
839     *        {@link WritableRaster}
840     * @param autoUpdateChannelBounds
841     *        If true then channel bounds are automatically calculated.<br>
842     */
843    protected IcyBufferedImage(IcyColorModel cm, WritableRaster wr, boolean autoUpdateChannelBounds)
844    {
845        this(cm, wr, autoUpdateChannelBounds, true, false);
846    }
847
848    /**
849     * Create an Icy formatted BufferedImage, takes an IcyColorModel and a WritableRaster as input
850     * 
851     * @param cm
852     *        {@link IcyColorModel}
853     * @param wr
854     *        {@link WritableRaster}
855     */
856    protected IcyBufferedImage(IcyColorModel cm, WritableRaster wr)
857    {
858        this(cm, wr, false, true, false);
859
860    }
861
862    /**
863     * Create an Icy formatted BufferedImage with specified IcyColorModel, width and height.<br>
864     * Private version, {@link IcyColorModel} is directly used internally.
865     */
866    protected IcyBufferedImage(IcyColorModel cm, int width, int height, boolean forceVolatileData)
867    {
868        this(cm, cm.createCompatibleWritableRaster(width, height), false, false, forceVolatileData);
869    }
870
871    /**
872     * Create an Icy formatted BufferedImage with specified IcyColorModel, width and height.<br>
873     * Private version, {@link IcyColorModel} is directly used internally.
874     */
875    protected IcyBufferedImage(IcyColorModel cm, int width, int height)
876    {
877        this(cm, cm.createCompatibleWritableRaster(width, height), false, false, false);
878    }
879
880    /**
881     * Create an Icy formatted BufferedImage with specified colorModel, width, height and input data.<br>
882     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[3][640 * 480], true);</code><br>
883     * <br>
884     * This constructor provides the best performance for massive image creation and computation as it allow you to
885     * directly send the data array and disable the channel bounds calculation.
886     * 
887     * @param cm
888     *        the color model
889     * @param width
890     * @param height
891     * @param data
892     *        image data<br>
893     *        Should be a 2D array with first dimension giving the number of component<br>
894     *        and second dimension equals to <code>width * height</code><br>
895     *        The array data type specify the internal data type and should match the given color
896     *        model parameter.
897     * @param autoUpdateChannelBounds
898     *        If true then channel bounds are automatically calculated.<br>
899     *        When set to false, you have to set bounds manually by calling
900     *        {@link #updateChannelsBounds()} or #setC
901     */
902    protected IcyBufferedImage(IcyColorModel cm, Object[] data, int width, int height, boolean autoUpdateChannelBounds)
903    {
904        this(cm, cm.createWritableRaster(data, width, height), autoUpdateChannelBounds);
905    }
906
907    /**
908     * Create an Icy formatted BufferedImage with specified width, height and input data.<br>
909     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[3][640 * 480], true);</code><br>
910     * <br>
911     * This constructor provides the best performance for massive image creation and computation as
912     * it allow you to directly send the data array and disable the channel bounds calculation.
913     * 
914     * @param width
915     * @param height
916     * @param data
917     *        image data<br>
918     *        Should be a 2D array with first dimension giving the number of component<br>
919     *        and second dimension equals to <code>width * height</code><br>
920     *        The array data type specify the internal data type.
921     * @param signed
922     *        use signed data for data type
923     * @param autoUpdateChannelBounds
924     *        If true then channel bounds are automatically calculated.<br>
925     *        When set to false, you have to set bounds manually by calling
926     *        {@link #updateChannelsBounds()} or #setC
927     */
928    public IcyBufferedImage(int width, int height, Object[] data, boolean signed, boolean autoUpdateChannelBounds)
929    {
930        this(IcyColorModel.createInstance(data.length, ArrayUtil.getDataType(data[0], signed)), data, width, height,
931                autoUpdateChannelBounds);
932    }
933
934    /**
935     * Create an Icy formatted BufferedImage with specified width, height and input data.<br>
936     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[3][640 * 480]);</code>
937     * 
938     * @param width
939     * @param height
940     * @param data
941     *        image data<br>
942     *        Should be a 2D array with first dimension giving the number of component<br>
943     *        and second dimension equals to <code>width * height</code><br>
944     *        The array data type specify the internal data type.
945     * @param signed
946     *        use signed data for data type
947     */
948    public IcyBufferedImage(int width, int height, Object[] data, boolean signed)
949    {
950        this(IcyColorModel.createInstance(data.length, ArrayUtil.getDataType(data[0], signed)), data, width, height,
951                false);
952    }
953
954    /**
955     * Create an Icy formatted BufferedImage with specified width, height and input data.<br>
956     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[3][640 * 480]);</code>
957     * 
958     * @param width
959     * @param height
960     * @param data
961     *        image data<br>
962     *        Should be a 2D array with first dimension giving the number of component<br>
963     *        and second dimension equals to <code>width * height</code><br>
964     *        The array data type specify the internal data type.
965     */
966    public IcyBufferedImage(int width, int height, Object[] data)
967    {
968        this(width, height, data, false);
969    }
970
971    /**
972     * Create a single channel Icy formatted BufferedImage with specified width, height and input data.<br>
973     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[640 * 480], true);</code><br>
974     * <br>
975     * This constructor provides the best performance for massive image creation and computation as it allow you to
976     * directly send the data array and disable the channel bounds calculation.
977     * 
978     * @param width
979     * @param height
980     * @param data
981     *        image data array.<br>
982     *        The length of the array should be equals to <code>width * height</code>.<br>
983     *        The array data type specify the internal data type.
984     * @param signed
985     *        use signed data for data type
986     * @param autoUpdateChannelBounds
987     *        If true then channel bounds are automatically calculated.<br>
988     *        When set to false, you have to set bounds manually by calling
989     *        {@link #updateChannelsBounds()} or #setC
990     * @see #IcyBufferedImage(int, int, Object[], boolean, boolean)
991     */
992    public IcyBufferedImage(int width, int height, Object data, boolean signed, boolean autoUpdateChannelBounds)
993    {
994        this(width, height, ArrayUtil.encapsulate(data), signed, autoUpdateChannelBounds);
995    }
996
997    /**
998     * Create a single channel Icy formatted BufferedImage with specified width, height and input
999     * data.<br>
1000     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[640 * 480]);</code>
1001     * 
1002     * @param width
1003     * @param height
1004     * @param data
1005     *        image data<br>
1006     *        The length of the array should be equals to <code>width * height</code>.<br>
1007     *        The array data type specify the internal data type.
1008     * @param signed
1009     *        use signed data for data type
1010     */
1011    public IcyBufferedImage(int width, int height, Object data, boolean signed)
1012    {
1013        this(width, height, ArrayUtil.encapsulate(data), signed);
1014    }
1015
1016    /**
1017     * Create a single channel Icy formatted BufferedImage with specified width, height and input
1018     * data.<br>
1019     * ex : <code>img = new IcyBufferedImage(640, 480, new byte[640 * 480]);</code>
1020     * 
1021     * @param width
1022     * @param height
1023     * @param data
1024     *        image data<br>
1025     *        The length of the array should be equals to <code>width * height</code>.<br>
1026     *        The array data type specify the internal data type.
1027     */
1028    public IcyBufferedImage(int width, int height, Object data)
1029    {
1030        this(width, height, ArrayUtil.encapsulate(data));
1031    }
1032
1033    /**
1034     * Create an ICY formatted BufferedImage with specified width, height,<br>
1035     * number of component and dataType.
1036     * 
1037     * @param width
1038     * @param height
1039     * @param numComponents
1040     * @param dataType
1041     *        image data type {@link DataType}
1042     */
1043    public IcyBufferedImage(int width, int height, int numComponents, DataType dataType, boolean forceVolatileData)
1044    {
1045        this(IcyColorModel.createInstance(numComponents, dataType), width, height, forceVolatileData);
1046    }
1047
1048    /**
1049     * Create an ICY formatted BufferedImage with specified width, height,<br>
1050     * number of component and dataType.
1051     * 
1052     * @param width
1053     * @param height
1054     * @param numComponents
1055     * @param dataType
1056     *        image data type {@link DataType}
1057     */
1058    public IcyBufferedImage(int width, int height, int numComponents, DataType dataType)
1059    {
1060        this(IcyColorModel.createInstance(numComponents, dataType), width, height, false);
1061    }
1062
1063    /**
1064     * Create an ICY formatted BufferedImage with specified width, height and IcyColorModel
1065     * type.<br>
1066     */
1067    public IcyBufferedImage(int width, int height, IcyColorModel cm)
1068    {
1069        this(width, height, cm.getNumComponents(), cm.getDataType_());
1070    }
1071
1072    /**
1073     * @deprecated use {@link #IcyBufferedImage(int, int, int, DataType)} instead
1074     */
1075    @Deprecated
1076    public IcyBufferedImage(int width, int height, int numComponents, int dataType, boolean signed)
1077    {
1078        this(IcyColorModel.createInstance(numComponents, dataType, signed), width, height);
1079    }
1080
1081    /**
1082     * @deprecated use {@link #IcyBufferedImage(int, int, int, DataType)} instead
1083     */
1084    @Deprecated
1085    public IcyBufferedImage(int width, int height, int numComponents, int dataType)
1086    {
1087        this(IcyColorModel.createInstance(numComponents, dataType, false), width, height);
1088    }
1089
1090    @Override
1091    protected void finalize() throws Throwable
1092    {
1093        // cancel any pending loading tasks for this image
1094        imageDataLoader.cancelTasks(this);
1095        // image has been released, be sure to clear cache
1096        ImageCache.remove(this);
1097
1098        // remove it from hashmap
1099        synchronized (images)
1100        {
1101            images.remove(Integer.valueOf(System.identityHashCode(this)));
1102        }
1103
1104        super.finalize();
1105    }
1106
1107    public ImageSourceInfo getImageSourceInfo()
1108    {
1109        return imageSourceInfo;
1110    }
1111
1112    /**
1113     * Set the image source information that will be used later for lazy image data loading.
1114     */
1115    public void setImageSourceInfo(SequenceIdImporter imp, int series, int resolution, Rectangle region, int t, int z,
1116            int c)
1117    {
1118        imageSourceInfo = new ImageSourceInfo(imp, series, resolution, region, t, z, c);
1119    }
1120
1121    /**
1122     * Returns <code>true</code> if data is initialized
1123     */
1124    public boolean isDataInitialized()
1125    {
1126        return dataInitialized;
1127    }
1128
1129    /**
1130     * Returns <code>true</code> if data is currently loaded in memory.<br>
1131     * It returns <code>false</code> if data has not yet be initialized (see {@link #isDataInitialized()}) or if data is cached on disk (not anymore in memory)
1132     */
1133    public boolean isDataInMemory()
1134    {
1135        return ImageCache.isOnMemoryCache(this);
1136    }
1137
1138    /**
1139     * Returns <code>true</code> if image data is volatile.<br>
1140     * Volatile data means <b>there is no strong reference on the internal data arrays</b> (data can be cached on disk) and so <b>any <i>external</i> changes on
1141     * them can be lost</b> if they has not been specifically set using setDataxx() methods.
1142     * Volatile is useful when you want to load many images with low memory consumption but you should use it carefully as it has some limitations.<br>
1143     * 
1144     * @see #setVolatile(boolean)
1145     */
1146    public boolean isVolatile()
1147    {
1148        return rasterField != null;
1149    }
1150
1151    /**
1152     * Sets the <i>volatile</i> state for this image.<br>
1153     * Volatile data means <b>there is no strong reference on the internal data arrays</b> (data can be cached on disk) and so <b>any <i>external</i> changes on
1154     * them can be lost</b> if they has not been specifically set using setDataxx() methods.
1155     * Volatile is useful when you want to load many images with low memory consumption but you should use it carefully as it has some limitations.<br>
1156     * Setting the image to volatile <b>immediately release internal strong reference on data arrays</b> (see {@link #lockRaster()} and
1157     * {@link #releaseRaster(boolean)} methods).<br>
1158     * 
1159     * @throws OutOfMemoryError
1160     *         if there is not enough memory available to store image
1161     *         data when setting back to <i>non volatile</i> state
1162     * @throws UnsupportedOperationException
1163     *         if cache engine is not initialized (error at initialization).
1164     */
1165    public void setVolatile(boolean value) throws OutOfMemoryError, UnsupportedOperationException
1166    {
1167        // we want volatile data ?
1168        if (value)
1169        {
1170            if (rasterField == null)
1171            {
1172                // cache engine couldn't be used
1173                if (!ImageCache.isEnabled())
1174                    throw new UnsupportedOperationException(
1175                            "IcyBufferedImage.setVolatile(..) error: Image cache is disabled !");
1176
1177                try
1178                {
1179                    // we need access to parent raster field
1180                    rasterField = ReflectionUtil.getField(BufferedImage.class, "raster", true);
1181
1182                    if (rasterField != null)
1183                    {
1184                        // save data in cache as we will release the strong reference
1185                        saveDataInCache();
1186                        // immediately remove strong reference to data
1187                        rasterField.set(this, null);
1188                    }
1189                }
1190                catch (Exception e)
1191                {
1192                    System.out.println(e.getMessage());
1193                    System.out.println("Warning: cannot get access to internal BufferedImage.raster field.");
1194                    System.out.println("         Image caching cannot be used for this image.");
1195
1196                    // no access to parent raster...
1197                    rasterField = null;
1198                }
1199            }
1200        }
1201        // no volatile anymore ?
1202        else
1203        {
1204            // were we volatile ?
1205            if (rasterField != null)
1206            {
1207                try
1208                {
1209                    // need to set back raster strong reference
1210                    if (super.getRaster() == null)
1211                    {
1212                        // data not yet initialized ?
1213                        if (!isDataInitialized())
1214                            // we don't want to force data loading for that
1215                            rasterField.set(this, buildRaster(createEmptyRasterData()));
1216                        else
1217                            rasterField.set(this, getRaster());
1218                    }
1219
1220                    // set it to null so it won't retain data
1221                    rasterField = null;
1222                    // clear data set in cache (no more used)
1223                    ImageCache.remove(this);
1224                }
1225                catch (OutOfMemoryError e)
1226                {
1227                    System.err.println(e.getMessage());
1228                    System.err.println(
1229                            "IcyBufferedImage.setVolatile(false) error: not enough memory to set image data back in memory.");
1230                    throw e;
1231                }
1232                catch (Throwable e)
1233                {
1234                    System.err.println(e.getMessage());
1235                    System.err.println(
1236                            "IcyBufferedImage.setVolatile(..) error: cannot set parent raster field (data lost).");
1237                }
1238            }
1239        }
1240    }
1241
1242    /**
1243     * Force loading data for this image (so channel bounds can be correctly computed even with lazy loading)
1244     */
1245    public void loadData()
1246    {
1247        if (!isDataInitialized())
1248            // that is enough to get data loaded
1249            getRaster();
1250    }
1251
1252    protected synchronized WritableRaster getRaster(boolean retain)
1253    {
1254        // always try first from parent
1255        WritableRaster result = super.getRaster();
1256
1257        // data not yet initialized ?
1258        if (constructed && !isDataInitialized())
1259        {
1260            // initialize data
1261            final Object rasterData = initializeData();
1262
1263            // could not initialize data ? --> use temporary empty data (we want to retry data initialization later)
1264            if (rasterData == null)
1265                return buildRaster(createEmptyRasterData());
1266
1267            // save them in cache (for volatile image) but don't need to be eternal
1268            saveRasterDataInCache(rasterData, false);
1269            // we have the parent raster ? --> update its data (important to do it before setting data initialized)
1270            if (result != null)
1271                setRasterData(result, rasterData);
1272
1273            // data is initialized (important to set it before updating channel bounds)
1274            dataInitialized = true;
1275
1276            // update image channels bounds
1277            if (autoUpdateChannelBounds)
1278                updateChannelsBounds();
1279        }
1280
1281        // we don't have the parent raster ? (mean we have a volatile image)
1282        if (result == null)
1283        {
1284            // get it from cache
1285            result = loadRasterFromCache();
1286
1287            try
1288            {
1289                // store it in the original strong reference if asked
1290                if (retain && (rasterField != null))
1291                    rasterField.set(this, result);
1292            }
1293            catch (Exception e)
1294            {
1295                System.out.println(e.getMessage());
1296                System.out.println("Warning: cannot set internal BufferedImage.raster field.");
1297            }
1298        }
1299
1300        return result;
1301    }
1302
1303    @Override
1304    public WritableRaster getRaster()
1305    {
1306        return getRaster(false);
1307    }
1308
1309    @Override
1310    public WritableRaster getAlphaRaster()
1311    {
1312        return getColorModel().getAlphaRaster(getRaster());
1313    }
1314
1315    /**
1316     * Return <code>true</code> if raster data is strongly referenced, <code>false</code> otherwise.<br>
1317     * Note that it doesn't necessary mean that we called {@link #lockRaster()} method.
1318     * 
1319     * @see #lockRaster()
1320     * @see #isVolatile()
1321     */
1322    public synchronized boolean isRasterLocked()
1323    {
1324        try
1325        {
1326            return (rasterField == null) || (rasterField.get(this) != null);
1327        }
1328        catch (Exception e)
1329        {
1330            // something bad happened, just assume false then..
1331            return false;
1332        }
1333    }
1334
1335    /**
1336     * Ensure raster data remains strongly referenced until we call {@link #releaseRaster(boolean)}.<br>
1337     * This is important to lock / release raster for Volatile image when you are modifying data externally otherwise data could be lost.
1338     * 
1339     * @see #releaseRaster(boolean)
1340     * @see #isVolatile()
1341     */
1342    public synchronized void lockRaster()
1343    {
1344        if (lockedCount++ != 0)
1345            return;
1346
1347        getRaster(true);
1348    }
1349
1350    /**
1351     * Release the raster object.
1352     * 
1353     * @param saveInCache
1354     *        force to save raster data in cache (for volatile image only)
1355     */
1356    public synchronized void releaseRaster(boolean saveInCache)
1357    {
1358        if (--lockedCount != 0)
1359            return;
1360
1361        try
1362        {
1363            // force saving changed data in cache before releasing raster
1364            if (saveInCache)
1365                saveDataInCache();
1366            // set parent raster to null (release raster object)
1367            if (rasterField != null)
1368                rasterField.set(this, null);
1369        }
1370        catch (Exception e)
1371        {
1372            System.out.println(e.getMessage());
1373            System.out.println("Warning: cannot set internal BufferedImage.raster field.");
1374        }
1375    }
1376
1377    @Override
1378    public int getWidth()
1379    {
1380        return width;
1381    }
1382
1383    @Override
1384    public int getHeight()
1385    {
1386        return height;
1387    }
1388
1389    @Override
1390    public int getWidth(ImageObserver observer)
1391    {
1392        return width;
1393    }
1394
1395    @Override
1396    public int getHeight(ImageObserver observer)
1397    {
1398        return height;
1399    }
1400
1401    @Override
1402    public int getMinX()
1403    {
1404        return minX;
1405    }
1406
1407    @Override
1408    public int getMinY()
1409    {
1410        return minY;
1411    }
1412
1413    @Override
1414    public int getTileWidth()
1415    {
1416        return getWidth();
1417    }
1418
1419    @Override
1420    public int getTileHeight()
1421    {
1422        return getHeight();
1423    }
1424
1425    @Override
1426    public int getTileGridXOffset()
1427    {
1428        return offsetX;
1429    }
1430
1431    @Override
1432    public int getTileGridYOffset()
1433    {
1434        return offsetY;
1435    }
1436
1437    @Override
1438    public SampleModel getSampleModel()
1439    {
1440        return getRaster().getSampleModel();
1441    }
1442
1443    @Override
1444    public void coerceData(boolean isAlphaPremultiplied)
1445    {
1446        // don't need to do any conversion here...
1447    }
1448
1449    @Override
1450    public WritableRaster copyData(WritableRaster outRaster)
1451    {
1452        lockRaster();
1453        try
1454        {
1455            return super.copyData(outRaster);
1456        }
1457        finally
1458        {
1459            releaseRaster(true);
1460        }
1461    }
1462
1463    @Override
1464    public Raster getTile(int tileX, int tileY)
1465    {
1466        lockRaster();
1467        try
1468        {
1469            return super.getTile(tileX, tileY);
1470        }
1471        finally
1472        {
1473            releaseRaster(false);
1474        }
1475    }
1476
1477    @Override
1478    public WritableRaster getWritableTile(int tileX, int tileY)
1479    {
1480        lockRaster();
1481        try
1482        {
1483            return super.getWritableTile(tileX, tileY);
1484        }
1485        finally
1486        {
1487            releaseRaster(false);
1488        }
1489    }
1490
1491    @Override
1492    public Raster getData()
1493    {
1494        lockRaster();
1495        try
1496        {
1497            return super.getData();
1498        }
1499        finally
1500        {
1501            releaseRaster(false);
1502        }
1503    }
1504
1505    @Override
1506    public Raster getData(Rectangle rect)
1507    {
1508        lockRaster();
1509        try
1510        {
1511            return super.getData(rect);
1512        }
1513        finally
1514        {
1515            releaseRaster(false);
1516        }
1517    }
1518
1519    @Override
1520    public void setData(Raster r)
1521    {
1522        lockRaster();
1523        try
1524        {
1525            super.setData(r);
1526        }
1527        finally
1528        {
1529            releaseRaster(false);
1530        }
1531    }
1532
1533    @Override
1534    public int getRGB(int x, int y)
1535    {
1536        lockRaster();
1537        try
1538        {
1539            return super.getRGB(x, y);
1540        }
1541        finally
1542        {
1543            releaseRaster(false);
1544        }
1545    }
1546
1547    @Override
1548    public int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)
1549    {
1550        lockRaster();
1551        try
1552        {
1553            return super.getRGB(startX, startY, w, h, rgbArray, offset, scansize);
1554        }
1555        finally
1556        {
1557            releaseRaster(false);
1558        }
1559    }
1560
1561    @Override
1562    public synchronized void setRGB(int x, int y, int rgb)
1563    {
1564        lockRaster();
1565        try
1566        {
1567            super.setRGB(x, y, rgb);
1568        }
1569        finally
1570        {
1571            // FIXME: implement delayed cache saving to avoid very poor performance here
1572            releaseRaster(true);
1573        }
1574    }
1575
1576    @Override
1577    public void setRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)
1578    {
1579        lockRaster();
1580        try
1581        {
1582            super.setRGB(startX, startY, w, h, rgbArray, offset, scansize);
1583        }
1584        finally
1585        {
1586            releaseRaster(true);
1587        }
1588    }
1589
1590    /**
1591     * Return the owner sequence (can be null if the image is not owned in any Sequence)
1592     */
1593    public Sequence getOwnerSequence()
1594    {
1595        // just use the listeners to find it (Sequence always listen image event)
1596        for (IcyBufferedImageListener listener : listeners)
1597            if (listener instanceof Sequence)
1598                return (Sequence) listener;
1599
1600        return null;
1601    }
1602
1603    protected WritableRaster loadRasterFromCache()
1604    {
1605        Object rasterData = null;
1606        boolean datalost = false;
1607
1608        try
1609        {
1610            // get data from cache
1611            rasterData = ImageCache.get(this);
1612        }
1613        catch (Throwable e)
1614        {
1615            datalost = true;
1616            System.err.println(e.getMessage());
1617        }
1618
1619        // should happen only for unmodified data
1620        if (rasterData == null)
1621        {
1622            // we should be able to initialize data back
1623            rasterData = initializeData();
1624
1625            // couldn't initialize data ? create empty data without saving in cache (we want to retry later)
1626            if (rasterData == null)
1627            {
1628                rasterData = createEmptyRasterData();
1629
1630                //// don't notify twice
1631                // if (!datalost)
1632                // System.err.println("IcyBufferedImage.loadRasterFromCache: cannot get image data (data lost)");
1633            }
1634            else
1635            {
1636                // data could not be loaded from cache but was correctly restored
1637                if (datalost)
1638                    System.out.println("Data re-initialized (changes are lost)");
1639
1640                // save it in cache (not eternal here as this is default data)
1641                saveRasterDataInCache(rasterData, false);
1642            }
1643        }
1644
1645        return buildRaster(rasterData);
1646    }
1647
1648    /**
1649     * Explicitly save the image data in cache (only for volatile image)
1650     * 
1651     * @see #isVolatile()
1652     */
1653    public void saveDataInCache()
1654    {
1655        // need to be saved only if initialized
1656        if (isDataInitialized())
1657            saveRasterInCache(getRaster());
1658    }
1659
1660    protected void saveRasterInCache(WritableRaster wr, boolean eternal)
1661    {
1662        saveRasterDataInCache(getRasterData(wr), eternal);
1663    }
1664
1665    protected void saveRasterInCache(WritableRaster wr)
1666    {
1667        saveRasterInCache(wr, true);
1668    }
1669
1670    protected void saveRasterDataInCache(Object rasterData, boolean eternal)
1671    {
1672        // save data in cache (volatile image only)
1673        if (isVolatile())
1674        {
1675            try
1676            {
1677                ImageCache.set(this, rasterData, eternal);
1678            }
1679            catch (Throwable e)
1680            {
1681                System.err.println(e.getMessage());
1682            }
1683        }
1684    }
1685
1686    protected void saveRasterDataInCache(Object rasterData)
1687    {
1688        saveRasterDataInCache(rasterData, true);
1689    }
1690
1691    /**
1692     * Build raster from an Object (internally the Object is always a 2D array of native data type)
1693     */
1694    protected WritableRaster buildRaster(Object data)
1695    {
1696        return IcyColorModel.createWritableRaster(data, getWidth(), getHeight());
1697    }
1698
1699    /**
1700     * Returns raster data in Object format (internally we always have a 2D array of native data type)
1701     */
1702    protected static Object getRasterData(WritableRaster wr)
1703    {
1704        final DataBuffer db = wr.getDataBuffer();
1705
1706        switch (db.getDataType())
1707        {
1708            case DataBuffer.TYPE_BYTE:
1709                return ((DataBufferByte) db).getBankData();
1710            case DataBuffer.TYPE_USHORT:
1711                return ((DataBufferUShort) db).getBankData();
1712            case DataBuffer.TYPE_SHORT:
1713                return ((DataBufferShort) db).getBankData();
1714            case DataBuffer.TYPE_INT:
1715                return ((DataBufferInt) db).getBankData();
1716            case DataBuffer.TYPE_FLOAT:
1717                return ((DataBufferFloat) db).getBankData();
1718            case DataBuffer.TYPE_DOUBLE:
1719                return ((DataBufferDouble) db).getBankData();
1720            default:
1721                return null;
1722        }
1723    }
1724
1725    /**
1726     * Set raster data (Object is always a 2D array of native data type)
1727     */
1728    protected void setRasterData(WritableRaster wr, Object data)
1729    {
1730        final Object dest;
1731        final DataBuffer db = wr.getDataBuffer();
1732
1733        switch (db.getDataType())
1734        {
1735            case DataBuffer.TYPE_BYTE:
1736                dest = ((DataBufferByte) db).getBankData();
1737                break;
1738            case DataBuffer.TYPE_USHORT:
1739                dest = ((DataBufferUShort) db).getBankData();
1740                break;
1741            case DataBuffer.TYPE_SHORT:
1742                dest = ((DataBufferShort) db).getBankData();
1743                break;
1744            case DataBuffer.TYPE_INT:
1745                dest = ((DataBufferInt) db).getBankData();
1746                break;
1747            case DataBuffer.TYPE_FLOAT:
1748                dest = ((DataBufferFloat) db).getBankData();
1749                break;
1750            case DataBuffer.TYPE_DOUBLE:
1751                dest = ((DataBufferDouble) db).getBankData();
1752                break;
1753            default:
1754                dest = null;
1755                break;
1756        }
1757
1758        if (dest != null)
1759        {
1760            final int len = Array.getLength(dest);
1761            for (int i = 0; i < len; i++)
1762            {
1763                final Object destSub = Array.get(dest, i);
1764                System.arraycopy(Array.get(data, i), 0, destSub, 0, Array.getLength(destSub));
1765            }
1766        }
1767    }
1768
1769    protected Object initializeData()
1770    {
1771        try
1772        {
1773            // load data from importer
1774            return loadDataFromImporter();
1775        }
1776        catch (InterruptedException e)
1777        {
1778            System.err.println(
1779                    "IcyBufferedImage.loadDataFromImporter() warning: image loading from ImageProvider was interrupted (image data not retrieved).");
1780
1781            // we want to keep the interrupted state here
1782            Thread.currentThread().interrupt();
1783
1784            return null;
1785        }
1786        catch (ClosedByInterruptException e)
1787        {
1788            // this one should never happen as loading is done in a separate thread (executor)
1789            System.err.println(
1790                    "IcyBufferedImage.loadDataFromImporter() error: image loading from ImageProvider was interrupted (further image won't be loaded) !");
1791
1792            // we want to keep the interrupted state here
1793            Thread.currentThread().interrupt();
1794
1795            return null;
1796        }
1797        catch (Exception e)
1798        {
1799            System.err.println(e);
1800            System.err.println(
1801                    "IcyBufferedImage.loadDataFromImporter() warning: cannot get image from ImageProvider (possible data loss).");
1802
1803            return null;
1804        }
1805    }
1806
1807    protected Object createEmptyRasterData()
1808    {
1809        final DataType dataType = getDataType_();
1810        final int sizeC = getSizeC();
1811        final int sizeXY = getSizeX() * getSizeY();
1812
1813        // create the result array (always 2D native type)
1814        final Object[] result = Array2DUtil.createArray(dataType, sizeC);
1815
1816        for (int c = 0; c < sizeC; c++)
1817            result[c] = Array1DUtil.createArray(dataType, sizeXY);
1818
1819        return result;
1820    }
1821
1822    protected Object loadDataFromImporter() throws UnsupportedFormatException, IOException, InterruptedException
1823    {
1824        // image source information not defined (not attached to importer) ? --> create empty data
1825        if (imageSourceInfo == null)
1826            return createEmptyRasterData();
1827
1828        try
1829        {
1830            // get data from importer using
1831            return imageDataLoader.loadImageData(this);
1832        }
1833        catch (ExecutionException e)
1834        {
1835            final Throwable cause = e.getCause();
1836
1837            if (cause instanceof UnsupportedFormatException)
1838                throw ((UnsupportedFormatException) cause);
1839            if (cause instanceof IOException)
1840                throw ((IOException) cause);
1841            throw new IOException(cause);
1842        }
1843    }
1844
1845    /**
1846     * @return true is channel bounds are automatically updated when image data is modified.
1847     * @see #setAutoUpdateChannelBounds(boolean)
1848     */
1849    public boolean getAutoUpdateChannelBounds()
1850    {
1851        return autoUpdateChannelBounds;
1852    }
1853
1854    /**
1855     * If set to <code>true</code> (default) then channel bounds will be automatically recalculated
1856     * when image data is modified.<br>
1857     * This can consume some time if you make many updates on a large image.<br>
1858     * In this case you should do your updates in a {@link #beginUpdate()} ... {@link #endUpdate()}
1859     * block to avoid
1860     * severals recalculation.
1861     */
1862    public void setAutoUpdateChannelBounds(boolean value)
1863    {
1864        if (autoUpdateChannelBounds != value)
1865        {
1866            if (value)
1867                updateChannelsBounds();
1868
1869            autoUpdateChannelBounds = value;
1870        }
1871    }
1872
1873    /**
1874     * @deprecated Uses
1875     *             {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, BufferedImage, LUT)}
1876     *             instead.
1877     */
1878    @Deprecated
1879    public BufferedImage convertToBufferedImage(BufferedImage out, LUT lut)
1880    {
1881        return IcyBufferedImageUtil.toBufferedImage(this, out, lut);
1882    }
1883
1884    /**
1885     * @deprecated Uses
1886     *             {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, BufferedImage)}
1887     *             instead.
1888     */
1889    @Deprecated
1890    public BufferedImage convertToBufferedImage(BufferedImage out)
1891    {
1892        return IcyBufferedImageUtil.toBufferedImage(this, out);
1893    }
1894
1895    /**
1896     * @deprecated Uses
1897     *             {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, BufferedImage, LUT)}
1898     *             instead.
1899     */
1900    @Deprecated
1901    public BufferedImage getARGBImage(LUT lut, BufferedImage out)
1902    {
1903        return IcyBufferedImageUtil.getARGBImage(this, lut, out);
1904    }
1905
1906    /**
1907     * @deprecated Use {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, BufferedImage)}
1908     *             instead.
1909     */
1910    @Deprecated
1911    public BufferedImage getARGBImage(BufferedImage out)
1912    {
1913        return IcyBufferedImageUtil.getARGBImage(this, out);
1914    }
1915
1916    /**
1917     * @deprecated Use {@link IcyBufferedImageUtil#getARGBImage(IcyBufferedImage, LUT)} instead.
1918     */
1919    @Deprecated
1920    public BufferedImage getARGBImage(LUT lut)
1921    {
1922        return IcyBufferedImageUtil.getARGBImage(this, lut);
1923    }
1924
1925    /**
1926     * @deprecated Use {@link IcyBufferedImageUtil#getARGBImage(IcyBufferedImage)} instead.
1927     */
1928    @Deprecated
1929    public BufferedImage getARGBImage()
1930    {
1931        return IcyBufferedImageUtil.getARGBImage(this);
1932    }
1933
1934    /**
1935     * @deprecated Uses
1936     *             {@link IcyBufferedImageUtil#convertType(IcyBufferedImage, DataType, Scaler[])}
1937     *             instead.
1938     */
1939    @Deprecated
1940    public IcyBufferedImage convertToType(DataType dataType, Scaler scaler)
1941    {
1942        return IcyBufferedImageUtil.convertToType(this, dataType, scaler);
1943    }
1944
1945    /**
1946     * @deprecated Uses
1947     *             {@link IcyBufferedImageUtil#convertType(IcyBufferedImage,DataType, Scaler[])}
1948     *             instead.
1949     */
1950    @Deprecated
1951    public IcyBufferedImage convertToType(int dataType, boolean signed, Scaler scaler)
1952    {
1953        return IcyBufferedImageUtil.convertToType(this, DataType.getDataType(dataType, signed), scaler);
1954    }
1955
1956    /**
1957     * @deprecated Uses
1958     *             {@link IcyBufferedImageUtil#convertToType(IcyBufferedImage, DataType, boolean)}
1959     *             instead.
1960     */
1961    @Deprecated
1962    public IcyBufferedImage convertToType(DataType dataType, boolean rescale)
1963    {
1964        return IcyBufferedImageUtil.convertToType(this, dataType, rescale);
1965    }
1966
1967    /**
1968     * @deprecated Uses
1969     *             {@link IcyBufferedImageUtil#convertToType(IcyBufferedImage,DataType, boolean)}
1970     *             instead
1971     */
1972    @Deprecated
1973    public IcyBufferedImage convertToType(int dataType, boolean signed, boolean rescale)
1974    {
1975        return convertToType(DataType.getDataType(dataType, signed), rescale);
1976    }
1977
1978    /**
1979     * @deprecated Use {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, int, LUT)}
1980     *             instead
1981     */
1982    @Deprecated
1983    public BufferedImage convertToBufferedImage(LUT lut, int imageType)
1984    {
1985        return IcyBufferedImageUtil.toBufferedImage(this, imageType, lut);
1986    }
1987
1988    /**
1989     * @deprecated Use {@link IcyBufferedImageUtil#toBufferedImage(IcyBufferedImage, int, LUT)}
1990     *             instead
1991     */
1992    @Deprecated
1993    public BufferedImage convertToBufferedImage(int imageType, LUT lut)
1994    {
1995        return IcyBufferedImageUtil.toBufferedImage(this, imageType, lut);
1996    }
1997
1998    /**
1999     * @deprecated Use {@link IcyBufferedImageUtil#getCopy(IcyBufferedImage)} instead
2000     */
2001    @Deprecated
2002    public IcyBufferedImage getCopy()
2003    {
2004        return IcyBufferedImageUtil.getCopy(this);
2005    }
2006
2007    /**
2008     * @deprecated Uses
2009     *             {@link IcyBufferedImageUtil#getSubImage(IcyBufferedImage, int, int, int, int)}
2010     *             instead
2011     */
2012    @Deprecated
2013    public IcyBufferedImage getSubImageCopy(int x, int y, int w, int h)
2014    {
2015        return IcyBufferedImageUtil.getSubImage(this, x, y, w, h);
2016    }
2017
2018    /**
2019     * Not supported on IcyBufferedImage, use getSubImageCopy instead.
2020     */
2021    @Deprecated
2022    @Override
2023    public IcyBufferedImage getSubimage(int x, int y, int w, int h)
2024    {
2025        // IcyBufferedImage doesn't support subImaging (incorrect draw and copy operation)
2026        throw new UnsupportedOperationException(
2027                "IcyBufferedImage doesn't support getSubimage method, use getSubImageCopy instead.");
2028
2029        // return new IcyBufferedImage(getIcyColorModel(), getRaster().createWritableChild(x, y, w,
2030        // h, 0, 0, null));
2031    }
2032
2033    /**
2034     * Return a single component image corresponding to the component c of current image.<br>
2035     * This actually create a new image which share its data with internal image
2036     * so any modifications to one affect the other.<br>
2037     * if <code>(c == -1)</code> then current image is directly returned<br>
2038     * if <code>((c == 0) || (sizeC == 1))</code> then current image is directly returned<br>
2039     * if <code>((c < 0) || (c >= sizeC))</code> then it returns <code>null</code>
2040     * 
2041     * @see IcyBufferedImageUtil#extractChannel(IcyBufferedImage, int)
2042     * @since version 1.0.3.3b
2043     */
2044    public IcyBufferedImage getImage(int c)
2045    {
2046        if (c == -1)
2047            return this;
2048
2049        final int sizeC = getSizeC();
2050
2051        if ((c < 0) || (c >= sizeC))
2052            return null;
2053        if (sizeC == 1)
2054            return this;
2055
2056        return new IcyBufferedImage(getWidth(), getHeight(), getDataXY(c), isSignedDataType());
2057    }
2058
2059    /**
2060     * @deprecated Use {@link IcyBufferedImageUtil#extractChannel(IcyBufferedImage, int)} instead.
2061     */
2062    @Deprecated
2063    public IcyBufferedImage extractChannel(int channelNumber)
2064    {
2065        return IcyBufferedImageUtil.extractChannel(this, channelNumber);
2066    }
2067
2068    /**
2069     * @deprecated Use {@link IcyBufferedImageUtil#extractChannels(IcyBufferedImage, List)} instead.
2070     */
2071    @Deprecated
2072    public IcyBufferedImage extractChannels(List<Integer> channelNumbers)
2073    {
2074        return IcyBufferedImageUtil.extractChannels(this, channelNumbers);
2075    }
2076
2077    /**
2078     * @deprecated Use {@link IcyBufferedImageUtil#extractChannel(IcyBufferedImage, int)} instead
2079     */
2080    @Deprecated
2081    public IcyBufferedImage extractBand(int bandNumber)
2082    {
2083        return IcyBufferedImageUtil.extractChannel(this, bandNumber);
2084    }
2085
2086    /**
2087     * @deprecated Use {@link IcyBufferedImageUtil#extractChannels(IcyBufferedImage, List)} instead
2088     */
2089    @Deprecated
2090    public IcyBufferedImage extractBands(List<Integer> bandNumbers)
2091    {
2092        return IcyBufferedImageUtil.extractChannels(this, bandNumbers);
2093    }
2094
2095    /**
2096     * @deprecated Uses
2097     *             {@link IcyBufferedImageUtil#scale(IcyBufferedImage, int, int, boolean, int, int, IcyBufferedImageUtil.FilterType)}
2098     *             instead.
2099     */
2100    @Deprecated
2101    public IcyBufferedImage getScaledCopy(int width, int height, boolean resizeContent, int xAlign, int yAlign,
2102            FilterType filterType)
2103    {
2104        return IcyBufferedImageUtil.scale(this, width, height, resizeContent, xAlign, yAlign,
2105                getNewFilterType(filterType));
2106    }
2107
2108    /**
2109     * @deprecated Uses
2110     *             {@link IcyBufferedImageUtil#scale(IcyBufferedImage, int, int, boolean, int, int)}
2111     *             instead.
2112     */
2113    @Deprecated
2114    public IcyBufferedImage getScaledCopy(int width, int height, boolean resizeContent, int xAlign, int yAlign)
2115    {
2116        return IcyBufferedImageUtil.scale(this, width, height, resizeContent, xAlign, yAlign);
2117    }
2118
2119    /**
2120     * @deprecated Uses
2121     *             {@link IcyBufferedImageUtil#scale(IcyBufferedImage, int, int, IcyBufferedImageUtil.FilterType)}
2122     *             instead.
2123     */
2124    @Deprecated
2125    public IcyBufferedImage getScaledCopy(int width, int height, FilterType filterType)
2126    {
2127        return IcyBufferedImageUtil.scale(this, width, height, getNewFilterType(filterType));
2128    }
2129
2130    /**
2131     * @deprecated Use {@link IcyBufferedImageUtil#scale(IcyBufferedImage, int, int)} instead.
2132     */
2133    @Deprecated
2134    public IcyBufferedImage getScaledCopy(int width, int height)
2135    {
2136        return IcyBufferedImageUtil.scale(this, width, height);
2137    }
2138
2139    /**
2140     * @deprecated Use {@link IcyBufferedImageUtil#translate(IcyBufferedImage, int, int, int)}
2141     *             instead.
2142     */
2143    @Deprecated
2144    public void translate(int dx, int dy, int channel)
2145    {
2146        IcyBufferedImageUtil.translate(this, dx, dy, channel);
2147    }
2148
2149    /**
2150     * @deprecated Use {@link IcyBufferedImageUtil#translate(IcyBufferedImage, int, int)} instead.
2151     */
2152    @Deprecated
2153    public void translate(int dx, int dy)
2154    {
2155        IcyBufferedImageUtil.translate(this, dx, dy);
2156    }
2157
2158    /**
2159     * Get calculated image channel bounds (min and max values)
2160     */
2161    protected double[] getCalculatedChannelBounds(int channel)
2162    {
2163        // don't load data for that, just wait that data is loaded naturally
2164        if (!isDataInitialized())
2165            return new double[] {0d, 0d};
2166
2167        final DataType dataType = getDataType_();
2168
2169        final boolean signed = dataType.isSigned();
2170        final Object data = getDataXY(channel);
2171
2172        final double min = ArrayMath.min(data, signed);
2173        final double max = ArrayMath.max(data, signed);
2174
2175        return new double[] {min, max};
2176    }
2177
2178    /**
2179     * Adjust specified bounds depending internal data type
2180     */
2181    protected double[] adjustBoundsForDataType(double[] bounds)
2182    {
2183        double min, max;
2184
2185        min = bounds[0];
2186        max = bounds[1];
2187
2188        // only for integer data type
2189        if (!isFloatDataType())
2190        {
2191            // we force min to 0 if > 0
2192            if (min > 0d)
2193                min = 0d;
2194            // we force max to 0 if < 0
2195            if (max < 0d)
2196                max = 0d;
2197        }
2198
2199        final DataType dataType = getDataType_();
2200
2201        switch (dataType.getJavaType())
2202        {
2203            default:
2204            case BYTE:
2205                // return default bounds ([0..255] / [-128..127])
2206                return dataType.getDefaultBounds();
2207
2208            case SHORT:
2209            case INT:
2210            case LONG:
2211                min = MathUtil.prevPow2((long) min + 1);
2212                max = MathUtil.nextPow2Mask((long) max);
2213                break;
2214
2215            case FLOAT:
2216            case DOUBLE:
2217                // if [min..max] is included in [-1..1]
2218                if ((min >= -1d) && (max <= 1d))
2219                {
2220                    min = MathUtil.prevPow10(min);
2221                    max = MathUtil.nextPow10(max);
2222                }
2223                break;
2224        }
2225
2226        return new double[] {min, max};
2227    }
2228
2229    /**
2230     * Get the data type minimum value.
2231     */
2232    public double getDataTypeMin()
2233    {
2234        return getDataType_().getMinValue();
2235    }
2236
2237    /**
2238     * Get the data type maximum value.
2239     */
2240    public double getDataTypeMax()
2241    {
2242        return getDataType_().getMaxValue();
2243    }
2244
2245    /**
2246     * Get data type bounds (min and max values)
2247     */
2248    public double[] getDataTypeBounds()
2249    {
2250        return new double[] {getDataTypeMin(), getDataTypeMax()};
2251    }
2252
2253    /**
2254     * Get the minimum type value for the specified channel.
2255     */
2256    public double getChannelTypeMin(int channel)
2257    {
2258        return getIcyColorModel().getComponentAbsMinValue(channel);
2259    }
2260
2261    /**
2262     * Get the maximum type value for the specified channel.
2263     */
2264    public double getChannelTypeMax(int channel)
2265    {
2266        return getIcyColorModel().getComponentAbsMaxValue(channel);
2267    }
2268
2269    /**
2270     * Get type bounds (min and max values) for the specified channel.
2271     */
2272    public double[] getChannelTypeBounds(int channel)
2273    {
2274        return getIcyColorModel().getComponentAbsBounds(channel);
2275    }
2276
2277    /**
2278     * Get type bounds (min and max values) for all channels.
2279     */
2280    public double[][] getChannelsTypeBounds()
2281    {
2282        final int sizeC = getSizeC();
2283        final double[][] result = new double[sizeC][];
2284
2285        for (int c = 0; c < sizeC; c++)
2286            result[c] = getChannelTypeBounds(c);
2287
2288        return result;
2289    }
2290
2291    /**
2292     * Get global type bounds (min and max values) for all channels.
2293     */
2294    public double[] getChannelsGlobalTypeBounds()
2295    {
2296        final int sizeC = getSizeC();
2297        final double[] result = getChannelTypeBounds(0);
2298
2299        for (int c = 1; c < sizeC; c++)
2300        {
2301            final double[] bounds = getChannelTypeBounds(c);
2302            result[0] = Math.min(bounds[0], result[0]);
2303            result[1] = Math.max(bounds[1], result[1]);
2304        }
2305
2306        return result;
2307    }
2308
2309    /**
2310     * @deprecated Use {@link #getChannelsGlobalTypeBounds()} instead.
2311     */
2312    @Deprecated
2313    public double[] getChannelTypeGlobalBounds()
2314    {
2315        return getChannelsGlobalTypeBounds();
2316    }
2317
2318    /**
2319     * @deprecated Use {@link #getChannelTypeGlobalBounds()} instead.
2320     */
2321    @Deprecated
2322    public double[] getGlobalChannelTypeBounds()
2323    {
2324        return getChannelTypeGlobalBounds();
2325    }
2326
2327    /**
2328     * @deprecated Use {@link #getChannelTypeMin(int)} instead.
2329     */
2330    @Deprecated
2331    public double getComponentAbsMinValue(int component)
2332    {
2333        return getChannelTypeMin(component);
2334    }
2335
2336    /**
2337     * @deprecated Use {@link #getChannelTypeMax(int)} instead.
2338     */
2339    @Deprecated
2340    public double getComponentAbsMaxValue(int component)
2341    {
2342        return getChannelTypeMax(component);
2343    }
2344
2345    /**
2346     * @deprecated Use {@link #getChannelTypeBounds(int)} instead.
2347     */
2348    @Deprecated
2349    public double[] getComponentAbsBounds(int component)
2350    {
2351        return getChannelTypeBounds(component);
2352    }
2353
2354    /**
2355     * @deprecated Use {@link #getChannelsTypeBounds()} instead.
2356     */
2357    @Deprecated
2358    public double[][] getComponentsAbsBounds()
2359    {
2360        return getChannelsTypeBounds();
2361    }
2362
2363    /**
2364     * @deprecated Use {@link #getGlobalChannelTypeBounds()} instead.
2365     */
2366    @Deprecated
2367    public double[] getGlobalComponentAbsBounds()
2368    {
2369        return getChannelTypeGlobalBounds();
2370    }
2371
2372    /**
2373     * Get the minimum value for the specified channel.
2374     */
2375    public double getChannelMin(int channel)
2376    {
2377        return getIcyColorModel().getComponentUserMinValue(channel);
2378    }
2379
2380    /**
2381     * Get maximum value for the specified channel.
2382     */
2383    public double getChannelMax(int channel)
2384    {
2385        return getIcyColorModel().getComponentUserMaxValue(channel);
2386    }
2387
2388    /**
2389     * Get bounds (min and max values) for the specified channel.
2390     */
2391    public double[] getChannelBounds(int channel)
2392    {
2393        return getIcyColorModel().getComponentUserBounds(channel);
2394    }
2395
2396    /**
2397     * Get bounds (min and max values) for all channels.
2398     */
2399    public double[][] getChannelsBounds()
2400    {
2401        final int sizeC = getSizeC();
2402        final double[][] result = new double[sizeC][];
2403
2404        for (int c = 0; c < sizeC; c++)
2405            result[c] = getChannelBounds(c);
2406
2407        return result;
2408    }
2409
2410    /**
2411     * Get global bounds (min and max values) for all channels.
2412     */
2413    public double[] getChannelsGlobalBounds()
2414    {
2415        final int sizeC = getSizeC();
2416        final double[] result = new double[2];
2417
2418        result[0] = Double.MAX_VALUE;
2419        result[1] = -Double.MAX_VALUE;
2420
2421        for (int c = 0; c < sizeC; c++)
2422        {
2423            final double[] bounds = getChannelBounds(c);
2424
2425            if (bounds[0] < result[0])
2426                result[0] = bounds[0];
2427            if (bounds[1] > result[1])
2428                result[1] = bounds[1];
2429        }
2430
2431        return result;
2432    }
2433
2434    /**
2435     * @deprecated Use {@link #getChannelMin(int)} instead.
2436     */
2437    @Deprecated
2438    public double getComponentUserMinValue(int component)
2439    {
2440        return getChannelMin(component);
2441    }
2442
2443    /**
2444     * @deprecated Use {@link #getChannelMax(int)} instead.
2445     */
2446    @Deprecated
2447    public double getComponentUserMaxValue(int component)
2448    {
2449        return getChannelMax(component);
2450    }
2451
2452    /**
2453     * @deprecated Use {@link #getChannelBounds(int)} instead.
2454     */
2455    @Deprecated
2456    public double[] getComponentUserBounds(int component)
2457    {
2458        return getChannelBounds(component);
2459    }
2460
2461    /**
2462     * @deprecated Use {@link #getChannelsBounds()} instead.
2463     */
2464    @Deprecated
2465    public double[][] getComponentsUserBounds()
2466    {
2467        return getChannelsBounds();
2468    }
2469
2470    /**
2471     * Set the preferred data type minimum value for the specified channel.
2472     */
2473    public void setChannelTypeMin(int channel, double min)
2474    {
2475        getIcyColorModel().setComponentAbsMinValue(channel, min);
2476    }
2477
2478    /**
2479     * Set the preferred data type maximum value for the specified channel.
2480     */
2481    public void setChannelTypeMax(int channel, double max)
2482    {
2483        getIcyColorModel().setComponentAbsMaxValue(channel, max);
2484    }
2485
2486    /**
2487     * /**
2488     * Set the preferred data type min and max values for the specified channel.
2489     */
2490    public void setChannelTypeBounds(int channel, double min, double max)
2491    {
2492        getIcyColorModel().setComponentAbsBounds(channel, min, max);
2493    }
2494
2495    /**
2496     * Set the preferred data type bounds (min and max values) for all channels.
2497     */
2498    public void setChannelsTypeBounds(double[][] bounds)
2499    {
2500        getIcyColorModel().setComponentsAbsBounds(bounds);
2501    }
2502
2503    /**
2504     * @deprecated Use {@link #setChannelTypeMin(int, double)} instead.
2505     */
2506    @Deprecated
2507    public void setComponentAbsMinValue(int component, double min)
2508    {
2509        setChannelTypeMin(component, min);
2510    }
2511
2512    /**
2513     * @deprecated Use {@link #setChannelTypeMax(int, double)} instead.
2514     */
2515    @Deprecated
2516    public void setComponentAbsMaxValue(int component, double max)
2517    {
2518        setChannelTypeMax(component, max);
2519    }
2520
2521    /**
2522     * @deprecated Use {@link #setChannelTypeBounds(int, double, double)} instead.
2523     */
2524    @Deprecated
2525    public void setComponentAbsBounds(int component, double[] bounds)
2526    {
2527        setChannelTypeBounds(component, bounds[0], bounds[1]);
2528    }
2529
2530    /**
2531     * @deprecated Use {@link #setChannelTypeBounds(int, double, double)} instead.
2532     */
2533    @Deprecated
2534    public void setComponentAbsBounds(int component, double min, double max)
2535    {
2536        setChannelTypeBounds(component, min, max);
2537    }
2538
2539    /**
2540     * @deprecated Use {@link #setChannelsTypeBounds(double[][])} instead.
2541     */
2542    @Deprecated
2543    public void setComponentsAbsBounds(double[][] bounds)
2544    {
2545        setChannelsTypeBounds(bounds);
2546    }
2547
2548    /**
2549     * Set channel minimum value.
2550     */
2551    public void setChannelMin(int channel, double min)
2552    {
2553        final IcyColorModel cm = getIcyColorModel();
2554
2555        if ((min < cm.getComponentAbsMinValue(channel)))
2556            cm.setComponentAbsMinValue(channel, min);
2557        cm.setComponentUserMinValue(channel, min);
2558    }
2559
2560    /**
2561     * Set channel maximum value.
2562     */
2563    public void setChannelMax(int channel, double max)
2564    {
2565        final IcyColorModel cm = getIcyColorModel();
2566
2567        if ((max > cm.getComponentAbsMaxValue(channel)))
2568            cm.setComponentAbsMinValue(channel, max);
2569        cm.setComponentUserMaxValue(channel, max);
2570    }
2571
2572    /**
2573     * Set channel bounds (min and max values)
2574     */
2575    public void setChannelBounds(int channel, double min, double max)
2576    {
2577        final IcyColorModel cm = getIcyColorModel();
2578        final double[] typeBounds = cm.getComponentAbsBounds(channel);
2579
2580        if ((min < typeBounds[0]) || (max > typeBounds[1]))
2581            cm.setComponentAbsBounds(channel, min, max);
2582        cm.setComponentUserBounds(channel, min, max);
2583    }
2584
2585    /**
2586     * Set all channel bounds (min and max values)
2587     */
2588    public void setChannelsBounds(double[][] bounds)
2589    {
2590        // we use the setChannelBounds(..) method so we do range check
2591        for (int c = 0; c < bounds.length; c++)
2592        {
2593            final double[] b = bounds[c];
2594            setChannelBounds(c, b[0], b[1]);
2595        }
2596    }
2597
2598    /**
2599     * @deprecated Use {@link #setChannelMin(int, double)} instead.
2600     */
2601    @Deprecated
2602    public void setComponentUserMinValue(int component, double min)
2603    {
2604        setChannelMin(component, min);
2605    }
2606
2607    /**
2608     * @deprecated Use {@link #setChannelMax(int, double)} instead.
2609     */
2610    @Deprecated
2611    public void setComponentUserMaxValue(int component, double max)
2612    {
2613        setChannelMax(component, max);
2614    }
2615
2616    /**
2617     * @deprecated Use {@link #setChannelBounds(int, double, double)} instead.
2618     */
2619    @Deprecated
2620    public void setComponentUserBounds(int component, double[] bounds)
2621    {
2622        setChannelBounds(component, bounds[0], bounds[1]);
2623    }
2624
2625    /**
2626     * @deprecated Use {@link #setChannelBounds(int, double, double)} instead
2627     */
2628    @Deprecated
2629    public void setComponentUserBounds(int component, double min, double max)
2630    {
2631        setChannelBounds(component, min, max);
2632    }
2633
2634    /**
2635     * @deprecated Use {@link #setChannelsBounds(double[][])} instead.
2636     */
2637    @Deprecated
2638    public void setComponentsUserBounds(double[][] bounds)
2639    {
2640        setChannelsBounds(bounds);
2641    }
2642
2643    /**
2644     * Update channels bounds (min and max values).
2645     */
2646    public void updateChannelsBounds()
2647    {
2648        final IcyColorModel cm = getIcyColorModel();
2649
2650        if (cm != null)
2651        {
2652            final int sizeC = getSizeC();
2653
2654            for (int c = 0; c < sizeC; c++)
2655            {
2656                // get data type bounds
2657                final double[] bounds = getCalculatedChannelBounds(c);
2658
2659                cm.setComponentAbsBounds(c, adjustBoundsForDataType(bounds));
2660                cm.setComponentUserBounds(c, bounds);
2661
2662                // we do user bounds adjustment on "non ALPHA" component only
2663                // if (cm.getColorMap(c).getType() != IcyColorMapType.ALPHA)
2664                // cm.setComponentUserBounds(c, bounds);
2665            }
2666        }
2667    }
2668
2669    /**
2670     * @deprecated Use {@link #updateChannelsBounds()} instead.
2671     */
2672    @SuppressWarnings("unused")
2673    @Deprecated
2674    public void updateComponentsBounds(boolean updateChannelBounds, boolean adjustByteToo)
2675    {
2676        updateChannelsBounds();
2677    }
2678
2679    /**
2680     * @deprecated Use {@link #updateChannelsBounds()} instead.
2681     */
2682    @SuppressWarnings("unused")
2683    @Deprecated
2684    public void updateComponentsBounds(boolean updateUserBounds)
2685    {
2686        updateChannelsBounds();
2687    }
2688
2689    /**
2690     * Return true if point is inside the image
2691     */
2692    public boolean isInside(Point p)
2693    {
2694        return isInside(p.x, p.y);
2695    }
2696
2697    /**
2698     * Return true if point of coordinate (x, y) is inside the image
2699     */
2700    public boolean isInside(int x, int y)
2701    {
2702        return (x >= 0) && (x < getSizeX()) && (y >= 0) && (y < getSizeY());
2703    }
2704
2705    /**
2706     * Return true if point of coordinate (x, y) is inside the image
2707     */
2708    public boolean isInside(double x, double y)
2709    {
2710        return (x >= 0) && (x < getSizeX()) && (y >= 0) && (y < getSizeY());
2711    }
2712
2713    /**
2714     * Return the IcyColorModel
2715     * 
2716     * @return IcyColorModel
2717     */
2718    public IcyColorModel getIcyColorModel()
2719    {
2720        return (IcyColorModel) getColorModel();
2721    }
2722
2723    /**
2724     * Return the data type of this image
2725     * 
2726     * @return dataType
2727     * @see DataType
2728     */
2729    public DataType getDataType_()
2730    {
2731        return getIcyColorModel().getDataType_();
2732    }
2733
2734    /**
2735     * @deprecated use {@link #getDataType_()} instead
2736     */
2737    @Deprecated
2738    public int getDataType()
2739    {
2740        return getIcyColorModel().getDataType();
2741    }
2742
2743    /**
2744     * Return true if this is a float data type image
2745     */
2746    public boolean isFloatDataType()
2747    {
2748        return getDataType_().isFloat();
2749    }
2750
2751    /**
2752     * Return true if this is a signed data type image
2753     */
2754    public boolean isSignedDataType()
2755    {
2756        return getDataType_().isSigned();
2757    }
2758
2759    /**
2760     * @deprecated Use {@link #getSizeC()} instead.
2761     */
2762    @Deprecated
2763    public int getNumComponents()
2764    {
2765        return getSizeC();
2766    }
2767
2768    /**
2769     * @return the number of components of this image
2770     */
2771    public int getSizeC()
2772    {
2773        return getColorModel().getNumComponents();
2774    }
2775
2776    /**
2777     * @return the width of the image
2778     */
2779    public int getSizeX()
2780    {
2781        return getWidth();
2782    }
2783
2784    /**
2785     * @return the height of the image
2786     */
2787    public int getSizeY()
2788    {
2789        return getHeight();
2790    }
2791
2792    /**
2793     * Return 2D dimension of image {sizeX, sizeY}
2794     */
2795    public Dimension getDimension()
2796    {
2797        return new Dimension(getSizeX(), getSizeY());
2798    }
2799
2800    /**
2801     * Return 2D bounds of image {0, 0, sizeX, sizeY}
2802     */
2803    public Rectangle getBounds()
2804    {
2805        return new Rectangle(getSizeX(), getSizeY());
2806    }
2807
2808    /**
2809     * Return the number of sample.<br>
2810     * This is equivalent to<br>
2811     * <code>getSizeX() * getSizeY() * getSizeC()</code>
2812     */
2813    public int getNumSample()
2814    {
2815        return getSizeX() * getSizeY() * getSizeC();
2816    }
2817
2818    /**
2819     * Return the offset for specified (x, y) location
2820     */
2821    public int getOffset(int x, int y)
2822    {
2823        return (y * getWidth()) + x;
2824    }
2825
2826    /**
2827     * create a compatible LUT for this image.
2828     * 
2829     * @param createColorModel
2830     *        set to <code>true</code> to create a LUT using a new compatible ColorModel else it
2831     *        will use the image
2832     *        internal ColorModel
2833     */
2834    public LUT createCompatibleLUT(boolean createColorModel)
2835    {
2836        final IcyColorModel cm;
2837
2838        if (createColorModel)
2839            cm = IcyColorModel.createInstance(getIcyColorModel(), false, false);
2840        else
2841            cm = getIcyColorModel();
2842
2843        return new LUT(cm);
2844    }
2845
2846    /**
2847     * create a compatible LUT for this image
2848     */
2849    public LUT createCompatibleLUT()
2850    {
2851        return createCompatibleLUT(true);
2852    }
2853
2854    /**
2855     * @deprecated No attached LUT to an image.<br/>
2856     *             Use {@link #createCompatibleLUT(boolean)} instead.
2857     */
2858    @Deprecated
2859    public LUT getLUT()
2860    {
2861        return createCompatibleLUT();
2862    }
2863
2864    /**
2865     * Return a direct reference to internal 2D array data [C][XY]
2866     */
2867    public Object getDataXYC()
2868    {
2869        switch (getDataType_().getJavaType())
2870        {
2871            case BYTE:
2872                return getDataXYCAsByte();
2873            case SHORT:
2874                return getDataXYCAsShort();
2875            case INT:
2876                return getDataXYCAsInt();
2877            case FLOAT:
2878                return getDataXYCAsFloat();
2879            case DOUBLE:
2880                return getDataXYCAsDouble();
2881            default:
2882                return null;
2883        }
2884    }
2885
2886    /**
2887     * Return a direct reference to internal 1D array data [XY] for specified c
2888     */
2889    public Object getDataXY(int c)
2890    {
2891        switch (getDataType_().getJavaType())
2892        {
2893            case BYTE:
2894                return getDataXYAsByte(c);
2895            case SHORT:
2896                return getDataXYAsShort(c);
2897            case INT:
2898                return getDataXYAsInt(c);
2899            case FLOAT:
2900                return getDataXYAsFloat(c);
2901            case DOUBLE:
2902                return getDataXYAsDouble(c);
2903            default:
2904                return null;
2905        }
2906    }
2907
2908    /**
2909     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
2910     */
2911    public Object getDataCopyXYC()
2912    {
2913        return getDataCopyXYC(null, 0);
2914    }
2915
2916    /**
2917     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]<br>
2918     * If (out != null) then it's used to store result at the specified offset
2919     */
2920    public Object getDataCopyXYC(Object out, int offset)
2921    {
2922        switch (getDataType_().getJavaType())
2923        {
2924            case BYTE:
2925                return getDataCopyXYCAsByte((byte[]) out, offset);
2926            case SHORT:
2927                return getDataCopyXYCAsShort((short[]) out, offset);
2928            case INT:
2929                return getDataCopyXYCAsInt((int[]) out, offset);
2930            case FLOAT:
2931                return getDataCopyXYCAsFloat((float[]) out, offset);
2932            case DOUBLE:
2933                return getDataCopyXYCAsDouble((double[]) out, offset);
2934            default:
2935                return null;
2936        }
2937    }
2938
2939    /**
2940     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c
2941     */
2942    public Object getDataCopyXY(int c)
2943    {
2944        return getDataCopyXY(c, null, 0);
2945    }
2946
2947    /**
2948     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
2949     * If (out != null) then it's used to store result at the specified offset
2950     */
2951    public Object getDataCopyXY(int c, Object out, int offset)
2952    {
2953        switch (getDataType_().getJavaType())
2954        {
2955            case BYTE:
2956                return getDataCopyXYAsByte(c, (byte[]) out, offset);
2957            case SHORT:
2958                return getDataCopyXYAsShort(c, (short[]) out, offset);
2959            case INT:
2960                return getDataCopyXYAsInt(c, (int[]) out, offset);
2961            case FLOAT:
2962                return getDataCopyXYAsFloat(c, (float[]) out, offset);
2963            case DOUBLE:
2964                return getDataCopyXYAsDouble(c, (double[]) out, offset);
2965            default:
2966                return null;
2967        }
2968    }
2969
2970    /**
2971     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]
2972     */
2973    public Object getDataCopyCXY()
2974    {
2975        return getDataCopyCXY(null, 0);
2976    }
2977
2978    /**
2979     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
2980     * If (out != null) then it's used to store result at the specified offset
2981     */
2982    public Object getDataCopyCXY(Object out, int offset)
2983    {
2984        switch (getDataType_().getJavaType())
2985        {
2986            case BYTE:
2987                return getDataCopyCXYAsByte((byte[]) out, offset);
2988            case SHORT:
2989                return getDataCopyCXYAsShort((short[]) out, offset);
2990            case INT:
2991                return getDataCopyCXYAsInt((int[]) out, offset);
2992            case FLOAT:
2993                return getDataCopyCXYAsFloat((float[]) out, offset);
2994            case DOUBLE:
2995                return getDataCopyCXYAsDouble((double[]) out, offset);
2996            default:
2997                return null;
2998        }
2999
3000    }
3001
3002    /**
3003     * Return a 1D array data copy [C] of specified (x, y) position
3004     */
3005    public Object getDataCopyC(int x, int y)
3006    {
3007        return getDataCopyC(x, y, null, 0);
3008    }
3009
3010    /**
3011     * Return a 1D array data copy [C] of specified (x, y) position<br>
3012     * If (out != null) then it's used to store result at the specified offset
3013     */
3014    public Object getDataCopyC(int x, int y, Object out, int offset)
3015    {
3016        switch (getDataType_().getJavaType())
3017        {
3018            case BYTE:
3019                return getDataCopyCAsByte(x, y, (byte[]) out, offset);
3020            case SHORT:
3021                return getDataCopyCAsShort(x, y, (short[]) out, offset);
3022            case INT:
3023                return getDataCopyCAsInt(x, y, (int[]) out, offset);
3024            case FLOAT:
3025                return getDataCopyCAsFloat(x, y, (float[]) out, offset);
3026            case DOUBLE:
3027                return getDataCopyCAsDouble(x, y, (double[]) out, offset);
3028            default:
3029                return null;
3030        }
3031    }
3032
3033    /**
3034     * Set internal 1D byte array data ([XY]) for specified component
3035     */
3036    public void setDataXY(int c, Object values)
3037    {
3038        lockRaster();
3039        try
3040        {
3041            ArrayUtil.arrayToArray(values, getDataXY(c), getDataType_().isSigned());
3042        }
3043        finally
3044        {
3045            releaseRaster(true);
3046        }
3047
3048        // notify data changed
3049        dataChanged();
3050    }
3051
3052    /**
3053     * Set 1D array data [C] of specified (x, y) position
3054     */
3055    public void setDataC(int x, int y, Object values)
3056    {
3057        switch (getDataType_().getJavaType())
3058        {
3059            case BYTE:
3060                setDataCAsByte(x, y, (byte[]) values);
3061                break;
3062
3063            case SHORT:
3064                setDataCAsShort(x, y, (short[]) values);
3065                break;
3066
3067            case INT:
3068                setDataCAsInt(x, y, (int[]) values);
3069                break;
3070
3071            case FLOAT:
3072                setDataCAsFloat(x, y, (float[]) values);
3073                break;
3074
3075            case DOUBLE:
3076                setDataCAsDouble(x, y, (double[]) values);
3077                break;
3078
3079            default:
3080                // nothing here
3081                break;
3082        }
3083    }
3084
3085    /**
3086     * Return a direct reference to internal 2D array data [C][XY]
3087     */
3088    public byte[][] getDataXYCAsByte()
3089    {
3090        return ((DataBufferByte) getRaster().getDataBuffer()).getBankData();
3091    }
3092
3093    /**
3094     * Return a direct reference to internal 2D array data [C][XY]
3095     */
3096    public short[][] getDataXYCAsShort()
3097    {
3098        final DataBuffer db = getRaster().getDataBuffer();
3099        if (db instanceof DataBufferUShort)
3100            return ((DataBufferUShort) db).getBankData();
3101        return ((DataBufferShort) db).getBankData();
3102    }
3103
3104    /**
3105     * Return a direct reference to internal 2D array data [C][XY]
3106     */
3107    public int[][] getDataXYCAsInt()
3108    {
3109        return ((DataBufferInt) getRaster().getDataBuffer()).getBankData();
3110    }
3111
3112    /**
3113     * Return a direct reference to internal 2D array data [C][XY]
3114     */
3115    public float[][] getDataXYCAsFloat()
3116    {
3117        return ((DataBufferFloat) getRaster().getDataBuffer()).getBankData();
3118    }
3119
3120    /**
3121     * Return a direct reference to internal 2D array data [C][XY]
3122     */
3123    public double[][] getDataXYCAsDouble()
3124    {
3125        return ((DataBufferDouble) getRaster().getDataBuffer()).getBankData();
3126    }
3127
3128    /**
3129     * Return a direct reference to internal 1D array data [XY] for specified c
3130     */
3131    public byte[] getDataXYAsByte(int c)
3132    {
3133        return ((DataBufferByte) getRaster().getDataBuffer()).getData(c);
3134    }
3135
3136    /**
3137     * Return a direct reference to internal 1D array data [XY] for specified c
3138     */
3139    public short[] getDataXYAsShort(int c)
3140    {
3141        final DataBuffer db = getRaster().getDataBuffer();
3142        if (db instanceof DataBufferUShort)
3143            return ((DataBufferUShort) db).getData(c);
3144        return ((DataBufferShort) db).getData(c);
3145    }
3146
3147    /**
3148     * Return a direct reference to internal 1D array data [XY] for specified c
3149     */
3150    public int[] getDataXYAsInt(int c)
3151    {
3152        return ((DataBufferInt) getRaster().getDataBuffer()).getData(c);
3153    }
3154
3155    /**
3156     * Return a direct reference to internal 1D array data [XY] for specified c
3157     */
3158    public float[] getDataXYAsFloat(int c)
3159    {
3160        return ((DataBufferFloat) getRaster().getDataBuffer()).getData(c);
3161    }
3162
3163    /**
3164     * Return a direct reference to internal 1D array data [XY] for specified c
3165     */
3166    public double[] getDataXYAsDouble(int c)
3167    {
3168        return ((DataBufferDouble) getRaster().getDataBuffer()).getData(c);
3169    }
3170
3171    /**
3172     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
3173     */
3174    public byte[] getDataCopyXYCAsByte()
3175    {
3176        return getDataCopyXYCAsByte(null, 0);
3177    }
3178
3179    /**
3180     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
3181     * it's used to store result at the specified offset
3182     */
3183    public byte[] getDataCopyXYCAsByte(byte[] out, int off)
3184    {
3185        final long sizeC = getSizeC();
3186        final long len = (long) getSizeX() * (long) getSizeY();
3187        if ((len * sizeC) >= Integer.MAX_VALUE)
3188            throw new TooLargeArrayException();
3189
3190        final byte[][] banks = ((DataBufferByte) getRaster().getDataBuffer()).getBankData();
3191        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3192        int offset = off;
3193
3194        for (int c = 0; c < sizeC; c++)
3195        {
3196            final byte[] src = banks[c];
3197            System.arraycopy(src, 0, result, offset, (int) len);
3198            offset += len;
3199        }
3200
3201        return result;
3202    }
3203
3204    /**
3205     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
3206     */
3207    public short[] getDataCopyXYCAsShort()
3208    {
3209        return getDataCopyXYCAsShort(null, 0);
3210    }
3211
3212    /**
3213     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
3214     * it's used to store result at the specified offset
3215     */
3216    public short[] getDataCopyXYCAsShort(short[] out, int off)
3217    {
3218        final long sizeC = getSizeC();
3219        final long len = (long) getSizeX() * (long) getSizeY();
3220        if ((len * sizeC) >= Integer.MAX_VALUE)
3221            throw new TooLargeArrayException();
3222
3223        final DataBuffer db = getRaster().getDataBuffer();
3224        final short[][] banks;
3225        if (db instanceof DataBufferUShort)
3226            banks = ((DataBufferUShort) db).getBankData();
3227        else
3228            banks = ((DataBufferShort) db).getBankData();
3229        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3230        int offset = off;
3231
3232        for (int c = 0; c < sizeC; c++)
3233        {
3234            final short[] src = banks[c];
3235            System.arraycopy(src, 0, result, offset, (int) len);
3236            offset += len;
3237        }
3238
3239        return result;
3240    }
3241
3242    /**
3243     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
3244     */
3245    public int[] getDataCopyXYCAsInt()
3246    {
3247        return getDataCopyXYCAsInt(null, 0);
3248    }
3249
3250    /**
3251     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
3252     * it's used to store result at the specified offset
3253     */
3254    public int[] getDataCopyXYCAsInt(int[] out, int off)
3255    {
3256        final long sizeC = getSizeC();
3257        final long len = (long) getSizeX() * (long) getSizeY();
3258        if ((len * sizeC) >= Integer.MAX_VALUE)
3259            throw new TooLargeArrayException();
3260
3261        final int[][] banks = ((DataBufferInt) getRaster().getDataBuffer()).getBankData();
3262        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3263        int offset = off;
3264
3265        for (int c = 0; c < sizeC; c++)
3266        {
3267            final int[] src = banks[c];
3268            System.arraycopy(src, 0, result, offset, (int) len);
3269            offset += len;
3270        }
3271
3272        return result;
3273    }
3274
3275    /**
3276     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
3277     */
3278    public float[] getDataCopyXYCAsFloat()
3279    {
3280        return getDataCopyXYCAsFloat(null, 0);
3281    }
3282
3283    /**
3284     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
3285     * it's used to store result at the specified offset
3286     */
3287    public float[] getDataCopyXYCAsFloat(float[] out, int off)
3288    {
3289        final long sizeC = getSizeC();
3290        final long len = (long) getSizeX() * (long) getSizeY();
3291        if ((len * sizeC) >= Integer.MAX_VALUE)
3292            throw new TooLargeArrayException();
3293
3294        final float[][] banks = ((DataBufferFloat) getRaster().getDataBuffer()).getBankData();
3295        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3296        int offset = off;
3297
3298        for (int c = 0; c < sizeC; c++)
3299        {
3300            final float[] src = banks[c];
3301            System.arraycopy(src, 0, result, offset, (int) len);
3302            offset += len;
3303        }
3304
3305        return result;
3306    }
3307
3308    /**
3309     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY]
3310     */
3311    public double[] getDataCopyXYCAsDouble()
3312    {
3313        return getDataCopyXYCAsDouble(null, 0);
3314    }
3315
3316    /**
3317     * Return a 1D array data copy [XYC] of internal 2D array data [C][XY] If (out != null) then
3318     * it's used to store result at the specified offset
3319     */
3320    public double[] getDataCopyXYCAsDouble(double[] out, int off)
3321    {
3322        final long sizeC = getSizeC();
3323        final long len = (long) getSizeX() * (long) getSizeY();
3324        if ((len * sizeC) >= Integer.MAX_VALUE)
3325            throw new TooLargeArrayException();
3326
3327        final double[][] banks = ((DataBufferDouble) getRaster().getDataBuffer()).getBankData();
3328        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3329        int offset = off;
3330
3331        for (int c = 0; c < sizeC; c++)
3332        {
3333            final double[] src = banks[c];
3334            System.arraycopy(src, 0, result, offset, (int) len);
3335            offset += len;
3336        }
3337
3338        return result;
3339    }
3340
3341    /**
3342     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3343     */
3344    public byte[] getDataCopyXYAsByte(int c)
3345    {
3346        return getDataCopyXYAsByte(c, null, 0);
3347    }
3348
3349    /**
3350     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3351     * If (out != null) then it's used to store result at the specified offset
3352     */
3353    public byte[] getDataCopyXYAsByte(int c, byte[] out, int off)
3354    {
3355        final int len = getSizeX() * getSizeY();
3356        final byte[] src = ((DataBufferByte) getRaster().getDataBuffer()).getData(c);
3357        final byte[] result = Array1DUtil.allocIfNull(out, len);
3358
3359        System.arraycopy(src, 0, result, off, len);
3360
3361        return result;
3362    }
3363
3364    /**
3365     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3366     */
3367    public short[] getDataCopyXYAsShort(int c)
3368    {
3369        return getDataCopyXYAsShort(c, null, 0);
3370    }
3371
3372    /**
3373     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3374     * If (out != null) then it's used to store result at the specified offset
3375     */
3376    public short[] getDataCopyXYAsShort(int c, short[] out, int off)
3377    {
3378        final int len = getSizeX() * getSizeY();
3379        final DataBuffer db = getRaster().getDataBuffer();
3380        final short[] src;
3381        if (db instanceof DataBufferUShort)
3382            src = ((DataBufferUShort) db).getData(c);
3383        else
3384            src = ((DataBufferShort) db).getData(c);
3385        final short[] result = Array1DUtil.allocIfNull(out, len);
3386
3387        System.arraycopy(src, 0, result, off, len);
3388
3389        return result;
3390    }
3391
3392    /**
3393     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3394     */
3395    public int[] getDataCopyXYAsInt(int c)
3396    {
3397        return getDataCopyXYAsInt(c, null, 0);
3398    }
3399
3400    /**
3401     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3402     * If (out != null) then it's used to store result at the specified offset
3403     */
3404    public int[] getDataCopyXYAsInt(int c, int[] out, int off)
3405    {
3406        final int len = getSizeX() * getSizeY();
3407        final int[] src = ((DataBufferInt) getRaster().getDataBuffer()).getData(c);
3408        final int[] result = Array1DUtil.allocIfNull(out, len);
3409
3410        System.arraycopy(src, 0, result, off, len);
3411
3412        return result;
3413    }
3414
3415    /**
3416     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3417     */
3418    public float[] getDataCopyXYAsFloat(int c)
3419    {
3420        return getDataCopyXYAsFloat(c, null, 0);
3421    }
3422
3423    /**
3424     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3425     * If (out != null) then it's used to store result at the specified offset
3426     */
3427    public float[] getDataCopyXYAsFloat(int c, float[] out, int off)
3428    {
3429        final int len = getSizeX() * getSizeY();
3430        final float[] src = ((DataBufferFloat) getRaster().getDataBuffer()).getData(c);
3431        final float[] result = Array1DUtil.allocIfNull(out, len);
3432
3433        System.arraycopy(src, 0, result, off, len);
3434
3435        return result;
3436    }
3437
3438    /**
3439     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3440     */
3441    public double[] getDataCopyXYAsDouble(int c)
3442    {
3443        return getDataCopyXYAsDouble(c, null, 0);
3444    }
3445
3446    /**
3447     * Return a 1D array data copy [XY] of internal 1D array data [XY] for specified c<br>
3448     * If (out != null) then it's used to store result at the specified offset
3449     */
3450    public double[] getDataCopyXYAsDouble(int c, double[] out, int off)
3451    {
3452        final int len = getSizeX() * getSizeY();
3453        final double[] src = ((DataBufferDouble) getRaster().getDataBuffer()).getData(c);
3454        final double[] result = Array1DUtil.allocIfNull(out, len);
3455
3456        System.arraycopy(src, 0, result, off, len);
3457
3458        return result;
3459    }
3460
3461    /**
3462     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3463     */
3464    public byte[] getDataCopyCXYAsByte()
3465    {
3466        return getDataCopyCXYAsByte(null, 0);
3467    }
3468
3469    /**
3470     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3471     * If (out != null) then it's used to store result at the specified offset
3472     */
3473    public byte[] getDataCopyCXYAsByte(byte[] out, int off)
3474    {
3475        final long sizeC = getSizeC();
3476        final long len = (long) getSizeX() * (long) getSizeY();
3477        if ((len * sizeC) >= Integer.MAX_VALUE)
3478            throw new TooLargeArrayException();
3479
3480        final byte[][] banks = ((DataBufferByte) getRaster().getDataBuffer()).getBankData();
3481        final byte[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3482
3483        for (int c = 0; c < sizeC; c++)
3484        {
3485            final byte[] src = banks[c];
3486            int offset = c + off;
3487            for (int i = 0; i < len; i++, offset += sizeC)
3488                result[offset] = src[i];
3489        }
3490
3491        return result;
3492    }
3493
3494    /**
3495     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3496     */
3497    public short[] getDataCopyCXYAsShort()
3498    {
3499        return getDataCopyCXYAsShort(null, 0);
3500    }
3501
3502    /**
3503     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3504     * If (out != null) then it's used to store result at the specified offset
3505     */
3506    public short[] getDataCopyCXYAsShort(short[] out, int off)
3507    {
3508        final long sizeC = getSizeC();
3509        final long len = (long) getSizeX() * (long) getSizeY();
3510        if ((len * sizeC) >= Integer.MAX_VALUE)
3511            throw new TooLargeArrayException();
3512
3513        final DataBuffer db = getRaster().getDataBuffer();
3514        final short[][] banks;
3515        if (db instanceof DataBufferUShort)
3516            banks = ((DataBufferUShort) db).getBankData();
3517        else
3518            banks = ((DataBufferShort) db).getBankData();
3519        final short[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3520
3521        for (int c = 0; c < sizeC; c++)
3522        {
3523            final short[] src = banks[c];
3524            int offset = c + off;
3525            for (int i = 0; i < len; i++, offset += sizeC)
3526                result[offset] = src[i];
3527        }
3528
3529        return result;
3530    }
3531
3532    /**
3533     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3534     */
3535    public int[] getDataCopyCXYAsInt()
3536    {
3537        return getDataCopyCXYAsInt(null, 0);
3538    }
3539
3540    /**
3541     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3542     * If (out != null) then it's used to store result at the specified offset
3543     */
3544    public int[] getDataCopyCXYAsInt(int[] out, int off)
3545    {
3546        final long sizeC = getSizeC();
3547        final long len = (long) getSizeX() * (long) getSizeY();
3548        if ((len * sizeC) >= Integer.MAX_VALUE)
3549            throw new TooLargeArrayException();
3550
3551        final int[][] banks = ((DataBufferInt) getRaster().getDataBuffer()).getBankData();
3552        final int[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3553
3554        for (int c = 0; c < sizeC; c++)
3555        {
3556            final int[] src = banks[c];
3557            int offset = c + off;
3558            for (int i = 0; i < len; i++, offset += sizeC)
3559                result[offset] = src[i];
3560        }
3561
3562        return result;
3563    }
3564
3565    /**
3566     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3567     */
3568    public float[] getDataCopyCXYAsFloat()
3569    {
3570        return getDataCopyCXYAsFloat(null, 0);
3571    }
3572
3573    /**
3574     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3575     * If (out != null) then it's used to store result at the specified offset
3576     */
3577    public float[] getDataCopyCXYAsFloat(float[] out, int off)
3578    {
3579        final long sizeC = getSizeC();
3580        final long len = (long) getSizeX() * (long) getSizeY();
3581        if ((len * sizeC) >= Integer.MAX_VALUE)
3582            throw new TooLargeArrayException();
3583
3584        final float[][] banks = ((DataBufferFloat) getRaster().getDataBuffer()).getBankData();
3585        final float[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3586
3587        for (int c = 0; c < sizeC; c++)
3588        {
3589            final float[] src = banks[c];
3590            int offset = c + off;
3591            for (int i = 0; i < len; i++, offset += sizeC)
3592                result[offset] = src[i];
3593        }
3594
3595        return result;
3596    }
3597
3598    /**
3599     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3600     */
3601    public double[] getDataCopyCXYAsDouble()
3602    {
3603        return getDataCopyCXYAsDouble(null, 0);
3604    }
3605
3606    /**
3607     * Return a 1D array data copy [CXY] of internal 2D array data [C][XY]<br>
3608     * If (out != null) then it's used to store result at the specified offset
3609     */
3610    public double[] getDataCopyCXYAsDouble(double[] out, int off)
3611    {
3612        final long sizeC = getSizeC();
3613        final long len = (long) getSizeX() * (long) getSizeY();
3614        if ((len * sizeC) >= Integer.MAX_VALUE)
3615            throw new TooLargeArrayException();
3616
3617        final double[][] banks = ((DataBufferDouble) getRaster().getDataBuffer()).getBankData();
3618        final double[] result = Array1DUtil.allocIfNull(out, (int) (len * sizeC));
3619
3620        for (int c = 0; c < sizeC; c++)
3621        {
3622            final double[] src = banks[c];
3623            int offset = c + off;
3624            for (int i = 0; i < len; i++, offset += sizeC)
3625                result[offset] = src[i];
3626        }
3627
3628        return result;
3629    }
3630
3631    /**
3632     * Return a 1D array data copy [C] of specified (x, y) position
3633     */
3634    public byte[] getDataCopyCAsByte(int x, int y)
3635    {
3636        return getDataCopyCAsByte(x, y, null, 0);
3637    }
3638
3639    /**
3640     * Return a 1D array data copy [C] of specified (x, y) position<br>
3641     * If (out != null) then it's used to store result at the specified offset
3642     */
3643    public byte[] getDataCopyCAsByte(int x, int y, byte[] out, int off)
3644    {
3645        final int sizeC = getSizeC();
3646        final int offset = x + (y * getWidth());
3647        final byte[][] data = ((DataBufferByte) getRaster().getDataBuffer()).getBankData();
3648        final byte[] result = Array1DUtil.allocIfNull(out, sizeC);
3649
3650        for (int c = 0; c < sizeC; c++)
3651            // ignore band offset as it's always 0 here
3652            result[c + off] = data[c][offset];
3653
3654        return result;
3655    }
3656
3657    /**
3658     * Return a 1D array data copy [C] of specified (x, y) position
3659     */
3660    public short[] getDataCopyCAsShort(int x, int y)
3661    {
3662        return getDataCopyCAsShort(x, y, null, 0);
3663    }
3664
3665    /**
3666     * Return a 1D array data copy [C] of specified (x, y) position<br>
3667     * If (out != null) then it's used to store result at the specified offset
3668     */
3669    public short[] getDataCopyCAsShort(int x, int y, short[] out, int off)
3670    {
3671        final int sizeC = getSizeC();
3672        final int offset = x + (y * getWidth());
3673        final DataBuffer db = getRaster().getDataBuffer();
3674        final short[][] data;
3675        if (db instanceof DataBufferUShort)
3676            data = ((DataBufferUShort) db).getBankData();
3677        else
3678            data = ((DataBufferShort) db).getBankData();
3679        final short[] result = Array1DUtil.allocIfNull(out, sizeC);
3680
3681        for (int c = 0; c < sizeC; c++)
3682            // ignore band offset as it's always 0 here
3683            result[c + off] = data[c][offset];
3684
3685        return result;
3686    }
3687
3688    /**
3689     * Return a 1D array data copy [C] of specified (x, y) position
3690     */
3691    public int[] getDataCopyCAsInt(int x, int y)
3692    {
3693        return getDataCopyCAsInt(x, y, null, 0);
3694    }
3695
3696    /**
3697     * Return a 1D array data copy [C] of specified (x, y) position<br>
3698     * If (out != null) then it's used to store result at the specified offset
3699     */
3700    public int[] getDataCopyCAsInt(int x, int y, int[] out, int off)
3701    {
3702        final int sizeC = getSizeC();
3703        final int offset = x + (y * getWidth());
3704        final int[][] data = ((DataBufferInt) getRaster().getDataBuffer()).getBankData();
3705        final int[] result = Array1DUtil.allocIfNull(out, sizeC);
3706
3707        for (int c = 0; c < sizeC; c++)
3708            // ignore band offset as it's always 0 here
3709            result[c + off] = data[c][offset];
3710
3711        return result;
3712    }
3713
3714    /**
3715     * Return a 1D array data copy [C] of specified (x, y) position
3716     */
3717    public float[] getDataCopyCAsFloat(int x, int y)
3718    {
3719        return getDataCopyCAsFloat(x, y, null, 0);
3720    }
3721
3722    /**
3723     * Return a 1D array data copy [C] of specified (x, y) position<br>
3724     * If (out != null) then it's used to store result at the specified offset
3725     */
3726    public float[] getDataCopyCAsFloat(int x, int y, float[] out, int off)
3727    {
3728        final int sizeC = getSizeC();
3729        final int offset = x + (y * getWidth());
3730        final float[][] data = ((DataBufferFloat) getRaster().getDataBuffer()).getBankData();
3731        final float[] result = Array1DUtil.allocIfNull(out, sizeC);
3732
3733        for (int c = 0; c < sizeC; c++)
3734            // ignore band offset as it's always 0 here
3735            result[c + off] = data[c][offset];
3736
3737        return result;
3738    }
3739
3740    /**
3741     * Return a 1D array data copy [C] of specified (x, y) position
3742     */
3743    public double[] getDataCopyCAsDouble(int x, int y)
3744    {
3745        return getDataCopyCAsDouble(x, y, null, 0);
3746    }
3747
3748    /**
3749     * Return a 1D array data copy [C] of specified (x, y) position<br>
3750     * If (out != null) then it's used to store result at the specified offset
3751     */
3752    public double[] getDataCopyCAsDouble(int x, int y, double[] out, int off)
3753    {
3754        final int sizeC = getSizeC();
3755        final int offset = x + (y * getWidth());
3756        final double[][] data = ((DataBufferDouble) getRaster().getDataBuffer()).getBankData();
3757        final double[] result = Array1DUtil.allocIfNull(out, sizeC);
3758
3759        for (int c = 0; c < sizeC; c++)
3760            // ignore band offset as it's always 0 here
3761            result[c + off] = data[c][offset];
3762
3763        return result;
3764    }
3765
3766    /**
3767     * Set internal 1D byte array data ([XY]) for specified component
3768     */
3769    public void setDataXYAsByte(int c, byte[] values)
3770    {
3771        lockRaster();
3772        try
3773        {
3774            System.arraycopy(values, 0, getDataXYAsByte(c), 0, getSizeX() * getSizeY());
3775        }
3776        finally
3777        {
3778            releaseRaster(true);
3779        }
3780
3781        // notify data changed
3782        dataChanged();
3783    }
3784
3785    /**
3786     * Set internal 1D byte array data ([XY]) for specified component
3787     */
3788    public void setDataXYAsShort(int c, short[] values)
3789    {
3790        lockRaster();
3791        try
3792        {
3793            System.arraycopy(values, 0, getDataXYAsShort(c), 0, getSizeX() * getSizeY());
3794        }
3795        finally
3796        {
3797            releaseRaster(true);
3798        }
3799
3800        // notify data changed
3801        dataChanged();
3802    }
3803
3804    /**
3805     * Set internal 1D byte array data ([XY]) for specified component
3806     */
3807    public void setDataXYAsInt(int c, int[] values)
3808    {
3809        lockRaster();
3810        try
3811        {
3812            System.arraycopy(values, 0, getDataXYAsInt(c), 0, getSizeX() * getSizeY());
3813        }
3814        finally
3815        {
3816            releaseRaster(true);
3817        }
3818
3819        // notify data changed
3820        dataChanged();
3821    }
3822
3823    /**
3824     * Set internal 1D byte array data ([XY]) for specified component
3825     */
3826    public void setDataXYAsFloat(int c, float[] values)
3827    {
3828        lockRaster();
3829        try
3830        {
3831            System.arraycopy(values, 0, getDataXYAsFloat(c), 0, getSizeX() * getSizeY());
3832        }
3833        finally
3834        {
3835            releaseRaster(true);
3836        }
3837
3838        // notify data changed
3839        dataChanged();
3840    }
3841
3842    /**
3843     * Set internal 1D byte array data ([XY]) for specified component
3844     */
3845    public void setDataXYAsDouble(int c, double[] values)
3846    {
3847        lockRaster();
3848        try
3849        {
3850            System.arraycopy(values, 0, getDataXYAsDouble(c), 0, getSizeX() * getSizeY());
3851        }
3852        finally
3853        {
3854            releaseRaster(true);
3855        }
3856
3857        // notify data changed
3858        dataChanged();
3859    }
3860
3861    /**
3862     * Set 1D array data [C] of specified (x, y) position
3863     */
3864    public void setDataCAsByte(int x, int y, byte[] values)
3865    {
3866        final int offset = x + (y * getWidth());
3867        final int len = values.length;
3868        final WritableRaster wr = getRaster();
3869        final byte[][] data = ((DataBufferByte) wr.getDataBuffer()).getBankData();
3870
3871        for (int comp = 0; comp < len; comp++)
3872            // ignore band offset as it's always 0 here
3873            data[comp][offset] = values[comp];
3874
3875        // save changed data in cache (need to do cache behind here and still that is terribly slow !!)
3876        saveRasterInCache(wr);
3877        // notify data changed
3878        dataChanged();
3879    }
3880
3881    /**
3882     * Set 1D array data [C] of specified (x, y) position
3883     */
3884    public void setDataCAsShort(int x, int y, short[] values)
3885    {
3886        final int offset = x + (y * getWidth());
3887        final int len = values.length;
3888        final WritableRaster wr = getRaster();
3889        final DataBuffer db = wr.getDataBuffer();
3890        final short[][] data;
3891        if (db instanceof DataBufferUShort)
3892            data = ((DataBufferUShort) db).getBankData();
3893        else
3894            data = ((DataBufferShort) db).getBankData();
3895
3896        for (int comp = 0; comp < len; comp++)
3897            // ignore band offset as it's always 0 here
3898            data[comp][offset] = values[comp];
3899
3900        // save changed data in cache (need to do cache behind here and still that is terribly slow !!)
3901        saveRasterInCache(wr);
3902        // notify data changed
3903        dataChanged();
3904    }
3905
3906    /**
3907     * Set 1D array data [C] of specified (x, y) position
3908     */
3909    public void setDataCAsInt(int x, int y, int[] values)
3910    {
3911        final int offset = x + (y * getWidth());
3912        final int len = values.length;
3913        final WritableRaster wr = getRaster();
3914        final int[][] data = ((DataBufferInt) wr.getDataBuffer()).getBankData();
3915
3916        for (int comp = 0; comp < len; comp++)
3917            // ignore band offset as it's always 0 here
3918            data[comp][offset] = values[comp];
3919
3920        // save changed data in cache (need to do cache behind here and still that is terribly slow !!)
3921        saveRasterInCache(wr);
3922        // notify data changed
3923        dataChanged();
3924    }
3925
3926    /**
3927     * Set 1D array data [C] of specified (x, y) position
3928     */
3929    public void setDataCAsFloat(int x, int y, float[] values)
3930    {
3931        final int offset = x + (y * getWidth());
3932        final int len = values.length;
3933        final WritableRaster wr = getRaster();
3934        final float[][] data = ((DataBufferFloat) wr.getDataBuffer()).getBankData();
3935
3936        for (int comp = 0; comp < len; comp++)
3937            // ignore band offset as it's always 0 here
3938            data[comp][offset] = values[comp];
3939
3940        // save changed data in cache (need to do cache behind here and still that is terribly slow !!)
3941        saveRasterInCache(wr);
3942        // notify data changed
3943        dataChanged();
3944    }
3945
3946    /**
3947     * Set 1D array data [C] of specified (x, y) position
3948     */
3949    public void setDataCAsDouble(int x, int y, double[] values)
3950    {
3951        final int offset = x + (y * getWidth());
3952        final int len = values.length;
3953        final WritableRaster wr = getRaster();
3954        final double[][] data = ((DataBufferDouble) wr.getDataBuffer()).getBankData();
3955
3956        for (int comp = 0; comp < len; comp++)
3957            // ignore band offset as it's always 0 here
3958            data[comp][offset] = values[comp];
3959
3960        // save changed data in cache (need to do cache behind here and still that is terribly slow !!)
3961        saveRasterInCache(wr);
3962        // notify data changed
3963        dataChanged();
3964    }
3965
3966    /**
3967     * Return the value located at (x, y, c) position as a double
3968     * whatever is the internal data type
3969     */
3970    public double getData(int x, int y, int c)
3971    {
3972        return Array1DUtil.getValue(getDataXY(c), getOffset(x, y), getDataType_());
3973    }
3974
3975    /**
3976     * Set the value located at (x, y, c) position as a double
3977     * whatever is the internal data type
3978     */
3979    public void setData(int x, int y, int c, double value)
3980    {
3981        lockRaster();
3982        try
3983        {
3984            // set value
3985            Array1DUtil.setValue(getDataXY(c), getOffset(x, y), getDataType_(), value);
3986        }
3987        finally
3988        {
3989            // FIXME : save changed data in cache (need to do cache behind here and still that is terribly slow !!)
3990            releaseRaster(true);
3991        }
3992
3993        // notify data changed
3994        dataChanged();
3995    }
3996
3997    /**
3998     * Returns the data value located at position (x, y, c) as double whatever is the internal data type.<br>
3999     * The value is interpolated depending the current double (x,y) coordinates.<br>
4000     * It returns 0d if value is out of range.
4001     */
4002    public double getDataInterpolated(double x, double y, int c)
4003    {
4004        final int xi = (int) x;
4005        final int xip = xi + 1;
4006        final int yi = (int) y;
4007        final int yip = yi + 1;
4008        final int sx = getSizeX();
4009        final int sy = getSizeY();
4010
4011        double result = 0d;
4012
4013        // at least one pixel inside
4014        if ((xi < sx) && (yi < sy) && (xip >= 0) && (yip >= 0))
4015        {
4016            final double ratioNextX = x - (double) xi;
4017            final double ratioCurX = 1d - ratioNextX;
4018            final double ratioNextY = y - (double) yi;
4019            final double ratioCurY = 1d - ratioNextY;
4020
4021            if (yi >= 0)
4022            {
4023                if (xi >= 0)
4024                    result += getData(xi, yi, c) * (ratioCurX * ratioCurY);
4025                if (xip < sx)
4026                    result += getData(xip, yi, c) * (ratioNextX * ratioCurY);
4027            }
4028            if (yip < sy)
4029            {
4030                if (xi >= 0)
4031                    result += getData(xi, yip, c) * (ratioCurX * ratioNextY);
4032                if (xip < sx)
4033                    result += getData(xip, yip, c) * (ratioNextX * ratioNextY);
4034            }
4035        }
4036
4037        return result;
4038    }
4039
4040    /**
4041     * Return the value located at (x, y, c) position
4042     */
4043    public byte getDataAsByte(int x, int y, int c)
4044    {
4045        // ignore band offset as it's always 0 here
4046        return (((DataBufferByte) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())];
4047    }
4048
4049    /**
4050     * Set the value located at (x, y, c) position
4051     */
4052    public void setDataAsByte(int x, int y, int c, byte value)
4053    {
4054        final WritableRaster wr = getRaster();
4055        // ignore band offset as it's always 0 here
4056        (((DataBufferByte) wr.getDataBuffer()).getData(c))[x + (y * getWidth())] = value;
4057        // save changed data in cache
4058        saveRasterInCache(wr);
4059        // notify data changed
4060        dataChanged();
4061    }
4062
4063    /**
4064     * Return the value located at (x, y, c) position
4065     */
4066    public short getDataAsShort(int x, int y, int c)
4067    {
4068        // ignore band offset as it's always 0 here
4069        final DataBuffer db = getRaster().getDataBuffer();
4070
4071        if (db instanceof DataBufferUShort)
4072            return (((DataBufferUShort) db).getData(c))[x + (y * getWidth())];
4073
4074        return (((DataBufferShort) db).getData(c))[x + (y * getWidth())];
4075    }
4076
4077    /**
4078     * Set the value located at (x, y, c) position
4079     */
4080    public void setDataAsShort(int x, int y, int c, short value)
4081    {
4082        final WritableRaster wr = getRaster();
4083        final DataBuffer db = wr.getDataBuffer();
4084        if (db instanceof DataBufferUShort)
4085            // ignore band offset as it's always 0 here
4086            (((DataBufferUShort) db).getData(c))[x + (y * getWidth())] = value;
4087        else
4088            (((DataBufferShort) db).getData(c))[x + (y * getWidth())] = value;
4089        // save changed data in cache
4090        saveRasterInCache(wr);
4091        // notify data changed
4092        dataChanged();
4093    }
4094
4095    /**
4096     * Return the value located at (x, y, c) position
4097     */
4098    public int getDataAsInt(int x, int y, int c)
4099    {
4100        // ignore band offset as it's always 0 here
4101        return (((DataBufferInt) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())];
4102    }
4103
4104    /**
4105     * Set the value located at (x, y, c) position
4106     */
4107    public void setDataAsInt(int x, int y, int c, int value)
4108    {
4109        final WritableRaster wr = getRaster();
4110        // ignore band offset as it's always 0 here
4111        (((DataBufferInt) wr.getDataBuffer()).getData(c))[x + (y * getWidth())] = value;
4112        // save changed data in cache
4113        saveRasterInCache(wr);
4114        // notify data changed
4115        dataChanged();
4116    }
4117
4118    /**
4119     * Return the value located at (x, y, c) position
4120     */
4121    public float getDataAsFloat(int x, int y, int c)
4122    {
4123        // ignore band offset as it's always 0 here
4124        return (((DataBufferFloat) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())];
4125    }
4126
4127    /**
4128     * Set the value located at (x, y, c) position
4129     */
4130    public void setDataAsFloat(int x, int y, int c, float value)
4131    {
4132        final WritableRaster wr = getRaster();
4133        // ignore band offset as it's always 0 here
4134        (((DataBufferFloat) wr.getDataBuffer()).getData(c))[x + (y * getWidth())] = value;
4135        // save changed data in cache
4136        saveRasterInCache(wr);
4137        // notify data changed
4138        dataChanged();
4139    }
4140
4141    /**
4142     * Return the value located at (x, y, c) position
4143     */
4144    public double getDataAsDouble(int x, int y, int c)
4145    {
4146        // ignore band offset as it's always 0 here
4147        return (((DataBufferDouble) getRaster().getDataBuffer()).getData(c))[x + (y * getWidth())];
4148    }
4149
4150    /**
4151     * Set the value located at (x, y, c) position
4152     */
4153    public void setDataAsDouble(int x, int y, int c, double value)
4154    {
4155        final WritableRaster wr = getRaster();
4156        // ignore band offset as it's always 0 here
4157        (((DataBufferDouble) wr.getDataBuffer()).getData(c))[x + (y * getWidth())] = value;
4158        // save changed data in cache
4159        saveRasterInCache(wr);
4160        // notify data changed
4161        dataChanged();
4162    }
4163
4164    /**
4165     * Same as getRGB but by using the specified LUT instead of internal one
4166     * 
4167     * @see java.awt.image.BufferedImage#getRGB(int, int)
4168     */
4169    public int getRGB(int x, int y, LUT lut)
4170    {
4171        return getIcyColorModel().getRGB(getRaster().getDataElements(x, y, null), lut);
4172    }
4173
4174    /**
4175     * Internal copy data from an icy image (notify data changed)
4176     * 
4177     * @param srcImage
4178     *        source icy image
4179     * @param srcRect
4180     *        source region
4181     * @param dstPt
4182     *        destination X,Y position
4183     * @param srcChannel
4184     *        source channel
4185     * @param dstChannel
4186     *        destination channel
4187     */
4188    protected void fastCopyData(IcyBufferedImage srcImage, Rectangle srcRect, Point dstPt, int srcChannel,
4189            int dstChannel)
4190    {
4191        final int srcSizeX = srcImage.getSizeX();
4192        final int dstSizeX = getSizeX();
4193
4194        // limit to source image size
4195        Rectangle adjSrcRect = srcRect.intersection(new Rectangle(srcSizeX, srcImage.getSizeY()));
4196        // negative destination x position
4197        if (dstPt.x < 0)
4198            // adjust source rect
4199            adjSrcRect.x += -dstPt.x;
4200        // negative destination y position
4201        if (dstPt.y < 0)
4202            // adjust source rect
4203            adjSrcRect.y += -dstPt.y;
4204
4205        final Rectangle dstRect = new Rectangle(dstPt.x, dstPt.y, adjSrcRect.width, adjSrcRect.height);
4206        // limit to destination image size
4207        final Rectangle adjDstRect = dstRect.intersection(new Rectangle(dstSizeX, getSizeY()));
4208
4209        final int w = Math.min(adjSrcRect.width, adjDstRect.width);
4210        final int h = Math.min(adjSrcRect.height, adjDstRect.height);
4211
4212        // nothing to copy
4213        if ((w == 0) || (h == 0))
4214            return;
4215
4216        lockRaster();
4217        try
4218        {
4219            final boolean signed = srcImage.getDataType_().isSigned();
4220            final Object src = srcImage.getDataXY(srcChannel);
4221            final Object dst = getDataXY(dstChannel);
4222
4223            int srcOffset = adjSrcRect.x + (adjSrcRect.y * srcSizeX);
4224            int dstOffset = adjDstRect.x + (adjDstRect.y * dstSizeX);
4225
4226            for (int y = 0; y < h; y++)
4227            {
4228                ArrayUtil.arrayToArray(src, srcOffset, dst, dstOffset, w, signed);
4229                srcOffset += srcSizeX;
4230                dstOffset += dstSizeX;
4231            }
4232        }
4233        finally
4234        {
4235            releaseRaster(true);
4236        }
4237
4238        // notify data changed
4239        dataChanged();
4240    }
4241
4242    /**
4243     * Internal copy data from a compatible image (notify data changed)
4244     * 
4245     * @param srcImage
4246     *        source image
4247     */
4248    protected void internalCopyData(int srcChannel, int dstChannel, DataBuffer src_db, DataBuffer dst_db, int[] indices,
4249            int[] band_offsets, int[] bank_offsets, int scanlineStride_src, int pixelStride_src, int maxX, int maxY,
4250            int decOffsetSrc)
4251    {
4252        final int scanlineStride_dst = getSizeX();
4253
4254        final int bank = indices[srcChannel];
4255        final int offset = band_offsets[srcChannel] + bank_offsets[bank] - decOffsetSrc;
4256
4257        switch (getDataType_().getJavaType())
4258        {
4259            case BYTE:
4260            {
4261                final byte[] src;
4262                final byte[] dst = ((DataBufferByte) dst_db).getData(dstChannel);
4263
4264                // LOCI use its own buffer classes
4265                if (src_db instanceof SignedByteBuffer)
4266                    src = ((SignedByteBuffer) src_db).getData(bank);
4267                else
4268                    src = ((DataBufferByte) src_db).getData(bank);
4269
4270                int offset_src = offset;
4271                int offset_dst = 0;
4272                for (int y = 0; y < maxY; y++)
4273                {
4274                    int offset_src_pix = offset_src;
4275                    int offset_dst_pix = offset_dst;
4276
4277                    for (int x = 0; x < maxX; x++)
4278                    {
4279                        dst[offset_dst_pix] = src[offset_src_pix];
4280                        offset_src_pix += pixelStride_src;
4281                        offset_dst_pix++;
4282                    }
4283
4284                    offset_src += scanlineStride_src;
4285                    offset_dst += scanlineStride_dst;
4286                }
4287                break;
4288            }
4289
4290            case SHORT:
4291            {
4292                final short[] src;
4293                final short[] dst;
4294
4295                // LOCI use its own buffer classes
4296                if (src_db instanceof SignedShortBuffer)
4297                    src = ((SignedShortBuffer) src_db).getData(bank);
4298                else if (src_db instanceof DataBufferShort)
4299                    src = ((DataBufferShort) src_db).getData(bank);
4300                else
4301                    src = ((DataBufferUShort) src_db).getData(bank);
4302
4303                if (dst_db instanceof DataBufferShort)
4304                    dst = ((DataBufferShort) dst_db).getData(dstChannel);
4305                else
4306                    dst = ((DataBufferUShort) dst_db).getData(dstChannel);
4307
4308                int offset_src = offset;
4309                int offset_dst = 0;
4310                for (int y = 0; y < maxY; y++)
4311                {
4312                    int offset_src_pix = offset_src;
4313                    int offset_dst_pix = offset_dst;
4314
4315                    for (int x = 0; x < maxX; x++)
4316                    {
4317                        dst[offset_dst_pix] = src[offset_src_pix];
4318                        offset_src_pix += pixelStride_src;
4319                        offset_dst_pix++;
4320                    }
4321
4322                    offset_src += scanlineStride_src;
4323                    offset_dst += scanlineStride_dst;
4324                }
4325                break;
4326            }
4327
4328            case INT:
4329            {
4330                final int[] src;
4331                final int[] dst = ((DataBufferInt) dst_db).getData(dstChannel);
4332
4333                // LOCI use its own buffer classes
4334                if (src_db instanceof UnsignedIntBuffer)
4335                    src = ((UnsignedIntBuffer) src_db).getData(bank);
4336                else
4337                    src = ((DataBufferInt) src_db).getData(bank);
4338
4339                int offset_src = offset;
4340                int offset_dst = 0;
4341                for (int y = 0; y < maxY; y++)
4342                {
4343                    int offset_src_pix = offset_src;
4344                    int offset_dst_pix = offset_dst;
4345
4346                    for (int x = 0; x < maxX; x++)
4347                    {
4348                        dst[offset_dst_pix] = src[offset_src_pix];
4349                        offset_src_pix += pixelStride_src;
4350                        offset_dst_pix++;
4351                    }
4352
4353                    offset_src += scanlineStride_src;
4354                    offset_dst += scanlineStride_dst;
4355                }
4356                break;
4357            }
4358
4359            case FLOAT:
4360            {
4361                final float[] src = ((DataBufferFloat) src_db).getData(bank);
4362                final float[] dst = ((DataBufferFloat) dst_db).getData(dstChannel);
4363
4364                int offset_src = offset;
4365                int offset_dst = 0;
4366                for (int y = 0; y < maxY; y++)
4367                {
4368                    int offset_src_pix = offset_src;
4369                    int offset_dst_pix = offset_dst;
4370
4371                    for (int x = 0; x < maxX; x++)
4372                    {
4373                        dst[offset_dst_pix] = src[offset_src_pix];
4374                        offset_src_pix += pixelStride_src;
4375                        offset_dst_pix++;
4376                    }
4377
4378                    offset_src += scanlineStride_src;
4379                    offset_dst += scanlineStride_dst;
4380                }
4381                break;
4382            }
4383
4384            case DOUBLE:
4385            {
4386                final double[] src = ((DataBufferDouble) src_db).getData(bank);
4387                final double[] dst = ((DataBufferDouble) dst_db).getData(dstChannel);
4388
4389                int offset_src = offset;
4390                int offset_dst = 0;
4391                for (int y = 0; y < maxY; y++)
4392                {
4393                    int offset_src_pix = offset_src;
4394                    int offset_dst_pix = offset_dst;
4395
4396                    for (int x = 0; x < maxX; x++)
4397                    {
4398                        dst[offset_dst_pix] = src[offset_src_pix];
4399                        offset_src_pix += pixelStride_src;
4400                        offset_dst_pix++;
4401                    }
4402
4403                    offset_src += scanlineStride_src;
4404                    offset_dst += scanlineStride_dst;
4405                }
4406                break;
4407            }
4408
4409            default:
4410                // do nothing here
4411                break;
4412        }
4413    }
4414
4415    /**
4416     * Copy channel data from a compatible sample model and writable raster (notify data changed).
4417     * 
4418     * @param sampleModel
4419     *        source sample model
4420     * @param raster
4421     *        source writable raster to read data from
4422     * @param srcChannel
4423     *        source channel (-1 for all channels)
4424     * @param dstChannel
4425     *        destination channel (only significant if source channel != -1)
4426     * @return <code>true</code> if the copy operation succeed, <code>false</code> otherwise
4427     */
4428    public boolean copyData(ComponentSampleModel sampleModel, WritableRaster sourceRaster, int srcChannel,
4429            int dstChannel)
4430    {
4431        // not compatible sample model
4432        if (DataType.getDataTypeFromDataBufferType(sampleModel.getDataType()) != getDataType_())
4433            return false;
4434
4435        final DataBuffer src_db = sourceRaster.getDataBuffer();
4436        final WritableRaster dst_raster = getRaster();
4437        final DataBuffer dst_db = dst_raster.getDataBuffer();
4438        final int[] indices = sampleModel.getBankIndices();
4439        final int[] band_offsets = sampleModel.getBandOffsets();
4440        final int[] bank_offsets = src_db.getOffsets();
4441        final int scanlineStride_src = sampleModel.getScanlineStride();
4442        final int pixelStride_src = sampleModel.getPixelStride();
4443        final int maxX = Math.min(getSizeX(), sampleModel.getWidth());
4444        final int maxY = Math.min(getSizeY(), sampleModel.getHeight());
4445        final int decOffsetSrc = sourceRaster.getSampleModelTranslateX()
4446                + (sourceRaster.getSampleModelTranslateY() * scanlineStride_src);
4447
4448        // all channels
4449        if (srcChannel == -1)
4450        {
4451            final int numBands = sampleModel.getNumBands();
4452
4453            for (int band = 0; band < numBands; band++)
4454                internalCopyData(band, band, src_db, dst_db, indices, band_offsets, bank_offsets, scanlineStride_src,
4455                        pixelStride_src, maxX, maxY, decOffsetSrc);
4456        }
4457        else
4458        {
4459            internalCopyData(srcChannel, dstChannel, src_db, dst_db, indices, band_offsets, bank_offsets,
4460                    scanlineStride_src, pixelStride_src, maxX, maxY, decOffsetSrc);
4461        }
4462
4463        // save in cache changed data
4464        saveRasterInCache(dst_raster);
4465        // notify data changed
4466        dataChanged();
4467
4468        return true;
4469    }
4470
4471    /**
4472     * Copy data to specified location from an data array.
4473     * 
4474     * @param data
4475     *        source data array (should be same type than image data type)
4476     * @param dataDim
4477     *        source data dimension (array length should be >= Dimension.width * Dimension.heigth)
4478     * @param signed
4479     *        if the source data array should be considered as signed data (meaningful for integer
4480     *        data type only)
4481     * @param dstPt
4482     *        destination X,Y position (assume [0,0] if null)
4483     * @param dstChannel
4484     *        destination channel
4485     */
4486    public void copyData(Object data, Dimension dataDim, boolean signed, Point dstPt, int dstChannel)
4487    {
4488        if ((data == null) || (dataDim == null))
4489            return;
4490
4491        // source image size
4492        final Rectangle adjSrcRect = new Rectangle(dataDim);
4493        // negative destination x position
4494        if (dstPt.x < 0)
4495            // adjust source rect
4496            adjSrcRect.x += -dstPt.x;
4497        // negative destination y position
4498        if (dstPt.y < 0)
4499            // adjust source rect
4500            adjSrcRect.y += -dstPt.y;
4501
4502        final Rectangle dstRect = new Rectangle(dstPt.x, dstPt.y, adjSrcRect.width, adjSrcRect.height);
4503        // limit to destination image size
4504        final Rectangle adjDstRect = dstRect.intersection(new Rectangle(getSizeX(), getSizeY()));
4505
4506        final int w = Math.min(adjSrcRect.width, adjDstRect.width);
4507        final int h = Math.min(adjSrcRect.height, adjDstRect.height);
4508
4509        // nothing to copy
4510        if ((w == 0) || (h == 0))
4511            return;
4512
4513        lockRaster();
4514        try
4515        {
4516            final Object dst = getDataXY(dstChannel);
4517            final int srcSizeX = dataDim.width;
4518            final int dstSizeX = getSizeX();
4519
4520            int srcOffset = adjSrcRect.x + (adjSrcRect.y * srcSizeX);
4521            int dstOffset = adjDstRect.x + (adjDstRect.y * dstSizeX);
4522
4523            for (int y = 0; y < h; y++)
4524            {
4525                // do data copy (and conversion if needed)
4526                ArrayUtil.arrayToArray(data, srcOffset, dst, dstOffset, w, signed);
4527                srcOffset += srcSizeX;
4528                dstOffset += dstSizeX;
4529            }
4530        }
4531        finally
4532        {
4533            releaseRaster(true);
4534        }
4535
4536        // notify data changed
4537        dataChanged();
4538    }
4539
4540    /**
4541     * Copy data from an image (notify data changed)
4542     * 
4543     * @param srcImage
4544     *        source image
4545     * @param srcRect
4546     *        source region to copy (assume whole image if null)
4547     * @param dstPt
4548     *        destination X,Y position (assume [0,0] if null)
4549     * @param srcChannel
4550     *        source channel (-1 for all channels)
4551     * @param dstChannel
4552     *        destination channel (only significant if source channel != -1)
4553     */
4554    public void copyData(IcyBufferedImage srcImage, Rectangle srcRect, Point dstPt, int srcChannel, int dstChannel)
4555    {
4556        if (srcImage == null)
4557            return;
4558
4559        final Rectangle adjSrcRect;
4560        final Point adjDstPt;
4561
4562        if (srcRect == null)
4563            adjSrcRect = new Rectangle(srcImage.getSizeX(), srcImage.getSizeY());
4564        else
4565            adjSrcRect = srcRect;
4566        if (dstPt == null)
4567            adjDstPt = new Point(0, 0);
4568        else
4569            adjDstPt = dstPt;
4570
4571        // copy all possible components
4572        if (srcChannel == -1)
4573        {
4574            final int sizeC = Math.min(srcImage.getSizeC(), getSizeC());
4575
4576            beginUpdate();
4577            try
4578            {
4579                for (int c = 0; c < sizeC; c++)
4580                    fastCopyData(srcImage, adjSrcRect, adjDstPt, c, c);
4581            }
4582            finally
4583            {
4584                endUpdate();
4585            }
4586        }
4587        else
4588            fastCopyData(srcImage, adjSrcRect, adjDstPt, srcChannel, dstChannel);
4589    }
4590
4591    /**
4592     * Copy data from an image (notify data changed)
4593     * 
4594     * @param srcImage
4595     *        source image
4596     * @param srcRect
4597     *        source region to copy (assume whole image if null)
4598     * @param dstPt
4599     *        destination (assume [0,0] if null)
4600     */
4601    public void copyData(IcyBufferedImage srcImage, Rectangle srcRect, Point dstPt)
4602    {
4603        if (srcImage == null)
4604            return;
4605
4606        copyData(srcImage, srcRect, dstPt, -1, 0);
4607    }
4608
4609    /**
4610     * Copy data from an image (notify data changed)
4611     * 
4612     * @param srcImage
4613     *        source image
4614     * @param srcChannel
4615     *        source channel to copy (-1 for all channels)
4616     * @param dstChannel
4617     *        destination channel to receive data (only significant if source channel != -1)
4618     */
4619    public void copyData(BufferedImage srcImage, int srcChannel, int dstChannel)
4620    {
4621        if (srcImage == null)
4622            return;
4623
4624        if (srcImage instanceof IcyBufferedImage)
4625            copyData(((IcyBufferedImage) srcImage), null, null, srcChannel, dstChannel);
4626        else
4627        {
4628            final boolean done;
4629
4630            // try to use faster copy for compatible image
4631            if (srcImage.getSampleModel() instanceof ComponentSampleModel)
4632                done = copyData((ComponentSampleModel) srcImage.getSampleModel(), srcImage.getRaster(), srcChannel,
4633                        dstChannel);
4634            else
4635                done = false;
4636
4637            if (!done)
4638            {
4639                final WritableRaster wr = getRaster();
4640                // image not compatible, use generic (and slow) data copy
4641                srcImage.copyData(wr);
4642                // save changed data in cache
4643                saveRasterInCache(wr);
4644                // notify data changed
4645                dataChanged();
4646            }
4647        }
4648    }
4649
4650    /**
4651     * Copy data from an image (notify data changed)
4652     * 
4653     * @param srcImage
4654     *        source image
4655     */
4656    public void copyData(BufferedImage srcImage)
4657    {
4658        copyData(srcImage, -1, -1);
4659    }
4660
4661    /**
4662     * Return raw data component as an array of byte
4663     * 
4664     * @param c
4665     *        component index
4666     * @param out
4667     *        output array (can be null)
4668     * @param offset
4669     *        output offset
4670     * @param little
4671     *        little endian order
4672     */
4673    public byte[] getRawData(int c, byte[] out, int offset, boolean little)
4674    {
4675        // alloc output array if needed
4676        final byte[] result = Array1DUtil.allocIfNull(out,
4677                offset + (getSizeX() * getSizeY() * getDataType_().getSize()));
4678
4679        return ByteArrayConvert.toByteArray(getDataXY(c), 0, result, offset, little);
4680    }
4681
4682    /**
4683     * Return raw data component as an array of byte
4684     * 
4685     * @param c
4686     *        component index
4687     * @param little
4688     *        little endian order
4689     */
4690    public byte[] getRawData(int c, boolean little)
4691    {
4692        return getRawData(c, null, 0, little);
4693    }
4694
4695    /**
4696     * Return raw data for all components as an array of byte
4697     * 
4698     * @param out
4699     *        output array (can be null)
4700     * @param offset
4701     *        output offset
4702     * @param little
4703     *        little endian order
4704     */
4705    public byte[] getRawData(byte[] out, int offset, boolean little)
4706    {
4707        final int sizeXY = getSizeX() * getSizeY();
4708        final int sizeC = getSizeC();
4709        final int sizeType = getDataType_().getSize();
4710
4711        // alloc output array if needed
4712        final byte[] result = Array1DUtil.allocIfNull(out, offset + (sizeC * sizeXY * sizeType));
4713
4714        int outOff = offset;
4715        for (int c = 0; c < sizeC; c++)
4716        {
4717            getRawData(c, result, outOff, little);
4718            outOff += sizeXY * sizeType;
4719        }
4720
4721        return result;
4722    }
4723
4724    /**
4725     * Return raw data for all components as an array of byte
4726     * 
4727     * @param little
4728     *        little endian order
4729     */
4730    public byte[] getRawData(boolean little)
4731    {
4732        return getRawData(null, 0, little);
4733    }
4734
4735    /**
4736     * Set raw data component from an array of byte (notify data changed)
4737     * 
4738     * @param c
4739     *        component index
4740     * @param data
4741     *        data as byte array
4742     * @param offset
4743     *        input offset
4744     * @param little
4745     *        little endian order
4746     */
4747    public void setRawData(int c, byte[] data, int offset, boolean little)
4748    {
4749        if (data == null)
4750            return;
4751
4752        lockRaster();
4753        try
4754        {
4755            ByteArrayConvert.byteArrayTo(data, offset, getDataXY(c), 0, -1, little);
4756        }
4757        finally
4758        {
4759            releaseRaster(true);
4760        }
4761
4762        // notify data changed
4763        dataChanged();
4764    }
4765
4766    /**
4767     * Set raw data component from an array of byte (notify data changed)
4768     * 
4769     * @param c
4770     *        component index
4771     * @param data
4772     *        data as byte array
4773     * @param little
4774     *        little endian order
4775     */
4776    public void setRawData(int c, byte[] data, boolean little)
4777    {
4778        setRawData(c, data, 0, little);
4779    }
4780
4781    /**
4782     * Set raw data for all components from an array of byte (notify data changed).<br/>
4783     * Data are arranged in the following dimension order: XYC
4784     * 
4785     * @param data
4786     *        data as byte array
4787     * @param offset
4788     *        input offset
4789     * @param little
4790     *        little endian order
4791     */
4792    public void setRawData(byte[] data, int offset, boolean little)
4793    {
4794        if (data == null)
4795            return;
4796
4797        final int sizeXY = getSizeX() * getSizeY();
4798        final int sizeC = getSizeC();
4799        final int sizeType = getDataType_().getSize();
4800
4801        beginUpdate();
4802        try
4803        {
4804            int inOff = offset;
4805            for (int c = 0; c < sizeC; c++)
4806            {
4807                setRawData(c, data, inOff, little);
4808                inOff += sizeXY * sizeType;
4809            }
4810        }
4811        finally
4812        {
4813            endUpdate();
4814        }
4815    }
4816
4817    /**
4818     * Set raw data for all components from an array of byte (notify data changed)
4819     * 
4820     * @param data
4821     *        data as byte array
4822     * @param little
4823     *        little endian order
4824     */
4825    public void setRawData(byte[] data, boolean little)
4826    {
4827        setRawData(data, 0, little);
4828    }
4829
4830    /**
4831     * Return the colormap of the specified channel.
4832     */
4833    public IcyColorMap getColorMap(int channel)
4834    {
4835        return getIcyColorModel().getColorMap(channel);
4836    }
4837
4838    /**
4839     * @deprecated Use {@link #getColorMap(int)} instead (different case).
4840     */
4841    @Deprecated
4842    public IcyColorMap getColormap(int channel)
4843    {
4844        return getColorMap(channel);
4845    }
4846
4847    /**
4848     * @deprecated Use {@link #setColorMaps(BufferedImage)} instead.
4849     */
4850    @Deprecated
4851    public void copyColormap(BufferedImage srcImage)
4852    {
4853        setColorMaps(srcImage);
4854    }
4855
4856    /**
4857     * Set colormaps from specified image.
4858     */
4859    public void setColorMaps(BufferedImage srcImage)
4860    {
4861        getIcyColorModel().setColorMaps(srcImage.getColorModel());
4862    }
4863
4864    /**
4865     * @deprecated Use {@link #setColorMaps(BufferedImage)} instead (different case).
4866     */
4867    @Deprecated
4868    public void setColormaps(BufferedImage srcImage)
4869    {
4870        setColorMaps(srcImage);
4871    }
4872
4873    /**
4874     * Set the colormap for the specified channel.
4875     * 
4876     * @param channel
4877     *        channel we want to set the colormap
4878     * @param map
4879     *        source colorspace to copy
4880     * @param setAlpha
4881     *        also set the alpha information
4882     */
4883    public void setColorMap(int channel, IcyColorMap map, boolean setAlpha)
4884    {
4885        getIcyColorModel().setColorMap(channel, map, setAlpha);
4886    }
4887
4888    /**
4889     * Set the colormap for the specified channel.
4890     * 
4891     * @param channel
4892     *        channel we want to set the colormap
4893     * @param map
4894     *        source colorspace to copy
4895     */
4896    public void setColorMap(int channel, IcyColorMap map)
4897    {
4898        getIcyColorModel().setColorMap(channel, map, map.isAlpha());
4899    }
4900
4901    /**
4902     * @deprecated Use {@link #setColorMap(int, IcyColorMap, boolean)} instead.
4903     */
4904    @Deprecated
4905    public void setColormap(int channel, IcyColorMap map)
4906    {
4907        setColorMap(channel, map, true);
4908    }
4909
4910    /**
4911     * notify image data has changed
4912     */
4913    public void dataChanged()
4914    {
4915        updater.changed(new IcyBufferedImageEvent(this, IcyBufferedImageEventType.DATA_CHANGED));
4916    }
4917
4918    /**
4919     * notify image colorMap has changed
4920     */
4921    protected void colormapChanged(int component)
4922    {
4923        updater.changed(new IcyBufferedImageEvent(this, IcyBufferedImageEventType.COLORMAP_CHANGED, component));
4924    }
4925
4926    /**
4927     * notify image channels bounds has changed
4928     */
4929    public void channelBoundsChanged(int channel)
4930    {
4931        updater.changed(new IcyBufferedImageEvent(this, IcyBufferedImageEventType.BOUNDS_CHANGED, channel));
4932    }
4933
4934    /**
4935     * @deprecated Use {@link #channelBoundsChanged(int)} instead.
4936     */
4937    @Deprecated
4938    public void componentBoundsChanged(int component)
4939    {
4940        channelBoundsChanged(component);
4941    }
4942
4943    /**
4944     * fire change event
4945     */
4946    protected void fireChangeEvent(IcyBufferedImageEvent e)
4947    {
4948        for (IcyBufferedImageListener listener : new ArrayList<IcyBufferedImageListener>(listeners))
4949            listener.imageChanged(e);
4950    }
4951
4952    public void addListener(IcyBufferedImageListener listener)
4953    {
4954        listeners.add(listener);
4955    }
4956
4957    public void removeListener(IcyBufferedImageListener listener)
4958    {
4959        listeners.remove(listener);
4960    }
4961
4962    public void beginUpdate()
4963    {
4964        updater.beginUpdate();
4965    }
4966
4967    public void endUpdate()
4968    {
4969        updater.endUpdate();
4970    }
4971
4972    public boolean isUpdating()
4973    {
4974        return updater.isUpdating();
4975    }
4976
4977    @Override
4978    public void onChanged(CollapsibleEvent object)
4979    {
4980        IcyBufferedImageEvent event = (IcyBufferedImageEvent) object;
4981
4982        switch (event.getType())
4983        {
4984            // do here global process on image data change
4985            case DATA_CHANGED:
4986                // update image components bounds
4987                if (autoUpdateChannelBounds)
4988                    updateChannelsBounds();
4989                break;
4990
4991            // do here global process on image bounds change
4992            case BOUNDS_CHANGED:
4993                break;
4994
4995            // do here global process on image colormap change
4996            case COLORMAP_CHANGED:
4997                break;
4998        }
4999
5000        // notify listener we have changed
5001        fireChangeEvent(event);
5002    }
5003
5004    @Override
5005    public void colorModelChanged(IcyColorModelEvent e)
5006    {
5007        switch (e.getType())
5008        {
5009            case COLORMAP_CHANGED:
5010                colormapChanged(e.getComponent());
5011                break;
5012
5013            case SCALER_CHANGED:
5014                channelBoundsChanged(e.getComponent());
5015                break;
5016        }
5017    }
5018
5019    @Override
5020    public String toString()
5021    {
5022        return "IcyBufferedImage: " + getSizeX() + " x " + getSizeY() + " - " + getSizeC() + " ch (" + getDataType_()
5023                + ")";
5024    }
5025}