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.AlphaComposite;
022import java.awt.Graphics2D;
023import java.awt.Point;
024import java.awt.Rectangle;
025import java.awt.RenderingHints;
026import java.awt.image.BufferedImage;
027import java.util.List;
028
029import javax.media.jai.BorderExtender;
030import javax.media.jai.Interpolation;
031import javax.media.jai.JAI;
032import javax.media.jai.RenderedOp;
033import javax.media.jai.operator.RotateDescriptor;
034import javax.media.jai.operator.ScaleDescriptor;
035import javax.swing.SwingConstants;
036
037import icy.image.lut.LUT;
038import icy.math.Scaler;
039import icy.type.DataType;
040import icy.type.collection.array.Array1DUtil;
041import icy.type.collection.array.ArrayType;
042import icy.type.collection.array.ArrayUtil;
043
044/**
045 * {@link IcyBufferedImage} utilities class.<br>
046 * You can find here tools to clone, manipulate the image data type, its size...
047 * 
048 * @author Stephane
049 */
050public class IcyBufferedImageUtil
051{
052    public static enum FilterType
053    {
054        NEAREST, BILINEAR, BICUBIC
055    };
056
057    /**
058     * used for getARGBImage method (fast conversion)
059     */
060    private static ARGBImageBuilder argbImageBuilder = new ARGBImageBuilder();
061
062    /**
063     * @deprecated Use {@link IcyBufferedImage#createFrom(BufferedImage)} instead.
064     */
065    @Deprecated
066    public static IcyBufferedImage toIcyBufferedImage(BufferedImage image)
067    {
068        return IcyBufferedImage.createFrom(image);
069    }
070
071    /**
072     * @deprecated Use {@link IcyBufferedImage#createFrom(List)} instead.
073     */
074    @Deprecated
075    public static IcyBufferedImage toIcyBufferedImage(List<BufferedImage> images)
076    {
077        return IcyBufferedImage.createFrom(images);
078    }
079
080    /**
081     * Draw the source {@link IcyBufferedImage} into the destination {@link BufferedImage}<br>
082     * If <code>dest</code> is <code>null</code> then a new TYPE_INT_ARGB {@link BufferedImage} is
083     * returned.<br>
084     * 
085     * @param source
086     *        source image
087     * @param dest
088     *        destination image
089     * @param lut
090     *        {@link LUT} is used for color calculation (internal lut is used if null).
091     */
092    public static BufferedImage toBufferedImage(IcyBufferedImage source, BufferedImage dest, LUT lut)
093    {
094        final BufferedImage result;
095
096        if (dest == null)
097            result = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_INT_ARGB);
098        else
099            result = dest;
100
101        // else we need to convert to wanted type...
102        final Graphics2D g = result.createGraphics();
103        // we don't want to blend over previous image (if any)
104        g.setComposite(AlphaComposite.Src);
105        g.drawImage(getARGBImage(source, lut), 0, 0, null);
106        g.dispose();
107
108        return result;
109    }
110
111    /**
112     * Draw the source {@link IcyBufferedImage} into the destination {@link BufferedImage}<br>
113     * If <code>dest</code> is null then a new TYPE_INT_ARGB {@link BufferedImage} is returned.
114     * 
115     * @param source
116     *        source image
117     * @param dest
118     *        destination image
119     */
120    public static BufferedImage toBufferedImage(IcyBufferedImage source, BufferedImage dest)
121    {
122        return toBufferedImage(source, dest, null);
123    }
124
125    /**
126     * Convert the current {@link IcyBufferedImage} into a {@link BufferedImage} of the specified
127     * type.
128     * 
129     * @param source
130     *        source image
131     * @param imageType
132     *        wanted image type, only the following is accepted :<br>
133     *        BufferedImage.TYPE_INT_ARGB<br>
134     *        BufferedImage.TYPE_INT_RGB<br>
135     *        BufferedImage.TYPE_BYTE_GRAY<br>
136     * @param lut
137     *        lut used for color calculation (source image lut is used if null)
138     * @return BufferedImage
139     */
140    public static BufferedImage toBufferedImage(IcyBufferedImage source, int imageType, LUT lut)
141    {
142        if (source == null)
143            return null;
144
145        final BufferedImage outImg = new BufferedImage(source.getWidth(), source.getHeight(), imageType);
146
147        toBufferedImage(source, outImg, lut);
148
149        return outImg;
150    }
151
152    /**
153     * Convert the current {@link IcyBufferedImage} into a {@link BufferedImage} of the specified
154     * type.
155     * 
156     * @param source
157     *        source image
158     * @param imageType
159     *        wanted image type, only the following is accepted :<br>
160     *        BufferedImage.TYPE_INT_ARGB<br>
161     *        BufferedImage.TYPE_INT_RGB<br>
162     *        BufferedImage.TYPE_BYTE_GRAY<br>
163     * @return BufferedImage
164     */
165    public static BufferedImage toBufferedImage(IcyBufferedImage source, int imageType)
166    {
167        return toBufferedImage(source, imageType, null);
168    }
169
170    /**
171     * Draw the source {@link IcyBufferedImage} into the destination ARGB {@link BufferedImage}<br>
172     * If <code>dest</code> is <code>null</code> then a new ARGB {@link BufferedImage} is
173     * returned.<br>
174     * This function is faster for ARGB conversion than
175     * {@link #toBufferedImage(IcyBufferedImage, BufferedImage, LUT)}
176     * but the output {@link BufferedImage} is fixed to ARGB type (TYPE_INT_ARGB) and the image
177     * cannot be volatile, use {@link #toBufferedImage(IcyBufferedImage, BufferedImage, LUT)} for no
178     * ARGB image or if you want volatile accelerated image.
179     * 
180     * @param source
181     *        source image
182     * @param dest
183     *        destination image. Note that we access image data so it can't be volatile anymore
184     *        which may result in slower drawing
185     * @param lut
186     *        {@link LUT} is used for color calculation (internal lut is used if null).
187     * @deprecated Use {@link #toBufferedImage(IcyBufferedImage, BufferedImage, LUT)} instead.
188     */
189    @Deprecated
190    public static BufferedImage getARGBImage(IcyBufferedImage source, LUT lut, BufferedImage dest)
191    {
192        if (source == null)
193            return null;
194
195        // use image lut when no specific lut
196        if (lut == null)
197        {
198            // manually update bounds if needed before doing RGB conversion from internal LUT
199            if (!source.getAutoUpdateChannelBounds())
200                source.updateChannelsBounds();
201
202            return argbImageBuilder.buildARGBImage(source, source.createCompatibleLUT(false), dest);
203        }
204
205        return argbImageBuilder.buildARGBImage(source, lut, dest);
206    }
207
208    /**
209     * Draw the source {@link IcyBufferedImage} into the destination ARGB {@link BufferedImage}<br>
210     * If <code>dest</code> is null then a new ARGB {@link BufferedImage} is returned.<br>
211     * <br>
212     * This function is faster for ARGB conversion than
213     * {@link #toBufferedImage(IcyBufferedImage, BufferedImage)}
214     * but the output {@link BufferedImage} is fixed to ARGB type (TYPE_INT_ARGB) and the image
215     * cannot be volatile, use {@link #toBufferedImage(IcyBufferedImage, BufferedImage)} for no
216     * ARGB image or if you want volatile accelerated image.
217     * 
218     * @param source
219     *        source image
220     * @param dest
221     *        destination image. Note that we access image data so it can't be volatile anymore
222     *        which may result in slower drawing
223     * @deprecated Use {@link #toBufferedImage(IcyBufferedImage, BufferedImage)} instead.
224     */
225    @Deprecated
226    public static BufferedImage getARGBImage(IcyBufferedImage source, BufferedImage dest)
227    {
228        return getARGBImage(source, null, dest);
229    }
230
231    /**
232     * Convert the current {@link IcyBufferedImage} into a ARGB {@link BufferedImage}.<br>
233     * Note that we access image data so it can't be volatile anymore which may result in slower
234     * drawing.
235     * 
236     * @param source
237     *        source image
238     * @param lut
239     *        {@link LUT} is used for color calculation (internal lut is used if null).
240     */
241    public static BufferedImage getARGBImage(IcyBufferedImage source, LUT lut)
242    {
243        if (source == null)
244            return null;
245
246        // use image lut when no specific lut
247        if (lut == null)
248        {
249            // manually update bounds if needed before doing RGB conversion from internal LUT
250            if (!source.getAutoUpdateChannelBounds())
251                source.updateChannelsBounds();
252
253            return argbImageBuilder.buildARGBImage(source, source.createCompatibleLUT(false));
254        }
255
256        return argbImageBuilder.buildARGBImage(source, lut);
257    }
258
259    /**
260     * Convert the current {@link IcyBufferedImage} into a ARGB {@link BufferedImage}.<br>
261     * Note that we access image data so it can't be volatile anymore which may result in slower
262     * drawing.
263     * 
264     * @param source
265     *        source image
266     */
267    public static BufferedImage getARGBImage(IcyBufferedImage source)
268    {
269        return getARGBImage(source, (LUT) null);
270    }
271
272    /**
273     * Convert the source image to the specified data type.<br>
274     * This method returns a new image (the source image is not modified).
275     * 
276     * @param source
277     *        source image
278     * @param dataType
279     *        data type wanted
280     * @param scalers
281     *        scalers for scaling internal data during conversion (1 scaler per channel).<br>
282     *        Can be set to <code>null</code> to avoid value conversion.
283     * @return converted image
284     */
285    public static IcyBufferedImage convertType(IcyBufferedImage source, DataType dataType, Scaler[] scalers)
286    {
287        if (source == null)
288            return null;
289
290        final DataType srcDataType = source.getDataType_();
291
292        // can't convert
293        if ((srcDataType == null) || (srcDataType == DataType.UNDEFINED) || (dataType == null)
294                || (dataType == DataType.UNDEFINED))
295            return null;
296
297        final boolean srcSigned = srcDataType.isSigned();
298        final boolean dstSigned = dataType.isSigned();
299        final int sizeC = source.getSizeC();
300        final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), sizeC, dataType);
301
302        result.lockRaster();
303        try
304        {
305            for (int c = 0; c < sizeC; c++)
306            {
307                // no rescale ?
308                if ((scalers == null) || (c >= scalers.length) || scalers[c].isNull())
309                    // simple type change
310                    ArrayUtil.arrayToSafeArray(source.getDataXY(c), result.getDataXY(c), srcSigned, dstSigned);
311                else
312                {
313                    // first we convert in double
314                    final double[] darray = Array1DUtil.arrayToDoubleArray(source.getDataXY(c), srcSigned);
315                    // then we scale data
316                    scalers[c].scale(darray);
317                    // and finally we convert in wanted datatype
318                    Array1DUtil.doubleArrayToSafeArray(darray, result.getDataXY(c), dstSigned);
319                }
320            }
321        }
322        finally
323        {
324            result.releaseRaster(true);
325        }
326
327        // copy colormap from source image
328        result.setColorMaps(source);
329        // notify we modified data
330        result.dataChanged();
331
332        return result;
333    }
334
335    /**
336     * @deprecated Use {@link #convertType(IcyBufferedImage, DataType, Scaler[])} instead.
337     */
338    @Deprecated
339    public static IcyBufferedImage convertToType(IcyBufferedImage source, DataType dataType, Scaler scaler)
340    {
341        if (source == null)
342            return null;
343
344        final DataType srcDataType = source.getDataType_();
345
346        // can't convert
347        if ((srcDataType == DataType.UNDEFINED) || (dataType == DataType.UNDEFINED))
348            return null;
349
350        final boolean srcSigned = srcDataType.isSigned();
351        final boolean dstSigned = dataType.isSigned();
352        final int sizeC = source.getSizeC();
353        final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), sizeC, dataType);
354
355        result.lockRaster();
356        try
357        {
358            for (int c = 0; c < sizeC; c++)
359            {
360                // no rescale ?
361                if ((scaler == null) || scaler.isNull())
362                    // simple type change
363                    ArrayUtil.arrayToSafeArray(source.getDataXY(c), result.getDataXY(c), srcSigned, dstSigned);
364                else
365                {
366                    // first we convert in double
367                    final double[] darray = Array1DUtil.arrayToDoubleArray(source.getDataXY(c), srcSigned);
368                    // then we scale data
369                    scaler.scale(darray);
370                    // and finally we convert in wanted datatype
371                    Array1DUtil.doubleArrayToSafeArray(darray, result.getDataXY(c), dstSigned);
372                }
373            }
374        }
375        finally
376        {
377            result.releaseRaster(true);
378        }
379
380        // copy colormap from source image
381        result.setColorMaps(source);
382        // notify we modified data
383        result.dataChanged();
384
385        return result;
386    }
387
388    /**
389     * Convert the source image to the specified data type.<br>
390     * This method returns a new image (the source image is not modified).
391     * 
392     * @param dataType
393     *        Data type wanted
394     * @param rescale
395     *        Indicate if we want to scale data value according to data (or data type) range
396     * @param useDataBounds
397     *        Only used when <code>rescale</code> parameter is true.<br>
398     *        Specify if we use the data bounds for rescaling instead of data type bounds.
399     * @return converted image
400     */
401    public static IcyBufferedImage convertToType(IcyBufferedImage source, DataType dataType, boolean rescale,
402            boolean useDataBounds)
403    {
404        if (source == null)
405            return null;
406
407        if (!rescale)
408            return convertType(source, dataType, null);
409
410        // convert with rescale
411        final double boundsDst[] = dataType.getDefaultBounds();
412        final int sizeC = source.getSizeC();
413        final Scaler[] scalers = new Scaler[sizeC];
414
415        // build scalers
416        for (int c = 0; c < sizeC; c++)
417        {
418            final double boundsSrc[];
419
420            if (useDataBounds)
421                boundsSrc = source.getChannelBounds(c);
422            else
423                boundsSrc = source.getChannelTypeBounds(c);
424
425            scalers[c] = new Scaler(boundsSrc[0], boundsSrc[1], boundsDst[0], boundsDst[1], false);
426        }
427
428        // use scaler to scale data
429        return convertType(source, dataType, scalers);
430    }
431
432    /**
433     * Convert the source image to the specified data type.<br>
434     * This method returns a new image (the source image is not modified).
435     * 
436     * @param dataType
437     *        data type wanted
438     * @param rescale
439     *        indicate if we want to scale data value according to data type range
440     * @return converted image
441     */
442    public static IcyBufferedImage convertToType(IcyBufferedImage source, DataType dataType, boolean rescale)
443    {
444        return convertToType(source, dataType, rescale, false);
445    }
446
447    /**
448     * Create a copy of the specified image.
449     */
450    public static IcyBufferedImage getCopy(IcyBufferedImage source)
451    {
452        if (source == null)
453            return null;
454
455        // create a compatible image
456        final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), source.getSizeC(),
457                source.getDataType_());
458        // copy data from this image
459        result.copyData(source);
460
461        return result;
462    }
463
464    /**
465     * Creates a new image from the specified region of the source image.
466     */
467    public static IcyBufferedImage getSubImage(IcyBufferedImage source, Rectangle region, int c, int sizeC)
468    {
469        if (source == null)
470            return null;
471
472        final int startX;
473        final int endX;
474        final int startY;
475        final int endY;
476        final int startC;
477        final int endC;
478
479        // infinite X dimension ?
480        if ((region.x == Integer.MIN_VALUE) && (region.width == Integer.MAX_VALUE))
481        {
482            startX = 0;
483            endX = source.getSizeX();
484        }
485        else
486        {
487            startX = Math.max(0, region.x);
488            endX = Math.min(source.getSizeX(), region.x + region.width);
489        }
490        // infinite Y dimension ?
491        if ((region.y == Integer.MIN_VALUE) && (region.height == Integer.MAX_VALUE))
492        {
493            startY = 0;
494            endY = source.getSizeY();
495        }
496        else
497        {
498            startY = Math.max(0, region.y);
499            endY = Math.min(source.getSizeY(), region.y + region.height);
500        }
501        // infinite C dimension ?
502        if ((c == Integer.MIN_VALUE) && (sizeC == Integer.MAX_VALUE))
503        {
504            startC = 0;
505            endC = source.getSizeC();
506        }
507        else
508        {
509            startC = Math.max(0, c);
510            endC = Math.min(source.getSizeC(), c + sizeC);
511        }
512
513        final int sizeX = endX - startX;
514        final int sizeY = endY - startY;
515        final int adjSizeC = endC - startC;
516
517        if ((sizeX <= 0) || (sizeY <= 0) || (adjSizeC <= 0))
518            return null;
519
520        // adjust rectangle
521        final DataType dataType = source.getDataType_();
522        final boolean signed = dataType.isSigned();
523
524        final IcyBufferedImage result = new IcyBufferedImage(sizeX, sizeY, adjSizeC, dataType);
525        final int srcSizeX = source.getSizeX();
526
527        result.lockRaster();
528        try
529        {
530            for (int ch = startC; ch < endC; ch++)
531            {
532                final Object src = source.getDataXY(ch);
533                final Object dst = result.getDataXY(ch - startC);
534
535                int srcOffset = source.getOffset(startX, startY);
536                int dstOffset = 0;
537
538                for (int curY = 0; curY < sizeY; curY++)
539                {
540                    Array1DUtil.arrayToArray(src, srcOffset, dst, dstOffset, sizeX, signed);
541                    srcOffset += srcSizeX;
542                    dstOffset += sizeX;
543                }
544            }
545        }
546        finally
547        {
548            result.releaseRaster(true);
549        }
550
551        result.dataChanged();
552
553        return result;
554    }
555
556    /**
557     * Creates a new image which is a sub part of the source image from the specified region.
558     * 
559     * @see #getSubImage(IcyBufferedImage, Rectangle, int, int)
560     */
561    public static IcyBufferedImage getSubImage(IcyBufferedImage source, Rectangle region)
562    {
563        return getSubImage(source, region, 0, source.getSizeC());
564    }
565
566    /**
567     * @deprecated Use {@link #getSubImage(IcyBufferedImage, Rectangle, int, int)} instead.
568     */
569    @Deprecated
570    public static IcyBufferedImage getSubImage(IcyBufferedImage source, int x, int y, int c, int sizeX, int sizeY,
571            int sizeC)
572    {
573        return getSubImage(source, new Rectangle(x, y, sizeX, sizeY), c, sizeC);
574    }
575
576    /**
577     * Creates a new image which is a sub part of the source image from the specified
578     * coordinates and dimensions.
579     */
580    public static IcyBufferedImage getSubImage(IcyBufferedImage source, int x, int y, int w, int h)
581    {
582        return getSubImage(source, new Rectangle(x, y, w, h), 0, source.getSizeC());
583    }
584
585    /**
586     * Build a new single channel image (greyscale) from the specified source image channel.
587     */
588    public static IcyBufferedImage extractChannel(IcyBufferedImage source, int channel)
589    {
590        return extractChannels(source, channel);
591    }
592
593    /**
594     * @deprecated Use {@link #extractChannels(IcyBufferedImage, int[])} instead.
595     */
596    @Deprecated
597    public static IcyBufferedImage extractChannels(IcyBufferedImage source, List<Integer> channelNumbers)
598    {
599        if (source == null)
600            return null;
601
602        // create output
603        final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(),
604                channelNumbers.size(), source.getDataType_());
605        final int sizeC = source.getSizeC();
606
607        // set data from specified band
608        for (int i = 0; i < channelNumbers.size(); i++)
609        {
610            final int channel = channelNumbers.get(i).intValue();
611
612            if (channel < sizeC)
613                result.setDataXY(i, source.getDataXY(channel));
614        }
615
616        return result;
617    }
618
619    /**
620     * Build a new image from the specified source image channels.
621     */
622    public static IcyBufferedImage extractChannels(IcyBufferedImage source, int... channels)
623    {
624        if ((source == null) || (channels == null) || (channels.length == 0))
625            return null;
626
627        // create output
628        final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), channels.length,
629                source.getDataType_());
630        final int sizeC = source.getSizeC();
631
632        // set data from specified band
633        for (int i = 0; i < channels.length; i++)
634        {
635            final int channel = channels[i];
636
637            if (channel < sizeC)
638                result.setDataXY(i, source.getDataXY(channel));
639        }
640
641        return result;
642    }
643
644    /**
645     * Add empty channel(s) to the specified image and return result as a new image.
646     * 
647     * @param source
648     *        source image.
649     * @param index
650     *        position where we want to add channel(s).
651     * @param num
652     *        number of channel(s) to add.
653     */
654    public static IcyBufferedImage addChannels(IcyBufferedImage source, int index, int num)
655    {
656        if (source == null)
657            return null;
658
659        final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(),
660                source.getSizeC() + num, source.getDataType_());
661
662        for (int c = 0; c < index; c++)
663        {
664            result.copyData(source, c, c);
665            result.setColorMap(c, source.getColorMap(c), false);
666        }
667        for (int c = index; c < source.getSizeC(); c++)
668        {
669            result.copyData(source, c, c + num);
670            result.setColorMap(c + num, source.getColorMap(c), false);
671        }
672
673        return result;
674    }
675
676    /**
677     * Add an empty channel to the specified image and return result as a new image.
678     * 
679     * @param source
680     *        source image.
681     * @param index
682     *        position where we want to add channel.
683     */
684    public static IcyBufferedImage addChannel(IcyBufferedImage source, int index)
685    {
686        return addChannels(source, index, 1);
687    }
688
689    /**
690     * Add an empty channel to the specified image and return result as a new image.
691     * 
692     * @param source
693     *        source image.
694     */
695    public static IcyBufferedImage addChannel(IcyBufferedImage source)
696    {
697        return addChannels(source, source.getSizeC(), 1);
698    }
699
700    /**
701     * Return a rotated version of the source image with specified parameters.
702     * 
703     * @param source
704     *        source image
705     * @param xOrigin
706     *        X origin for the rotation
707     * @param yOrigin
708     *        Y origin for the rotation
709     * @param angle
710     *        rotation angle in radian
711     * @param filterType
712     *        filter resampling method used
713     */
714    public static IcyBufferedImage rotate(IcyBufferedImage source, double xOrigin, double yOrigin, double angle,
715            FilterType filterType)
716    {
717        if (source == null)
718            return null;
719
720        final Interpolation interpolation;
721
722        switch (filterType)
723        {
724            default:
725            case NEAREST:
726                interpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
727                break;
728
729            case BILINEAR:
730                interpolation = Interpolation.getInstance(Interpolation.INTERP_BILINEAR);
731                break;
732
733            case BICUBIC:
734                interpolation = Interpolation.getInstance(Interpolation.INTERP_BICUBIC);
735                break;
736        }
737
738        // use JAI scaler (use a copy to avoid source alteration)
739        final RenderedOp renderedOp = RotateDescriptor.create(getCopy(source), Float.valueOf((float) xOrigin),
740                Float.valueOf((float) yOrigin), Float.valueOf((float) angle), interpolation, null,
741                new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(BorderExtender.BORDER_ZERO)));
742
743        return IcyBufferedImage.createFrom(renderedOp, source.isSignedDataType());
744    }
745
746    /**
747     * Return a rotated version of the source image with specified parameters.
748     * 
749     * @param source
750     *        source image
751     * @param angle
752     *        rotation angle in radian
753     * @param filterType
754     *        filter resampling method used
755     */
756    public static IcyBufferedImage rotate(IcyBufferedImage source, double angle, FilterType filterType)
757    {
758        if (source == null)
759            return null;
760
761        return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, filterType);
762    }
763
764    /**
765     * Return a rotated version of the source image with specified parameters.
766     * 
767     * @param source
768     *        source image
769     * @param angle
770     *        rotation angle in radian
771     */
772    public static IcyBufferedImage rotate(IcyBufferedImage source, double angle)
773    {
774        if (source == null)
775            return null;
776
777        return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, FilterType.BILINEAR);
778    }
779
780    /**
781     * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are
782     * divided by 2).<br>
783     * This function is specifically optimized for factor 2 down scaling.
784     * 
785     * @param input
786     *        input image byte data array
787     * @param sizeX
788     *        width of source image
789     * @param sizeY
790     *        height of source image
791     * @param signed
792     *        consider input byte data as signed (only meaningful when filter is enabled)
793     * @param filter
794     *        enable pixel blending for better representation of the down sampled result image
795     *        (otherwise nearest neighbor is used)
796     * @param output
797     *        output buffer (single dimension array with same data type as source image data array).
798     */
799    static void downscaleBy2(byte[] input, int sizeX, int sizeY, boolean signed, boolean filter, byte[] output)
800    {
801        final int halfSizeX = sizeX / 2;
802        final int halfSizeY = sizeY / 2;
803
804        if (filter)
805        {
806            int inOff = 0;
807            int outOff = 0;
808
809            for (int y = 0; y < halfSizeY; y++)
810            {
811                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
812                {
813                    int val;
814
815                    if (signed)
816                    {
817                        // accumulate 4 pixels
818                        val = input[inOffset + 0];
819                        val += input[inOffset + 1];
820                        val += input[inOffset + sizeX + 0];
821                        val += input[inOffset + sizeX + 1];
822                    }
823                    else
824                    {
825                        // accumulate 4 pixels
826                        val = input[inOffset + 0] & 0xFF;
827                        val += input[inOffset + 1] & 0xFF;
828                        val += input[inOffset + sizeX + 0] & 0xFF;
829                        val += input[inOffset + sizeX + 1] & 0xFF;
830                    }
831
832                    // divide by 4
833                    val >>= 2;
834
835                    // store result
836                    output[outOff++] = (byte) val;
837                }
838
839                inOff += sizeX * 2;
840            }
841        }
842        else
843        {
844            int inOff = 0;
845            int outOff = 0;
846
847            for (int y = 0; y < halfSizeY; y++)
848            {
849                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
850                    output[outOff++] = input[inOffset];
851
852                inOff += sizeX * 2;
853            }
854        }
855    }
856
857    /**
858     * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are
859     * divided by 2).<br>
860     * This function is specifically optimized for factor 2 down scaling.
861     * 
862     * @param input
863     *        input image byte data array
864     * @param sizeX
865     *        width of source image
866     * @param sizeY
867     *        height of source image
868     * @param signed
869     *        consider input byte data as signed (only meaningful when filter is enabled)
870     * @param filter
871     *        enable pixel blending for better representation of the down sampled result image
872     *        (otherwise nearest neighbor is used)
873     * @param output
874     *        output buffer (single dimension array with same data type as source image data array).
875     */
876    static void downscaleBy2(short[] input, int sizeX, int sizeY, boolean signed, boolean filter, short[] output)
877    {
878        final int halfSizeX = sizeX / 2;
879        final int halfSizeY = sizeY / 2;
880
881        if (filter)
882        {
883            int inOff = 0;
884            int outOff = 0;
885
886            for (int y = 0; y < halfSizeY; y++)
887            {
888                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
889                {
890                    int val;
891
892                    if (signed)
893                    {
894                        // accumulate 4 pixels
895                        val = input[inOffset + 0];
896                        val += input[inOffset + 1];
897                        val += input[inOffset + sizeX + 0];
898                        val += input[inOffset + sizeX + 1];
899                    }
900                    else
901                    {
902                        // accumulate 4 pixels
903                        val = input[inOffset + 0] & 0xFFFF;
904                        val += input[inOffset + 1] & 0xFFFF;
905                        val += input[inOffset + sizeX + 0] & 0xFFFF;
906                        val += input[inOffset + sizeX + 1] & 0xFFFF;
907                    }
908
909                    // divide by 4
910                    val >>= 2;
911
912                    // store result
913                    output[outOff++] = (short) val;
914                }
915
916                inOff += sizeX * 2;
917            }
918        }
919        else
920        {
921            int inOff = 0;
922            int outOff = 0;
923
924            for (int y = 0; y < halfSizeY; y++)
925            {
926                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
927                    output[outOff++] = input[inOffset];
928
929                inOff += sizeX * 2;
930            }
931        }
932    }
933
934    /**
935     * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are
936     * divided by 2).<br>
937     * This function is specifically optimized for factor 2 down scaling.
938     * 
939     * @param input
940     *        input image byte data array
941     * @param sizeX
942     *        width of source image
943     * @param sizeY
944     *        height of source image
945     * @param signed
946     *        consider input byte data as signed (only meaningful when filter is enabled)
947     * @param filter
948     *        enable pixel blending for better representation of the down sampled result image
949     *        (otherwise nearest neighbor is used)
950     * @param output
951     *        output buffer (single dimension array with same data type as source image data array).
952     */
953    static void downscaleBy2(int[] input, int sizeX, int sizeY, boolean signed, boolean filter, int[] output)
954    {
955        final int halfSizeX = sizeX / 2;
956        final int halfSizeY = sizeY / 2;
957
958        if (filter)
959        {
960            int inOff = 0;
961            int outOff = 0;
962
963            for (int y = 0; y < halfSizeY; y++)
964            {
965                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
966                {
967                    long val;
968
969                    if (signed)
970                    {
971                        // accumulate 4 pixels
972                        val = input[inOffset + 0];
973                        val += input[inOffset + 1];
974                        val += input[inOffset + sizeX + 0];
975                        val += input[inOffset + sizeX + 1];
976                    }
977                    else
978                    {
979                        // accumulate 4 pixels
980                        val = input[inOffset + 0] & 0xFFFFFFFFL;
981                        val += input[inOffset + 1] & 0xFFFFFFFFL;
982                        val += input[inOffset + sizeX + 0] & 0xFFFFFFFFL;
983                        val += input[inOffset + sizeX + 1] & 0xFFFFFFFFL;
984                    }
985
986                    // divide by 4
987                    val >>= 2;
988
989                    // store result
990                    output[outOff++] = (int) val;
991                }
992
993                inOff += sizeX * 2;
994            }
995        }
996        else
997        {
998            int inOff = 0;
999            int outOff = 0;
1000
1001            for (int y = 0; y < halfSizeY; y++)
1002            {
1003                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
1004                    output[outOff++] = input[inOffset];
1005
1006                inOff += sizeX * 2;
1007            }
1008        }
1009    }
1010
1011    /**
1012     * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are
1013     * divided by 2).<br>
1014     * This function is specifically optimized for factor 2 down scaling.
1015     * 
1016     * @param input
1017     *        input image byte data array
1018     * @param sizeX
1019     *        width of source image
1020     * @param sizeY
1021     *        height of source image
1022     * @param filter
1023     *        enable pixel blending for better representation of the down sampled result image
1024     *        (otherwise nearest neighbor is used)
1025     * @param output
1026     *        output buffer (single dimension array with same data type as source image data array).
1027     */
1028    static void downscaleBy2(float[] input, int sizeX, int sizeY, boolean filter, float[] output)
1029    {
1030        final int halfSizeX = sizeX / 2;
1031        final int halfSizeY = sizeY / 2;
1032
1033        if (filter)
1034        {
1035            int inOff = 0;
1036            int outOff = 0;
1037
1038            for (int y = 0; y < halfSizeY; y++)
1039            {
1040                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
1041                {
1042                    float val;
1043
1044                    // accumulate 4 pixels
1045                    val = input[inOffset + 0];
1046                    val += input[inOffset + 1];
1047                    val += input[inOffset + sizeX + 0];
1048                    val += input[inOffset + sizeX + 1];
1049                    // divide by 4
1050                    val /= 4f;
1051
1052                    // store result
1053                    output[outOff++] = val;
1054                }
1055
1056                inOff += sizeX * 2;
1057            }
1058        }
1059        else
1060        {
1061            int inOff = 0;
1062            int outOff = 0;
1063
1064            for (int y = 0; y < halfSizeY; y++)
1065            {
1066                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
1067                    output[outOff++] = input[inOffset];
1068
1069                inOff += sizeX * 2;
1070            }
1071        }
1072    }
1073
1074    /**
1075     * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are
1076     * divided by 2).<br>
1077     * This function is specifically optimized for factor 2 down scaling.
1078     * 
1079     * @param input
1080     *        input image byte data array
1081     * @param sizeX
1082     *        width of source image
1083     * @param sizeY
1084     *        height of source image
1085     * @param filter
1086     *        enable pixel blending for better representation of the down sampled result image
1087     *        (otherwise nearest neighbor is used)
1088     * @param output
1089     *        output buffer (single dimension array with same data type as source image data array).
1090     */
1091    static void downscaleBy2(double[] input, int sizeX, int sizeY, boolean filter, double[] output)
1092    {
1093        final int halfSizeX = sizeX / 2;
1094        final int halfSizeY = sizeY / 2;
1095
1096        if (filter)
1097        {
1098            int inOff = 0;
1099            int outOff = 0;
1100
1101            for (int y = 0; y < halfSizeY; y++)
1102            {
1103                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
1104                {
1105                    double val;
1106
1107                    // accumulate 4 pixels
1108                    val = input[inOffset + 0];
1109                    val += input[inOffset + 1];
1110                    val += input[inOffset + sizeX + 0];
1111                    val += input[inOffset + sizeX + 1];
1112                    // divide by 4
1113                    val /= 4d;
1114
1115                    // store result
1116                    output[outOff++] = val;
1117                }
1118
1119                inOff += sizeX * 2;
1120            }
1121        }
1122        else
1123        {
1124            int inOff = 0;
1125            int outOff = 0;
1126
1127            for (int y = 0; y < halfSizeY; y++)
1128            {
1129                for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2)
1130                    output[outOff++] = input[inOffset];
1131
1132                inOff += sizeX * 2;
1133            }
1134        }
1135    }
1136
1137    /**
1138     * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are
1139     * divided by 2).<br>
1140     * This function is specifically optimized for factor 2 down scaling.
1141     * 
1142     * @param input
1143     *        input image data array (single dimension)
1144     * @param sizeX
1145     *        width of source image
1146     * @param sizeY
1147     *        height of source image
1148     * @param signed
1149     *        consider input byte data as signed (only meaningful when filter is enabled)
1150     * @param filter
1151     *        enable pixel blending for better representation of the down sampled result image
1152     *        (otherwise nearest neighbor is used)
1153     * @param output
1154     *        output buffer (single dimension array with same data type as source image data array).
1155     *        If set to <code>null</code> a new array is allocated.
1156     * @return output buffer containing the down scaled version of input image data.
1157     */
1158    public static Object downscaleBy2(Object input, int sizeX, int sizeY, boolean signed, boolean filter, Object output)
1159    {
1160        if (input == null)
1161            return output;
1162
1163        final ArrayType arrayType = ArrayUtil.getArrayType(input);
1164        final Object result = ArrayUtil.allocIfNull(output, arrayType, (sizeX / 2) * (sizeY / 2));
1165
1166        switch (arrayType.getDataType().getJavaType())
1167        {
1168            case BYTE:
1169                downscaleBy2((byte[]) input, sizeX, sizeY, signed, filter, (byte[]) result);
1170                break;
1171
1172            case SHORT:
1173                downscaleBy2((short[]) input, sizeX, sizeY, signed, filter, (short[]) result);
1174                break;
1175
1176            case INT:
1177                downscaleBy2((int[]) input, sizeX, sizeY, signed, filter, (int[]) result);
1178                break;
1179
1180            case FLOAT:
1181                downscaleBy2((float[]) input, sizeX, sizeY, filter, (float[]) result);
1182                break;
1183
1184            case DOUBLE:
1185                downscaleBy2((double[]) input, sizeX, sizeY, filter, (double[]) result);
1186                break;
1187        }
1188
1189        return result;
1190    }
1191
1192    /**
1193     * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are
1194     * divided by 2).<br>
1195     * This function is specifically optimized for factor 2 down scaling.
1196     * 
1197     * @param input
1198     *        input image data array (single dimension)
1199     * @param sizeX
1200     *        width of source image
1201     * @param sizeY
1202     *        height of source image
1203     * @param signed
1204     *        consider input byte data as signed (only meaningful when filter is enabled)
1205     * @param filter
1206     *        enable pixel blending for better representation of the down sampled result image
1207     *        (otherwise nearest neighbor is used)
1208     * @return output buffer containing the down scaled version of input image data.
1209     */
1210    public static Object downscaleBy2(Object input, int sizeX, int sizeY, boolean signed, boolean filter)
1211    {
1212        return downscaleBy2(input, sizeX, sizeY, signed, filter, null);
1213    }
1214
1215    /**
1216     * Returns a down scaled by a factor of 2 of the input image (X and Y resolution are divided by
1217     * 2).<br>
1218     * This function is specifically optimized for factor 2 down scaling.
1219     * 
1220     * @param input
1221     *        input image
1222     * @param filter
1223     *        enable pixel blending for better representation of the down sampled result image
1224     *        (otherwise nearest neighbor is used)
1225     * @param output
1226     *        output image receiving the result (should be of same type as input image with X and Y
1227     *        resolution divided
1228     *        by 2). If set to <code>null</code> a new image is created.
1229     */
1230    public static IcyBufferedImage downscaleBy2(IcyBufferedImage input, boolean filter, IcyBufferedImage output)
1231    {
1232        if (input == null)
1233            return output;
1234
1235        final IcyBufferedImage result;
1236        final int sizeX = input.getSizeX();
1237        final int sizeY = input.getSizeY();
1238        final int sizeC = input.getSizeC();
1239
1240        if (output != null)
1241            result = output;
1242        else
1243            // create an empty image with specified size and current colormodel description
1244            result = new IcyBufferedImage(sizeX / 2, sizeY / 2, input.getIcyColorModel());
1245
1246        for (int c = 0; c < sizeC; c++)
1247            downscaleBy2(input.getDataXY(c), sizeX, sizeY, input.isSignedDataType(), filter, result.getDataXY(c));
1248
1249        return result;
1250    }
1251
1252    /**
1253     * Returns a down scaled by a factor of 2 of the input image (X and Y resolution are divided by
1254     * 2).<br>
1255     * This function is specifically optimized for factor 2 down scaling.
1256     * 
1257     * @param input
1258     *        input image
1259     * @param filter
1260     *        enable pixel blending for better representation of the down sampled result image
1261     *        (otherwise nearest neighbor is used)
1262     */
1263    public static IcyBufferedImage downscaleBy2(IcyBufferedImage input, boolean filter)
1264    {
1265        return downscaleBy2(input, filter, null);
1266    }
1267
1268    /**
1269     * Down scale the specified image with the given down scale factor.<br>
1270     * If down scale factor equals <code>0</code> then the input image is directly returned.
1271     * 
1272     * @param source
1273     *        input image
1274     * @param filter
1275     *        enable pixel blending for better representation of the down sampled result image
1276     *        (otherwise nearest neighbor is used)
1277     * @param level
1278     *        number of downscale to process: scale level = 1/2^level
1279     * @return scaled image or source image is scale level equals <code>0</code>
1280     */
1281    public static IcyBufferedImage downscaleBy2(IcyBufferedImage source, boolean filter, int level)
1282    {
1283        IcyBufferedImage result = source;
1284        int it = level;
1285
1286        // process fast down scaling
1287        while (it-- > 0)
1288            result = IcyBufferedImageUtil.downscaleBy2(result, true);
1289
1290        return result;
1291    }
1292
1293    /**
1294     * Return a copy of the source image with specified size, alignment rules and filter type.
1295     * 
1296     * @param source
1297     *        source image
1298     * @param resizeContent
1299     *        indicate if content should be resized or not (empty area are 0 filled)
1300     * @param xAlign
1301     *        horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br>
1302     *        (used only if resizeContent is false)
1303     * @param yAlign
1304     *        vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br>
1305     *        (used only if resizeContent is false)
1306     * @param filterType
1307     *        filter method used for scale (used only if resizeContent is true)
1308     */
1309    public static IcyBufferedImage scale(IcyBufferedImage source, int width, int height, boolean resizeContent,
1310            int xAlign, int yAlign, FilterType filterType)
1311    {
1312        if (source == null)
1313            return null;
1314
1315        final IcyBufferedImage result;
1316        final int srcW = source.getWidth();
1317        final int srcH = source.getHeight();
1318        final boolean resize = resizeContent && ((width != srcW) || (height != srcH));
1319
1320        // no content resize ?
1321        if (!resize)
1322        {
1323            final int xt;
1324            final int yt;
1325
1326            // calculate translation values
1327            final int dx = width - srcW;
1328            switch (xAlign)
1329            {
1330                default:
1331                case SwingConstants.LEFT:
1332                    xt = 0;
1333                    break;
1334
1335                case SwingConstants.CENTER:
1336                    xt = dx / 2;
1337                    break;
1338
1339                case SwingConstants.RIGHT:
1340                    xt = dx;
1341                    break;
1342            }
1343
1344            final int dy = height - srcH;
1345            switch (yAlign)
1346            {
1347                default:
1348                case SwingConstants.TOP:
1349                    yt = 0;
1350                    break;
1351
1352                case SwingConstants.CENTER:
1353                    yt = dy / 2;
1354                    break;
1355
1356                case SwingConstants.BOTTOM:
1357                    yt = dy;
1358                    break;
1359            }
1360
1361            // create an empty image with specified size and current colormodel description
1362            result = new IcyBufferedImage(width, height, source.getIcyColorModel());
1363            // copy data from current image to specified destination
1364            result.copyData(source, null, new Point(xt, yt));
1365        }
1366        else
1367        {
1368            final Float xScale = Float.valueOf((float) width / srcW);
1369            final Float yScale = Float.valueOf((float) height / srcH);
1370            final Interpolation interpolation;
1371
1372            switch (filterType)
1373            {
1374                default:
1375                case NEAREST:
1376                    interpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST);
1377                    break;
1378
1379                case BILINEAR:
1380                    interpolation = Interpolation.getInstance(Interpolation.INTERP_BILINEAR);
1381                    break;
1382
1383                case BICUBIC:
1384                    interpolation = Interpolation.getInstance(Interpolation.INTERP_BICUBIC);
1385                    break;
1386            }
1387
1388            // use a copy as JAI may alter source data
1389            final IcyBufferedImage srcCopy = getCopy(source);
1390
1391            // better to lock raster during JAI operation
1392            srcCopy.lockRaster();
1393            try
1394            {
1395                // use JAI scaler
1396                final RenderedOp renderedOp = ScaleDescriptor.create(srcCopy, xScale, yScale, Float.valueOf(0f),
1397                        Float.valueOf(0f), interpolation, new RenderingHints(JAI.KEY_BORDER_EXTENDER,
1398                                BorderExtender.createInstance(BorderExtender.BORDER_COPY)));
1399
1400                // get result
1401                result = IcyBufferedImage.createFrom(renderedOp, source.isSignedDataType());
1402            }
1403            finally
1404            {
1405                srcCopy.releaseRaster(false);
1406            }
1407        }
1408
1409        return result;
1410    }
1411
1412    /**
1413     * Return a copy of the image with specified size.<br>
1414     * By default the FilterType.BILINEAR is used as filter method if resizeContent is true
1415     * 
1416     * @param source
1417     *        source image
1418     * @param resizeContent
1419     *        indicate if content should be resized or not (empty area are 0 filled)
1420     * @param xAlign
1421     *        horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br>
1422     *        (used only if resizeContent is false)
1423     * @param yAlign
1424     *        vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br>
1425     *        (used only if resizeContent is false)
1426     */
1427    public static IcyBufferedImage scale(IcyBufferedImage source, int width, int height, boolean resizeContent,
1428            int xAlign, int yAlign)
1429    {
1430        return scale(source, width, height, resizeContent, xAlign, yAlign, FilterType.BILINEAR);
1431    }
1432
1433    /**
1434     * Return a copy of the image with specified size<br>
1435     * 
1436     * @param source
1437     *        source image
1438     * @param filterType
1439     *        filter method used for scale (used only if resizeContent is true)
1440     */
1441    public static IcyBufferedImage scale(IcyBufferedImage source, int width, int height, FilterType filterType)
1442    {
1443        return scale(source, width, height, true, 0, 0, filterType);
1444    }
1445
1446    /**
1447     * Return a copy of the image with specified size.<br>
1448     * By default the FilterType.BILINEAR is used as filter method.
1449     */
1450    public static IcyBufferedImage scale(IcyBufferedImage source, int width, int height)
1451    {
1452        return scale(source, width, height, FilterType.BILINEAR);
1453    }
1454
1455    /**
1456     * Translate image internal data of specified channel by the specified (x,y) vector.<br>
1457     * Data going "outside" image bounds is lost.
1458     */
1459    public static void translate(IcyBufferedImage source, int dx, int dy, int channel)
1460    {
1461        if (source == null)
1462            return;
1463
1464        // nothing to do
1465        if ((dx == 0) && (dy == 0))
1466            return;
1467
1468        final int sizeX = source.getSizeX();
1469        final int sizeY = source.getSizeY();
1470
1471        // limit to sizeX / sizeY
1472        final int adx = Math.min(Math.max(-sizeX, dx), sizeX);
1473        final int ady = Math.min(Math.max(-sizeY, dy), sizeY);
1474
1475        final int fromFill;
1476        final int toFill;
1477        final int fromCopy;
1478        final int toCopy;
1479        final int wCopy;
1480
1481        if (adx < 0)
1482        {
1483            fromCopy = -adx;
1484            toCopy = 0;
1485            wCopy = sizeX + adx;
1486            fromFill = wCopy;
1487            toFill = sizeX;
1488        }
1489        else
1490        {
1491            fromFill = 0;
1492            toFill = adx;
1493            fromCopy = fromFill;
1494            toCopy = toFill;
1495            wCopy = sizeX - adx;
1496        }
1497
1498        source.lockRaster();
1499        try
1500        {
1501            final Object data = source.getDataXY(channel);
1502
1503            if (ady < 0)
1504            {
1505                final int hCopy = sizeY + ady;
1506                int toOffset = 0;
1507                int fromOffset = -ady * sizeX;
1508
1509                // copy
1510                for (int y = 0; y < hCopy; y++)
1511                {
1512                    // copy first
1513                    Array1DUtil.innerCopy(data, fromOffset + fromCopy, toOffset + toCopy, wCopy);
1514                    // then fill
1515                    Array1DUtil.fill(data, toOffset + fromFill, toOffset + toFill, 0d);
1516                    // adjust offset
1517                    fromOffset += sizeX;
1518                    toOffset += sizeX;
1519                }
1520                // fill
1521                Array1DUtil.fill(data, toOffset, fromOffset, 0d);
1522            }
1523            else
1524            {
1525                final int hCopy = sizeY - ady;
1526                int toOffset = (sizeY - 1) * sizeX;
1527                int fromOffset = toOffset - (ady * sizeX);
1528
1529                // copy
1530                for (int y = 0; y < hCopy; y++)
1531                {
1532                    // copy first
1533                    Array1DUtil.innerCopy(data, fromOffset + fromCopy, toOffset + toCopy, wCopy);
1534                    // then fill
1535                    Array1DUtil.fill(data, toOffset + fromFill, toOffset + toFill, 0d);
1536                    // adjust offset
1537                    fromOffset -= sizeX;
1538                    toOffset -= sizeX;
1539                }
1540                // fill
1541                Array1DUtil.fill(data, 0, 0 + (ady * sizeX), 0d);
1542            }
1543        }
1544        finally
1545        {
1546            source.releaseRaster(true);
1547        }
1548
1549        // notify data changed
1550        source.dataChanged();
1551    }
1552
1553    /**
1554     * Translate image internal data (all channels) by the specified (x,y) vector.<br>
1555     * Data going "outside" image bounds is lost.
1556     */
1557    public static void translate(IcyBufferedImage source, int dx, int dy)
1558    {
1559        if (source != null)
1560        {
1561            final int sizeC = source.getSizeC();
1562
1563            source.beginUpdate();
1564            try
1565            {
1566                for (int c = 0; c < sizeC; c++)
1567                    translate(source, dx, dy, c);
1568            }
1569            finally
1570            {
1571                source.endUpdate();
1572            }
1573        }
1574    }
1575}