001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.processing.resize;
031
032import org.openimaj.citation.annotation.Reference;
033import org.openimaj.citation.annotation.ReferenceType;
034import org.openimaj.image.FImage;
035import org.openimaj.image.Image;
036import org.openimaj.image.processing.resize.filters.TriangleFilter;
037import org.openimaj.image.processor.SinglebandImageProcessor;
038import org.openimaj.math.geometry.shape.Rectangle;
039
040/**
041 * Image processor and utility methods that can resize images.
042 * <p>
043 * Based on <code>filter_rcg.c</code> by Dale Schumacher and Ray Gardener from
044 * Graphics Gems III, with improvements from TwelveMonkeys and ImageMagick,
045 * which in-particular fix normalisation problems.
046 * 
047 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
048 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
049 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
050 */
051@Reference(
052                type = ReferenceType.Incollection,
053                author = { "Schumacher, Dale" },
054                title = "Graphics Gems III",
055                year = "1992",
056                pages = { "8", "", "16" },
057                chapter = "General Filtered Image Rescaling",
058                url = "http://dl.acm.org/citation.cfm?id=130745.130747",
059                editor = { "Kirk, David" },
060                publisher = "Academic Press Professional, Inc.",
061                customData = {
062                                "isbn", "0-12-409671-9",
063                                "numpages", "9",
064                                "acmid", "130747",
065                                "address", "San Diego, CA, USA"
066                })
067public class ResizeProcessor implements SinglebandImageProcessor<Float, FImage> {
068        /**
069         * The resize mode to use.
070         * 
071         * @author Sina Samangooei (ss@ecs.soton.ac.uk)
072         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
073         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
074         * 
075         * @created 4 Apr 2011
076         */
077        public static enum Mode {
078                /** Double the size of the image using bilinear interpolation */
079                DOUBLE,
080                /** Halve the size of the image, by sampling alternate pixels */
081                HALF,
082                /** Scale the image using the given factors */
083                SCALE,
084                /** Resize the image preserving aspect ratio */
085                ASPECT_RATIO,
086                /** Resize the image to fit */
087                FIT,
088                /**
089                 * Resize to so that the longest side is at most the given maximum.
090                 * Images smaller than the max size are unchanged.
091                 */
092                MAX,
093                /**
094                 * Resize to so that the area is at most the given maximum. Images with
095                 * an area smaller than the max area are unchanged.
096                 */
097                MAX_AREA,
098                /** Lazyness operator to allow the quick switching off of resize filters **/
099                NONE,
100        }
101
102        /** The resize mode to use. */
103        private Mode mode = null;
104
105        /** The amount to scale the image by */
106        private float amount = 0;
107
108        /** The new width of the image */
109        private float newX;
110
111        /** The new height of the image */
112        private float newY;
113
114        /** The resize filter function to use */
115        private ResizeFilterFunction filterFunction;
116
117        /**
118         * The default {@link TriangleFilter} (bilinear-interpolation filter) used
119         * by instances of {@link ResizeProcessor}, unless otherwise specified.
120         */
121        public static final ResizeFilterFunction DEFAULT_FILTER = TriangleFilter.INSTANCE;
122
123        /**
124         * Constructor that takes the resize mode. Use this function if you only
125         * want to {@link Mode#DOUBLE double} or {@link Mode#HALF halve} the image
126         * size.
127         * 
128         * @param mode
129         *            The resize mode.
130         */
131        public ResizeProcessor(Mode mode) {
132                this.mode = mode;
133                this.filterFunction = DEFAULT_FILTER;
134        }
135
136        /**
137         * Constructor a resize processor that will rescale the image by a given
138         * scale factor using the given filter function.
139         * 
140         * @param amount
141         *            The amount to scale the image by
142         * @param ff
143         *            The resize filter function to use.
144         */
145        public ResizeProcessor(float amount, ResizeFilterFunction ff) {
146                this.mode = Mode.SCALE;
147                this.amount = amount;
148                this.filterFunction = ff;
149        }
150
151        /**
152         * Construct a resize processor that will rescale the image to the given
153         * width and height with the given filter function. By default, this method
154         * will retain the image's aspect ratio.
155         * 
156         * @param newX
157         *            The new width of the image.
158         * @param newY
159         *            The new height of the image.
160         * @param ff
161         *            The filter function to use.
162         */
163        public ResizeProcessor(float newX, float newY, ResizeFilterFunction ff) {
164                this.mode = Mode.ASPECT_RATIO;
165                this.newX = newX;
166                this.newY = newY;
167                this.filterFunction = ff;
168        }
169
170        /**
171         * Constructor a resize processor that will rescale the image by a given
172         * scale factor using the default filter function.
173         * 
174         * @param amount
175         *            The amount to scale the image by
176         */
177        public ResizeProcessor(float amount) {
178                this(amount, DEFAULT_FILTER);
179        }
180
181        /**
182         * Construct a resize processor that will rescale the image to the given
183         * width and height with the default filter function. By default, this
184         * method will retain the image's aspect ratio which means that the
185         * resulting image may have dimensions less than those specified here.
186         * 
187         * @param newX
188         *            The new width of the image.
189         * @param newY
190         *            The new height of the image.
191         */
192        public ResizeProcessor(float newX, float newY) {
193                this(newX, newY, DEFAULT_FILTER);
194        }
195
196        /**
197         * Construct a resize processor that will rescale images that are taller or
198         * wider than the given size such that their biggest side is equal to the
199         * given size. Images that have both sides smaller than the given size will
200         * be unchanged.
201         * 
202         * @param maxSize
203         *            The maximum allowable height or width
204         */
205        public ResizeProcessor(int maxSize) {
206                this.mode = Mode.MAX;
207                this.newX = maxSize;
208                this.newY = maxSize;
209                this.filterFunction = DEFAULT_FILTER;
210        }
211
212        /**
213         * Construct a resize processor that will rescale images that are either
214         * bigger than a maximum area or are taller or wider than the given size
215         * such that their biggest side is equal to the given size. Images that have
216         * a smaller area or both sides smaller than the given size will be
217         * unchanged.
218         * 
219         * @param maxSizeArea
220         *            The maximum allowable area, or height or width
221         * @param area
222         *            If true, then the limit is the area; false means limit is
223         *            longest side.
224         */
225        public ResizeProcessor(int maxSizeArea, boolean area) {
226                this.mode = area ? Mode.MAX_AREA : Mode.MAX;
227                this.newX = maxSizeArea;
228                this.newY = maxSizeArea;
229        }
230
231        /**
232         * Construct a resize processor that will rescale the image to the given
233         * width and height (optionally maintaining aspect ratio) with the default
234         * filter function. If <code>aspectRatio</code> is false the image will be
235         * stretched to fit within the new width and height. If
236         * <code>aspectRatio</code> is set to true, the resulting images may have
237         * dimensions less than those specified here.
238         * 
239         * @param newX
240         *            The new width of the image.
241         * @param newY
242         *            The new height of the image.
243         * @param aspectRatio
244         *            Whether to maintain the aspect ratio or not
245         */
246        public ResizeProcessor(int newX, int newY, boolean aspectRatio) {
247                this(newX, newY, DEFAULT_FILTER);
248
249                if (aspectRatio)
250                        this.mode = Mode.ASPECT_RATIO;
251                else
252                        this.mode = Mode.FIT;
253        }
254
255        /**
256         * Construct a resize processor that will rescale the image to the given
257         * width and height (optionally maintaining aspect ratio) with the given
258         * filter function. If <code>aspectRatio</code> is false the image will be
259         * stretched to fit within the new width and height. If
260         * <code>aspectRatio</code> is set to true, the resulting images may have
261         * dimensions less than those specified here.
262         * 
263         * @param newX
264         *            The new width of the image.
265         * @param newY
266         *            The new height of the image.
267         * @param aspectRatio
268         *            Whether to maintain the aspect ratio or not
269         * @param filterf
270         *            The filter function
271         */
272        public ResizeProcessor(int newX, int newY, boolean aspectRatio, ResizeFilterFunction filterf) {
273                this(newX, newY, filterf);
274
275                if (aspectRatio)
276                        this.mode = Mode.ASPECT_RATIO;
277                else
278                        this.mode = Mode.FIT;
279        }
280
281        /**
282         * {@inheritDoc}
283         * 
284         * @see org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj.image.Image)
285         */
286        @Override
287        public void processImage(FImage image) {
288                switch (this.mode) {
289                case DOUBLE:
290                        internalDoubleSize(image);
291                        break;
292                case HALF:
293                        internalHalfSize(image);
294                        break;
295                case FIT:
296                        zoomInplace(image, (int) newX, (int) newY, filterFunction);
297                        break;
298                case SCALE:
299                        newX = image.width * amount;
300                        newY = image.height * amount;
301                case ASPECT_RATIO:
302                        resample(image, (int) newX, (int) newY, true, filterFunction);
303                        break;
304                case MAX:
305                        resizeMax(image, (int) newX, filterFunction);
306                        break;
307                case MAX_AREA:
308                        resizeMaxArea(image, (int) newX, filterFunction);
309                        break;
310                case NONE:
311                        return;
312                default:
313                        zoomInplace(image, (int) newX, (int) newY, this.filterFunction);
314                }
315        }
316
317        /**
318         * Set the filter function used by the filter
319         * 
320         * @param filterFunction
321         *            the filter function
322         */
323        public void setFilterFunction(ResizeFilterFunction filterFunction) {
324                this.filterFunction = filterFunction;
325        }
326
327        /**
328         * Resize an image such that its biggest size is at most as big as the given
329         * size. Images whose sides are smaller than the given size are untouched.
330         * 
331         * @param image
332         *            the image to resize
333         * @param maxDim
334         *            the maximum allowable length for the longest side.
335         * @param filterf
336         *            The filter function
337         * @return the input image, appropriately resized.
338         */
339        public static FImage resizeMax(FImage image, int maxDim, ResizeFilterFunction filterf) {
340                final int width = image.width;
341                final int height = image.height;
342
343                int newWidth, newHeight;
344                if (width < maxDim && height < maxDim) {
345                        return image;
346                } else if (width < height) {
347                        newHeight = maxDim;
348                        final float resizeRatio = ((float) maxDim / (float) height);
349                        newWidth = (int) (width * resizeRatio);
350                } else {
351                        newWidth = maxDim;
352                        final float resizeRatio = ((float) maxDim / (float) width);
353                        newHeight = (int) (height * resizeRatio);
354                }
355
356                zoomInplace(image, newWidth, newHeight, filterf);
357
358                return image;
359        }
360
361        /**
362         * Resize an image such that its area size is at most as big as the given
363         * area. Images whose ares are smaller than the given area are untouched.
364         * 
365         * @param image
366         *            the image to resize
367         * @param maxArea
368         *            the maximum allowable area.
369         * @param filterf
370         *            The filter function
371         * @return the input image, appropriately resized.
372         */
373        public static FImage resizeMaxArea(FImage image, int maxArea, ResizeFilterFunction filterf) {
374                final int width = image.width;
375                final int height = image.height;
376                final int area = width * height;
377
378                if (area < maxArea) {
379                        return image;
380                } else {
381                        final float whRatio = width / height;
382                        final int newWidth = (int) Math.sqrt(maxArea * whRatio);
383                        final int newHeight = maxArea / newWidth;
384
385                        zoomInplace(image, newWidth, newHeight, filterf);
386
387                        return image;
388                }
389        }
390
391        /**
392         * Resize an image such that its biggest size is at most as big as the given
393         * size. Images whose sides are smaller than the given size are untouched.
394         * 
395         * @param image
396         *            the image to resize
397         * @param maxDim
398         *            the maximum allowable length for the longest side.
399         * @return the input image, resized appropriately
400         */
401        public static FImage resizeMax(FImage image, int maxDim) {
402                final int width = image.width;
403                final int height = image.height;
404
405                int newWidth, newHeight;
406                if (width < maxDim && height < maxDim) {
407                        return image;
408                } else if (width < height) {
409                        newHeight = maxDim;
410                        final float resizeRatio = ((float) maxDim / (float) height);
411                        newWidth = (int) (width * resizeRatio);
412                } else {
413                        newWidth = maxDim;
414                        final float resizeRatio = ((float) maxDim / (float) width);
415                        newHeight = (int) (height * resizeRatio);
416                }
417
418                zoomInplace(image, newWidth, newHeight);
419
420                return image;
421        }
422
423        /**
424         * Resize an image such that its area size is at most as big as the given
425         * area. Images whose ares are smaller than the given area are untouched.
426         * 
427         * @param image
428         *            the image to resize
429         * @param maxArea
430         *            the maximum allowable area.
431         * @return the input image, resized appropriately
432         */
433        public static FImage resizeMaxArea(FImage image, int maxArea) {
434                final int width = image.width;
435                final int height = image.height;
436                final int area = width * height;
437
438                if (area < maxArea) {
439                        return image;
440                } else {
441                        final float whRatio = width / height;
442                        final int newWidth = (int) Math.sqrt(maxArea * whRatio);
443                        final int newHeight = maxArea / newWidth;
444
445                        zoomInplace(image, newWidth, newHeight);
446
447                        return image;
448                }
449        }
450
451        /**
452         * Double the size of the image.
453         * 
454         * @param <I>
455         *            the image type
456         * 
457         * @param image
458         *            The image to double in size
459         * @return a copy of the original image with twice the size
460         */
461        public static <I extends Image<?, I> & SinglebandImageProcessor.Processable<Float, FImage, I>> I doubleSize(I image) {
462                return image.process(new ResizeProcessor(Mode.DOUBLE));
463        }
464
465        /**
466         * Double the size of the image.
467         * 
468         * @param image
469         *            The image to double in size
470         * @return a copy of the original image with twice the size
471         */
472        public static FImage doubleSize(FImage image) {
473                int nheight, nwidth;
474                float im[][], tmp[][];
475                FImage newimage;
476
477                nheight = 2 * image.height - 2;
478                nwidth = 2 * image.width - 2;
479                newimage = new FImage(nwidth, nheight);
480                im = image.pixels;
481                tmp = newimage.pixels;
482
483                for (int y = 0; y < image.height - 1; y++) {
484                        for (int x = 0; x < image.width - 1; x++) {
485                                final int y2 = 2 * y;
486                                final int x2 = 2 * x;
487                                tmp[y2][x2] = im[y][x];
488                                tmp[y2 + 1][x2] = 0.5f * (im[y][x] + im[y + 1][x]);
489                                tmp[y2][x2 + 1] = 0.5f * (im[y][x] + im[y][x + 1]);
490                                tmp[y2 + 1][x2 + 1] = 0.25f * (im[y][x] + im[y + 1][x] + im[y][x + 1] + im[y + 1][x + 1]);
491                        }
492                }
493                return newimage;
494        }
495
496        protected static void internalDoubleSize(FImage image) {
497                image.internalAssign(doubleSize(image));
498        }
499
500        /**
501         * Halve the size of the image.
502         * 
503         * @param <I>
504         * 
505         * @param image
506         *            The image halve in size
507         * @return a copy of the input image with half the size
508         */
509        public static <I extends Image<?, I> & SinglebandImageProcessor.Processable<Float, FImage, I>> I halfSize(I image) {
510                return image.process(new ResizeProcessor(Mode.HALF));
511        }
512
513        /**
514         * Halve the size of the image. Note that this method just samples every
515         * other pixel and will produce aliasing unless the image has been
516         * pre-filtered.
517         * 
518         * @param image
519         *            The image halve in size
520         * @return a copy the the image with half the size
521         */
522        public static FImage halfSize(FImage image) {
523                int newheight, newwidth;
524                float im[][], tmp[][];
525                FImage newimage;
526
527                newheight = image.height / 2;
528                newwidth = image.width / 2;
529                newimage = new FImage(newwidth, newheight);
530                im = image.pixels;
531                tmp = newimage.pixels;
532
533                for (int y = 0, yi = 0; y < newheight; y++, yi += 2) {
534                        for (int x = 0, xi = 0; x < newwidth; x++, xi += 2) {
535                                tmp[y][x] = im[yi][xi];
536                        }
537                }
538
539                return newimage;
540        }
541
542        protected static void internalHalfSize(FImage image) {
543                image.internalAssign(halfSize(image));
544        }
545
546        /**
547         * Returns a new image that is a resampled version of the given image.
548         * 
549         * @param in
550         *            The source image
551         * @param newX
552         *            The new width of the image
553         * @param newY
554         *            The new height of the image
555         * @return A new {@link FImage}
556         */
557        public static FImage resample(FImage in, int newX, int newY) {
558                return resample(in.clone(), newX, newY, false);
559        }
560
561        /**
562         * Resamples the given image returning it as a reference. If
563         * <code>aspect</code> is true, the aspect ratio of the image will be
564         * retained, which means newX or newY could be smaller than given here. The
565         * dimensions of the new image will not be larger than newX or newY.
566         * Side-affects the given image.
567         * 
568         * @param in
569         *            The source image
570         * @param newX
571         *            The new width of the image
572         * @param newY
573         *            The new height of the image
574         * @param aspect
575         *            Whether to maintain the aspect ratio
576         * @return the input image, resized appropriately
577         */
578        public static FImage resample(FImage in, int newX, int newY, boolean aspect) {
579                // Work out the size of the resampled image
580                // if the aspect ratio is set to true
581                int nx = newX;
582                int ny = newY;
583                if (aspect) {
584                        if (ny > nx)
585                                nx = (int) Math.round((in.width * ny) / (double) in.height);
586                        else
587                                ny = (int) Math.round((in.height * nx) / (double) in.width);
588                }
589
590                zoomInplace(in, nx, ny);
591                return in;
592        }
593
594        /**
595         * Resamples the given image returning it as a reference. If
596         * <code>aspect</code> is true, the aspect ratio of the image will be
597         * retained, which means newX or newY could be smaller than given here. The
598         * dimensions of the new image will not be larger than newX or newY.
599         * Side-affects the given image.
600         * 
601         * @param in
602         *            The source image
603         * @param newX
604         *            The new width of the image
605         * @param newY
606         *            The new height of the image
607         * @param aspect
608         *            Whether to maintain the aspect ratio
609         * @param filterf
610         *            The filter function
611         * @return the input image, resized appropriately
612         */
613        public static FImage resample(FImage in, int newX, int newY, boolean aspect, ResizeFilterFunction filterf)
614        {
615                // Work out the size of the resampled image
616                // if the aspect ratio is set to true
617                int nx = newX;
618                int ny = newY;
619                if (aspect) {
620                        if (ny > nx)
621                                nx = (int) Math.round((in.width * ny) / (double) in.height);
622                        else
623                                ny = (int) Math.round((in.height * nx) / (double) in.width);
624                }
625
626                zoomInplace(in, nx, ny, filterf);
627                return in;
628        }
629
630        /**
631         * For the port of the zoom function
632         * 
633         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
634         * 
635         */
636        static class PixelContribution {
637                /** Index of the pixel */
638                int pixel;
639
640                double weight;
641        }
642
643        /**
644         * For the port of the zoom function
645         * 
646         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
647         * 
648         */
649        static class PixelContributions {
650                int numberOfContributors;
651
652                PixelContribution[] contributions;
653        }
654
655        /**
656         * Calculates the filter weights for a single target column. contribX->p
657         * must be freed afterwards.
658         * 
659         * @param contribX
660         *            Receiver of contrib info
661         * @param xscale
662         *            Horizontal zooming scale
663         * @param fwidth
664         *            Filter sampling width
665         * @param dstwidth
666         *            Target bitmap width
667         * @param srcwidth
668         *            Source bitmap width
669         * @param filterf
670         *            Filter processor
671         * @param i
672         *            Pixel column in source bitmap being processed
673         * 
674         * @returns -1 if error, 0 otherwise.
675         */
676        private static void calc_x_contrib(PixelContributions contribX, double xscale, double fwidth, int dstwidth,
677                        int srcwidth, ResizeFilterFunction filterf, int i)
678        {
679                double width;
680                double fscale;
681                double center;
682                double weight;
683
684                if (xscale < 1.0) {
685                        /* Shrinking image */
686                        width = fwidth / xscale;
687                        fscale = 1.0 / xscale;
688
689                        if (width <= .5) {
690                                // Reduce to point sampling.
691                                width = .5 + 1.0e-6;
692                                fscale = 1.0;
693                        }
694
695                        contribX.numberOfContributors = 0;
696                        contribX.contributions = new PixelContribution[(int) (width * 2.0 + 1.0)];
697
698                        center = i / xscale;
699                        final int left = (int) Math.ceil(center - width);// Note: Assumes
700                                                                                                                                // width <= .5
701                        final int right = (int) Math.floor(center + width);
702
703                        double density = 0.0;
704
705                        for (int j = left; j <= right; j++) {
706                                weight = center - j;
707                                weight = filterf.filter(weight / fscale) / fscale;
708                                int n;
709                                if (j < 0) {
710                                        n = -j;
711                                }
712                                else if (j >= srcwidth) {
713                                        n = (srcwidth - j) + srcwidth - 1;
714                                }
715                                else {
716                                        n = j;
717                                }
718
719                                /**/
720                                if (n >= srcwidth) {
721                                        n = n % srcwidth;
722                                }
723                                else if (n < 0) {
724                                        n = srcwidth - 1;
725                                }
726                                /**/
727
728                                final int k = contribX.numberOfContributors++;
729                                contribX.contributions[k] = new PixelContribution();
730                                contribX.contributions[k].pixel = n;
731                                contribX.contributions[k].weight = weight;
732
733                                density += weight;
734
735                        }
736
737                        if ((density != 0.0) && (density != 1.0)) {
738                                // Normalize.
739                                density = 1.0 / density;
740                                for (int k = 0; k < contribX.numberOfContributors; k++) {
741                                        contribX.contributions[k].weight *= density;
742                                }
743                        }
744                }
745                else {
746                        /* Expanding image */
747                        contribX.numberOfContributors = 0;
748                        contribX.contributions = new PixelContribution[(int) (fwidth * 2.0 + 1.0)];
749
750                        center = i / xscale;
751                        final int left = (int) Math.ceil(center - fwidth);
752                        final int right = (int) Math.floor(center + fwidth);
753
754                        for (int j = left; j <= right; j++) {
755                                weight = center - j;
756                                weight = filterf.filter(weight);
757
758                                int n;
759                                if (j < 0) {
760                                        n = -j;
761                                }
762                                else if (j >= srcwidth) {
763                                        n = (srcwidth - j) + srcwidth - 1;
764                                }
765                                else {
766                                        n = j;
767                                }
768
769                                /**/
770                                if (n >= srcwidth) {
771                                        n = n % srcwidth;
772                                }
773                                else if (n < 0) {
774                                        n = srcwidth - 1;
775                                }
776                                /**/
777
778                                final int k = contribX.numberOfContributors++;
779                                contribX.contributions[k] = new PixelContribution();
780                                contribX.contributions[k].pixel = n;
781                                contribX.contributions[k].weight = weight;
782                        }
783                }
784        }/* calcXContrib */
785
786        /**
787         * Resizes an image.
788         * 
789         * @param in
790         *            The source image
791         * @param newX
792         *            The desired width of the image
793         * @param newY
794         *            The desired height of the image
795         * @return the input image, resized appropriately
796         */
797        public static FImage zoomInplace(FImage in, int newX, int newY) {
798                final ResizeFilterFunction filter = DEFAULT_FILTER;
799                return zoomInplace(in, newX, newY, filter);
800        }
801
802        /**
803         * Resizes an image.
804         * 
805         * @param newX
806         *            New width of the image
807         * @param newY
808         *            New height of the image
809         * @param in
810         *            The source image
811         * @param filterf
812         *            The filter function
813         * @return the input image, resized appropriately
814         */
815        public static FImage zoomInplace(FImage in, int newX, int newY, ResizeFilterFunction filterf) {
816                final FImage dst = new FImage(newX, newY);
817                zoom(in, dst, filterf);
818                in.internalAssign(dst);
819                return in;
820        }
821
822        /**
823         * Resizes bitmaps while resampling them.
824         * 
825         * @param dst
826         *            Destination Image
827         * @param in
828         *            Source Image
829         * @param filterf
830         *            Filter to use
831         * 
832         * @return the destination image
833         */
834        public static FImage zoom(FImage in, FImage dst, ResizeFilterFunction filterf) {
835                final int dstWidth = dst.getWidth();
836                final int dstHeight = dst.getHeight();
837
838                final int srcWidth = in.getWidth();
839                final int srcHeight = in.getHeight();
840
841                final double xscale = (double) dstWidth / (double) srcWidth;
842                final double yscale = (double) dstHeight / (double) srcHeight;
843
844                /* create intermediate column to hold horizontal dst column zoom */
845                final float[] work = new float[in.height];
846
847                final PixelContributions[] contribY = new PixelContributions[dstHeight];
848                for (int i = 0; i < contribY.length; i++) {
849                        contribY[i] = new PixelContributions();
850                }
851
852                final float maxValue = in.max();
853
854                // TODO: What to do when fwidth > srcHeight or dstHeight
855                final double fwidth = filterf.getSupport();
856                if (yscale < 1.0) {
857                        double width = fwidth / yscale;
858                        double fscale = 1.0 / yscale;
859
860                        if (width <= .5) {
861                                // Reduce to point sampling.
862                                width = .5 + 1.0e-6;
863                                fscale = 1.0;
864                        }
865
866                        for (int i = 0; i < dstHeight; i++) {
867                                contribY[i].contributions = new PixelContribution[(int) (width * 2.0 + 1)];
868                                contribY[i].numberOfContributors = 0;
869
870                                final double center = i / yscale;
871                                final int left = (int) Math.ceil(center - width);
872                                final int right = (int) Math.floor(center + width);
873
874                                double density = 0.0;
875
876                                for (int j = left; j <= right; j++) {
877                                        double weight = center - j;
878                                        weight = filterf.filter(weight / fscale) / fscale;
879                                        int n;
880                                        if (j < 0) {
881                                                n = -j;
882                                        }
883                                        else if (j >= srcHeight) {
884                                                n = (srcHeight - j) + srcHeight - 1;
885                                        }
886                                        else {
887                                                n = j;
888                                        }
889
890                                        /**/
891                                        if (n >= srcHeight) {
892                                                n = n % srcHeight;
893                                        }
894                                        else if (n < 0) {
895                                                n = srcHeight - 1;
896                                        }
897                                        /**/
898
899                                        final int k = contribY[i].numberOfContributors++;
900                                        contribY[i].contributions[k] = new PixelContribution();
901                                        contribY[i].contributions[k].pixel = n;
902                                        contribY[i].contributions[k].weight = weight;
903
904                                        density += weight;
905                                }
906
907                                if ((density != 0.0) && (density != 1.0)) {
908                                        // Normalize.
909                                        density = 1.0 / density;
910                                        for (int k = 0; k < contribY[i].numberOfContributors; k++) {
911                                                contribY[i].contributions[k].weight *= density;
912                                        }
913                                }
914                        }
915                }
916                else {
917                        for (int i = 0; i < dstHeight; ++i) {
918                                contribY[i].contributions = new PixelContribution[(int) (fwidth * 2 + 1)];
919                                contribY[i].numberOfContributors = 0;
920
921                                final double center = i / yscale;
922                                final double left = Math.ceil(center - fwidth);
923                                final double right = Math.floor(center + fwidth);
924                                for (int j = (int) left; j <= right; ++j) {
925                                        double weight = center - j;
926                                        weight = filterf.filter(weight);
927                                        int n;
928                                        if (j < 0) {
929                                                n = -j;
930                                        }
931                                        else if (j >= srcHeight) {
932                                                n = (srcHeight - j) + srcHeight - 1;
933                                        }
934                                        else {
935                                                n = j;
936                                        }
937
938                                        /**/
939                                        if (n >= srcHeight) {
940                                                n = n % srcHeight;
941                                        }
942                                        else if (n < 0) {
943                                                n = srcHeight - 1;
944                                        }
945                                        /**/
946
947                                        final int k = contribY[i].numberOfContributors++;
948                                        contribY[i].contributions[k] = new PixelContribution();
949                                        contribY[i].contributions[k].pixel = n;
950                                        contribY[i].contributions[k].weight = weight;
951                                }
952                        }
953                }
954
955                for (int xx = 0; xx < dstWidth; xx++) {
956                        final PixelContributions contribX = new PixelContributions();
957                        calc_x_contrib(contribX, xscale, fwidth, dst.width, in.width, filterf, xx);
958
959                        /* Apply horiz filter to make dst column in tmp. */
960                        for (int k = 0; k < srcHeight; k++) {
961                                double weight = 0.0;
962                                boolean bPelDelta = false;
963                                // TODO: This line throws index out of bounds, if the image
964                                // is smaller than filter.support()
965                                final double pel = in.pixels[k][contribX.contributions[0].pixel];
966                                for (int j = 0; j < contribX.numberOfContributors; j++) {
967                                        final double pel2 = j == 0 ? pel : in.pixels[k][contribX.contributions[j].pixel];
968                                        if (pel2 != pel) {
969                                                bPelDelta = true;
970                                        }
971                                        weight += pel2 * contribX.contributions[j].weight;
972                                }
973                                weight = bPelDelta ? Math.round(weight * 255) / 255f : pel;
974
975                                if (weight < 0) {
976                                        weight = 0;
977                                }
978                                else if (weight > maxValue) {
979                                        weight = maxValue;
980                                }
981
982                                work[k] = (float) weight;
983                        }/* next row in temp column */
984
985                        /*
986                         * The temp column has been built. Now stretch it vertically into
987                         * dst column.
988                         */
989                        for (int i = 0; i < dstHeight; i++) {
990                                double weight = 0.0;
991                                boolean bPelDelta = false;
992                                final double pel = work[contribY[i].contributions[0].pixel];
993
994                                for (int j = 0; j < contribY[i].numberOfContributors; j++) {
995                                        // TODO: This line throws index out of bounds, if the
996                                        // image is smaller than filter.support()
997                                        final double pel2 = j == 0 ? pel : work[contribY[i].contributions[j].pixel];
998                                        if (pel2 != pel) {
999                                                bPelDelta = true;
1000                                        }
1001                                        weight += pel2 * contribY[i].contributions[j].weight;
1002                                }
1003                                weight = bPelDelta ? Math.round(weight * 255) / 255f : pel;
1004
1005                                if (weight < 0) {
1006                                        weight = 0;
1007                                }
1008                                else if (weight > maxValue) {
1009                                        weight = maxValue;
1010                                }
1011
1012                                dst.pixels[i][xx] = (float) weight;
1013                        } /* next dst row */
1014                } /* next dst column */
1015
1016                return dst;
1017        }
1018
1019        /**
1020         * Draws one portion of an image into another, resampling as necessary using
1021         * the default filter function.
1022         * 
1023         * @param dst
1024         *            Destination Image
1025         * @param in
1026         *            Source Image
1027         * @param inRect
1028         *            the location of pixels in the source image
1029         * @param dstRect
1030         *            the destination of pixels in the destination image
1031         * @return the destination image
1032         */
1033        public static FImage zoom(FImage in, Rectangle inRect, FImage dst, Rectangle dstRect) {
1034                return zoom(in, inRect, dst, dstRect, DEFAULT_FILTER);
1035        }
1036
1037        /**
1038         * Draws one portion of an image into another, resampling as necessary.
1039         * 
1040         * @param dst
1041         *            Destination Image
1042         * @param in
1043         *            Source Image
1044         * @param inRect
1045         *            the location of pixels in the source image
1046         * @param dstRect
1047         *            the destination of pixels in the destination image
1048         * @param filterf
1049         *            Filter to use
1050         * 
1051         * @return the destination image
1052         */
1053        public static FImage zoom(FImage in, Rectangle inRect, FImage dst, Rectangle dstRect, ResizeFilterFunction filterf)
1054        {
1055                // First some sanity checking!
1056                if (!in.getBounds().isInside(inRect) || !dst.getBounds().isInside(dstRect))
1057                        throw new IllegalArgumentException("Bad bounds");
1058
1059                double xscale, yscale; /* zoom scale factors */
1060                int n; /* pixel number */
1061                double center, left, right; /* filter calculation variables */
1062                double width, fscale;
1063                double weight; /* filter calculation variables */
1064                boolean bPelDelta;
1065                float pel, pel2;
1066                PixelContributions contribX;
1067
1068                // This is a convenience
1069                final FImage src = in;
1070                final int srcX = (int) inRect.x;
1071                final int srcY = (int) inRect.y;
1072                final int srcWidth = (int) inRect.width;
1073                final int srcHeight = (int) inRect.height;
1074
1075                final int dstX = (int) dstRect.x;
1076                final int dstY = (int) dstRect.y;
1077                final int dstWidth = (int) dstRect.width;
1078                final int dstHeight = (int) dstRect.height;
1079
1080                final float maxValue = in.max();
1081
1082                /* create intermediate column to hold horizontal dst column zoom */
1083                final Float[] work = new Float[srcHeight];
1084
1085                xscale = (double) dstWidth / (double) srcWidth;
1086
1087                /* Build y weights */
1088                /* pre-calculate filter contributions for a column */
1089                final PixelContributions[] contribY = new PixelContributions[dstHeight];
1090
1091                yscale = (double) dstHeight / (double) srcHeight;
1092                final double fwidth = filterf.getSupport();
1093
1094                if (yscale < 1.0) {
1095                        width = fwidth / yscale;
1096                        fscale = 1.0 / yscale;
1097                        double density = 0;
1098                        for (int i = 0; i < dstHeight; ++i) {
1099                                contribY[i] = new PixelContributions();
1100                                contribY[i].numberOfContributors = 0;
1101                                contribY[i].contributions = new PixelContribution[(int) Math.round(width * 2 + 1)];
1102
1103                                center = i / yscale;
1104                                left = Math.ceil(center - width);
1105                                right = Math.floor(center + width);
1106                                for (int j = (int) left; j <= right; ++j) {
1107                                        weight = center - j;
1108                                        weight = filterf.filter(weight / fscale) / fscale;
1109
1110                                        if (j < 0) {
1111                                                n = -j;
1112                                        } else if (j >= srcHeight) {
1113                                                n = (srcHeight - j) + srcHeight - 1;
1114                                        } else {
1115                                                n = j;
1116                                        }
1117
1118                                        final int k = contribY[i].numberOfContributors++;
1119                                        contribY[i].contributions[k] = new PixelContribution();
1120                                        contribY[i].contributions[k].pixel = n;
1121                                        contribY[i].contributions[k].weight = weight;
1122                                        density += weight;
1123                                }
1124
1125                                if ((density != 0.0) && (density != 1.0)) {
1126                                        // Normalize.
1127                                        density = 1.0 / density;
1128                                        for (int k = 0; k < contribY[i].numberOfContributors; k++) {
1129                                                contribY[i].contributions[k].weight *= density;
1130                                        }
1131                                }
1132                        }
1133                } else {
1134                        for (int i = 0; i < dstHeight; ++i) {
1135                                contribY[i] = new PixelContributions();
1136                                contribY[i].numberOfContributors = 0;
1137                                contribY[i].contributions = new PixelContribution[(int) Math.round(fwidth * 2 + 1)];
1138
1139                                center = i / yscale;
1140                                left = Math.ceil(center - fwidth);
1141                                right = Math.floor(center + fwidth);
1142                                for (int j = (int) left; j <= right; ++j) {
1143                                        weight = center - j;
1144                                        weight = filterf.filter(weight);
1145
1146                                        if (j < 0) {
1147                                                n = -j;
1148                                        } else if (j >= srcHeight) {
1149                                                n = (srcHeight - j) + srcHeight - 1;
1150                                        } else {
1151                                                n = j;
1152                                        }
1153
1154                                        final int k = contribY[i].numberOfContributors++;
1155                                        contribY[i].contributions[k] = new PixelContribution();
1156                                        contribY[i].contributions[k].pixel = n;
1157                                        contribY[i].contributions[k].weight = weight;
1158                                }
1159                        }
1160                }
1161
1162                for (int xx = 0; xx < dstWidth; xx++) {
1163                        contribX = new PixelContributions();
1164                        calc_x_contrib(contribX, xscale, fwidth, dstWidth, srcWidth, filterf, xx);
1165
1166                        /* Apply horz filter to make dst column in tmp. */
1167                        for (int k = 0; k < srcHeight; ++k) {
1168                                weight = 0.0;
1169                                bPelDelta = false;
1170
1171                                pel = src.pixels[k + srcY][contribX.contributions[0].pixel + srcX];
1172
1173                                for (int j = 0; j < contribX.numberOfContributors; ++j) {
1174                                        pel2 = src.pixels[k + srcY][contribX.contributions[j].pixel + srcX];
1175                                        if (pel2 != pel)
1176                                                bPelDelta = true;
1177                                        weight += pel2 * contribX.contributions[j].weight;
1178                                }
1179                                weight = bPelDelta ? Math.round(weight * 255f) / 255f : pel;
1180
1181                                if (weight < 0) {
1182                                        weight = 0;
1183                                }
1184                                else if (weight > maxValue) {
1185                                        weight = maxValue;
1186                                }
1187
1188                                work[k] = (float) weight;
1189                        } /* next row in temp column */
1190
1191                        /*
1192                         * The temp column has been built. Now stretch it vertically into
1193                         * dst column.
1194                         */
1195                        for (int i = 0; i < dstHeight; ++i) {
1196                                weight = 0.0;
1197                                bPelDelta = false;
1198                                pel = work[contribY[i].contributions[0].pixel];
1199
1200                                for (int j = 0; j < contribY[i].numberOfContributors; ++j) {
1201                                        pel2 = work[contribY[i].contributions[j].pixel];
1202                                        if (pel2 != pel)
1203                                                bPelDelta = true;
1204                                        weight += pel2 * contribY[i].contributions[j].weight;
1205                                }
1206
1207                                weight = bPelDelta ? Math.round(weight * 255f) / 255f : pel;
1208
1209                                if (weight < 0) {
1210                                        weight = 0;
1211                                }
1212                                else if (weight > maxValue) {
1213                                        weight = maxValue;
1214                                }
1215
1216                                dst.pixels[i + dstY][xx + dstX] = (float) weight;
1217
1218                        } /* next dst row */
1219                } /* next dst column */
1220
1221                return dst;
1222        } /* zoom */
1223}