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 double whRatio = (double) width / (double) height;
382                        final int newWidth = (int) Math.sqrt(maxArea * whRatio);
383                        final int newHeight = (int) (newWidth / whRatio);
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                return resizeMaxArea(image, maxArea, DEFAULT_FILTER);
435        }
436
437        /**
438         * Double the size of the image.
439         *
440         * @param <I>
441         *            the image type
442         *
443         * @param image
444         *            The image to double in size
445         * @return a copy of the original image with twice the size
446         */
447        public static <I extends Image<?, I> & SinglebandImageProcessor.Processable<Float, FImage, I>> I doubleSize(I image) {
448                return image.process(new ResizeProcessor(Mode.DOUBLE));
449        }
450
451        /**
452         * Double the size of the image.
453         *
454         * @param image
455         *            The image to double in size
456         * @return a copy of the original image with twice the size
457         */
458        public static FImage doubleSize(FImage image) {
459                int nheight, nwidth;
460                float im[][], tmp[][];
461                FImage newimage;
462
463                nheight = 2 * image.height - 2;
464                nwidth = 2 * image.width - 2;
465                newimage = new FImage(nwidth, nheight);
466                im = image.pixels;
467                tmp = newimage.pixels;
468
469                for (int y = 0; y < image.height - 1; y++) {
470                        for (int x = 0; x < image.width - 1; x++) {
471                                final int y2 = 2 * y;
472                                final int x2 = 2 * x;
473                                tmp[y2][x2] = im[y][x];
474                                tmp[y2 + 1][x2] = 0.5f * (im[y][x] + im[y + 1][x]);
475                                tmp[y2][x2 + 1] = 0.5f * (im[y][x] + im[y][x + 1]);
476                                tmp[y2 + 1][x2 + 1] = 0.25f * (im[y][x] + im[y + 1][x] + im[y][x + 1] + im[y + 1][x + 1]);
477                        }
478                }
479                return newimage;
480        }
481
482        protected static void internalDoubleSize(FImage image) {
483                image.internalAssign(doubleSize(image));
484        }
485
486        /**
487         * Halve the size of the image.
488         *
489         * @param <I>
490         *
491         * @param image
492         *            The image halve in size
493         * @return a copy of the input image with half the size
494         */
495        public static <I extends Image<?, I> & SinglebandImageProcessor.Processable<Float, FImage, I>> I halfSize(I image) {
496                return image.process(new ResizeProcessor(Mode.HALF));
497        }
498
499        /**
500         * Halve the size of the image. Note that this method just samples every
501         * other pixel and will produce aliasing unless the image has been
502         * pre-filtered.
503         *
504         * @param image
505         *            The image halve in size
506         * @return a copy the the image with half the size
507         */
508        public static FImage halfSize(FImage image) {
509                int newheight, newwidth;
510                float im[][], tmp[][];
511                FImage newimage;
512
513                newheight = image.height / 2;
514                newwidth = image.width / 2;
515                newimage = new FImage(newwidth, newheight);
516                im = image.pixels;
517                tmp = newimage.pixels;
518
519                for (int y = 0, yi = 0; y < newheight; y++, yi += 2) {
520                        for (int x = 0, xi = 0; x < newwidth; x++, xi += 2) {
521                                tmp[y][x] = im[yi][xi];
522                        }
523                }
524
525                return newimage;
526        }
527
528        protected static void internalHalfSize(FImage image) {
529                image.internalAssign(halfSize(image));
530        }
531
532        /**
533         * Returns a new image that is a resampled version of the given image.
534         *
535         * @param in
536         *            The source image
537         * @param newX
538         *            The new width of the image
539         * @param newY
540         *            The new height of the image
541         * @return A new {@link FImage}
542         */
543        public static FImage resample(FImage in, int newX, int newY) {
544                return resample(in.clone(), newX, newY, false);
545        }
546
547        /**
548         * Resamples the given image returning it as a reference. If
549         * <code>aspect</code> is true, the aspect ratio of the image will be
550         * retained, which means newX or newY could be smaller than given here. The
551         * dimensions of the new image will not be larger than newX or newY.
552         * Side-affects the given image.
553         *
554         * @param in
555         *            The source image
556         * @param newX
557         *            The new width of the image
558         * @param newY
559         *            The new height of the image
560         * @param aspect
561         *            Whether to maintain the aspect ratio
562         * @return the input image, resized appropriately
563         */
564        public static FImage resample(FImage in, int newX, int newY, boolean aspect) {
565                // Work out the size of the resampled image
566                // if the aspect ratio is set to true
567                int nx = newX;
568                int ny = newY;
569                if (aspect) {
570                        if (ny > nx)
571                                nx = (int) Math.round((in.width * ny) / (double) in.height);
572                        else
573                                ny = (int) Math.round((in.height * nx) / (double) in.width);
574                }
575
576                zoomInplace(in, nx, ny);
577                return in;
578        }
579
580        /**
581         * Resamples the given image returning it as a reference. If
582         * <code>aspect</code> is true, the aspect ratio of the image will be
583         * retained, which means newX or newY could be smaller than given here. The
584         * dimensions of the new image will not be larger than newX or newY.
585         * Side-affects the given image.
586         *
587         * @param in
588         *            The source image
589         * @param newX
590         *            The new width of the image
591         * @param newY
592         *            The new height of the image
593         * @param aspect
594         *            Whether to maintain the aspect ratio
595         * @param filterf
596         *            The filter function
597         * @return the input image, resized appropriately
598         */
599        public static FImage resample(FImage in, int newX, int newY, boolean aspect, ResizeFilterFunction filterf)
600        {
601                // Work out the size of the resampled image
602                // if the aspect ratio is set to true
603                int nx = newX;
604                int ny = newY;
605                if (aspect) {
606                        if (ny > nx)
607                                nx = (int) Math.round((in.width * ny) / (double) in.height);
608                        else
609                                ny = (int) Math.round((in.height * nx) / (double) in.width);
610                }
611
612                zoomInplace(in, nx, ny, filterf);
613                return in;
614        }
615
616        /**
617         * For the port of the zoom function
618         *
619         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
620         *
621         */
622        static class PixelContribution {
623                /** Index of the pixel */
624                int pixel;
625
626                double weight;
627        }
628
629        /**
630         * For the port of the zoom function
631         *
632         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
633         *
634         */
635        static class PixelContributions {
636                int numberOfContributors;
637
638                PixelContribution[] contributions;
639        }
640
641        /**
642         * Calculates the filter weights for a single target column. contribX->p
643         * must be freed afterwards.
644         *
645         * @param contribX
646         *            Receiver of contrib info
647         * @param xscale
648         *            Horizontal zooming scale
649         * @param fwidth
650         *            Filter sampling width
651         * @param dstwidth
652         *            Target bitmap width
653         * @param srcwidth
654         *            Source bitmap width
655         * @param filterf
656         *            Filter processor
657         * @param i
658         *            Pixel column in source bitmap being processed
659         *
660         * @returns -1 if error, 0 otherwise.
661         */
662        private static void calc_x_contrib(PixelContributions contribX, double xscale, double fwidth, int dstwidth,
663                        int srcwidth, ResizeFilterFunction filterf, int i)
664        {
665                double width;
666                double fscale;
667                double center;
668                double weight;
669
670                if (xscale < 1.0) {
671                        /* Shrinking image */
672                        width = fwidth / xscale;
673                        fscale = 1.0 / xscale;
674
675                        if (width <= .5) {
676                                // Reduce to point sampling.
677                                width = .5 + 1.0e-6;
678                                fscale = 1.0;
679                        }
680
681                        contribX.numberOfContributors = 0;
682                        contribX.contributions = new PixelContribution[(int) (width * 2.0 + 1.0)];
683
684                        center = i / xscale;
685                        final int left = (int) Math.ceil(center - width);// Note: Assumes
686                        // width <= .5
687                        final int right = (int) Math.floor(center + width);
688
689                        double density = 0.0;
690
691                        for (int j = left; j <= right; j++) {
692                                weight = center - j;
693                                weight = filterf.filter(weight / fscale) / fscale;
694                                int n;
695                                if (j < 0) {
696                                        n = -j;
697                                }
698                                else if (j >= srcwidth) {
699                                        n = (srcwidth - j) + srcwidth - 1;
700                                }
701                                else {
702                                        n = j;
703                                }
704
705                                /**/
706                                if (n >= srcwidth) {
707                                        n = n % srcwidth;
708                                }
709                                else if (n < 0) {
710                                        n = srcwidth - 1;
711                                }
712                                /**/
713
714                                final int k = contribX.numberOfContributors++;
715                                contribX.contributions[k] = new PixelContribution();
716                                contribX.contributions[k].pixel = n;
717                                contribX.contributions[k].weight = weight;
718
719                                density += weight;
720
721                        }
722
723                        if ((density != 0.0) && (density != 1.0)) {
724                                // Normalize.
725                                density = 1.0 / density;
726                                for (int k = 0; k < contribX.numberOfContributors; k++) {
727                                        contribX.contributions[k].weight *= density;
728                                }
729                        }
730                }
731                else {
732                        /* Expanding image */
733                        contribX.numberOfContributors = 0;
734                        contribX.contributions = new PixelContribution[(int) (fwidth * 2.0 + 1.0)];
735
736                        center = i / xscale;
737                        final int left = (int) Math.ceil(center - fwidth);
738                        final int right = (int) Math.floor(center + fwidth);
739
740                        for (int j = left; j <= right; j++) {
741                                weight = center - j;
742                                weight = filterf.filter(weight);
743
744                                int n;
745                                if (j < 0) {
746                                        n = -j;
747                                }
748                                else if (j >= srcwidth) {
749                                        n = (srcwidth - j) + srcwidth - 1;
750                                }
751                                else {
752                                        n = j;
753                                }
754
755                                /**/
756                                if (n >= srcwidth) {
757                                        n = n % srcwidth;
758                                }
759                                else if (n < 0) {
760                                        n = srcwidth - 1;
761                                }
762                                /**/
763
764                                final int k = contribX.numberOfContributors++;
765                                contribX.contributions[k] = new PixelContribution();
766                                contribX.contributions[k].pixel = n;
767                                contribX.contributions[k].weight = weight;
768                        }
769                }
770        }/* calcXContrib */
771
772        /**
773         * Resizes an image.
774         *
775         * @param in
776         *            The source image
777         * @param newX
778         *            The desired width of the image
779         * @param newY
780         *            The desired height of the image
781         * @return the input image, resized appropriately
782         */
783        public static FImage zoomInplace(FImage in, int newX, int newY) {
784                final ResizeFilterFunction filter = DEFAULT_FILTER;
785                return zoomInplace(in, newX, newY, filter);
786        }
787
788        /**
789         * Resizes an image.
790         *
791         * @param newX
792         *            New width of the image
793         * @param newY
794         *            New height of the image
795         * @param in
796         *            The source image
797         * @param filterf
798         *            The filter function
799         * @return the input image, resized appropriately
800         */
801        public static FImage zoomInplace(FImage in, int newX, int newY, ResizeFilterFunction filterf) {
802                final FImage dst = new FImage(newX, newY);
803                zoom(in, dst, filterf);
804                in.internalAssign(dst);
805                return in;
806        }
807
808        /**
809         * Resizes bitmaps while resampling them.
810         *
811         * @param dst
812         *            Destination Image
813         * @param in
814         *            Source Image
815         * @param filterf
816         *            Filter to use
817         *
818         * @return the destination image
819         */
820        public static FImage zoom(FImage in, FImage dst, ResizeFilterFunction filterf) {
821                final int dstWidth = dst.getWidth();
822                final int dstHeight = dst.getHeight();
823
824                final int srcWidth = in.getWidth();
825                final int srcHeight = in.getHeight();
826
827                final double xscale = (double) dstWidth / (double) srcWidth;
828                final double yscale = (double) dstHeight / (double) srcHeight;
829
830                /* create intermediate column to hold horizontal dst column zoom */
831                final float[] work = new float[in.height];
832
833                final PixelContributions[] contribY = new PixelContributions[dstHeight];
834                for (int i = 0; i < contribY.length; i++) {
835                        contribY[i] = new PixelContributions();
836                }
837
838                final float maxValue = in.max();
839
840                // TODO: What to do when fwidth > srcHeight or dstHeight
841                final double fwidth = filterf.getSupport();
842                if (yscale < 1.0) {
843                        double width = fwidth / yscale;
844                        double fscale = 1.0 / yscale;
845
846                        if (width <= .5) {
847                                // Reduce to point sampling.
848                                width = .5 + 1.0e-6;
849                                fscale = 1.0;
850                        }
851
852                        for (int i = 0; i < dstHeight; i++) {
853                                contribY[i].contributions = new PixelContribution[(int) (width * 2.0 + 1)];
854                                contribY[i].numberOfContributors = 0;
855
856                                final double center = i / yscale;
857                                final int left = (int) Math.ceil(center - width);
858                                // final int right = (int) Math.floor(center + width);
859                                final int right = left + contribY[i].contributions.length - 1;
860
861                                double density = 0.0;
862                                for (int j = left; j <= right; j++) {
863                                        double weight = center - j;
864                                        weight = filterf.filter(weight / fscale) / fscale;
865                                        int n;
866                                        if (j < 0) {
867                                                n = -j;
868                                        }
869                                        else if (j >= srcHeight) {
870                                                n = (srcHeight - j) + srcHeight - 1;
871                                        }
872                                        else {
873                                                n = j;
874                                        }
875
876                                        /**/
877                                        if (n >= srcHeight) {
878                                                n = n % srcHeight;
879                                        }
880                                        else if (n < 0) {
881                                                n = srcHeight - 1;
882                                        }
883                                        /**/
884
885                                        final int k = contribY[i].numberOfContributors++;
886                                        contribY[i].contributions[k] = new PixelContribution();
887                                        contribY[i].contributions[k].pixel = n;
888                                        contribY[i].contributions[k].weight = weight;
889
890                                        density += weight;
891                                }
892
893                                if ((density != 0.0) && (density != 1.0)) {
894                                        // Normalize.
895                                        density = 1.0 / density;
896                                        for (int k = 0; k < contribY[i].numberOfContributors; k++) {
897                                                contribY[i].contributions[k].weight *= density;
898                                        }
899                                }
900                        }
901                }
902                else {
903                        for (int i = 0; i < dstHeight; ++i) {
904                                contribY[i].contributions = new PixelContribution[(int) (fwidth * 2 + 1)];
905                                contribY[i].numberOfContributors = 0;
906
907                                final double center = i / yscale;
908                                final double left = Math.ceil(center - fwidth);
909                                // final double right = Math.floor(center + fwidth);
910                                final double right = left + contribY[i].contributions.length - 1;
911                                for (int j = (int) left; j <= right; ++j) {
912                                        double weight = center - j;
913                                        weight = filterf.filter(weight);
914                                        int n;
915                                        if (j < 0) {
916                                                n = -j;
917                                        }
918                                        else if (j >= srcHeight) {
919                                                n = (srcHeight - j) + srcHeight - 1;
920                                        }
921                                        else {
922                                                n = j;
923                                        }
924
925                                        /**/
926                                        if (n >= srcHeight) {
927                                                n = n % srcHeight;
928                                        }
929                                        else if (n < 0) {
930                                                n = srcHeight - 1;
931                                        }
932                                        /**/
933
934                                        final int k = contribY[i].numberOfContributors++;
935                                        contribY[i].contributions[k] = new PixelContribution();
936                                        contribY[i].contributions[k].pixel = n;
937                                        contribY[i].contributions[k].weight = weight;
938                                }
939                        }
940                }
941
942                for (int xx = 0; xx < dstWidth; xx++) {
943                        final PixelContributions contribX = new PixelContributions();
944                        calc_x_contrib(contribX, xscale, fwidth, dst.width, in.width, filterf, xx);
945
946                        /* Apply horiz filter to make dst column in tmp. */
947                        for (int k = 0; k < srcHeight; k++) {
948                                double weight = 0.0;
949                                boolean bPelDelta = false;
950                                // TODO: This line throws index out of bounds, if the image
951                                // is smaller than filter.support()
952                                final double pel = in.pixels[k][contribX.contributions[0].pixel];
953                                for (int j = 0; j < contribX.numberOfContributors; j++) {
954                                        final double pel2 = j == 0 ? pel : in.pixels[k][contribX.contributions[j].pixel];
955                                        if (pel2 != pel) {
956                                                bPelDelta = true;
957                                        }
958                                        weight += pel2 * contribX.contributions[j].weight;
959                                }
960                                weight = bPelDelta ? Math.round(weight * 255) / 255f : pel;
961
962                                if (weight < 0) {
963                                        weight = 0;
964                                }
965                                else if (weight > maxValue) {
966                                        weight = maxValue;
967                                }
968
969                                work[k] = (float) weight;
970                        }/* next row in temp column */
971
972                        /*
973                         * The temp column has been built. Now stretch it vertically into
974                         * dst column.
975                         */
976                        for (int i = 0; i < dstHeight; i++) {
977                                double weight = 0.0;
978                                boolean bPelDelta = false;
979                                final double pel = work[contribY[i].contributions[0].pixel];
980
981                                for (int j = 0; j < contribY[i].numberOfContributors; j++) {
982                                        // TODO: This line throws index out of bounds, if the
983                                        // image is smaller than filter.support()
984                                        final double pel2 = j == 0 ? pel : work[contribY[i].contributions[j].pixel];
985                                        if (pel2 != pel) {
986                                                bPelDelta = true;
987                                        }
988                                        weight += pel2 * contribY[i].contributions[j].weight;
989                                }
990                                weight = bPelDelta ? Math.round(weight * 255) / 255f : pel;
991
992                                if (weight < 0) {
993                                        weight = 0;
994                                }
995                                else if (weight > maxValue) {
996                                        weight = maxValue;
997                                }
998
999                                dst.pixels[i][xx] = (float) weight;
1000                        } /* next dst row */
1001                } /* next dst column */
1002
1003                return dst;
1004        }
1005
1006        /**
1007         * Draws one portion of an image into another, resampling as necessary using
1008         * the default filter function.
1009         *
1010         * @param dst
1011         *            Destination Image
1012         * @param in
1013         *            Source Image
1014         * @param inRect
1015         *            the location of pixels in the source image
1016         * @param dstRect
1017         *            the destination of pixels in the destination image
1018         * @return the destination image
1019         */
1020        public static FImage zoom(FImage in, Rectangle inRect, FImage dst, Rectangle dstRect) {
1021                return zoom(in, inRect, dst, dstRect, DEFAULT_FILTER);
1022        }
1023
1024        /**
1025         * Draws one portion of an image into another, resampling as necessary.
1026         *
1027         * @param dst
1028         *            Destination Image
1029         * @param in
1030         *            Source Image
1031         * @param inRect
1032         *            the location of pixels in the source image
1033         * @param dstRect
1034         *            the destination of pixels in the destination image
1035         * @param filterf
1036         *            Filter to use
1037         *
1038         * @return the destination image
1039         */
1040        public static FImage zoom(FImage in, Rectangle inRect, FImage dst, Rectangle dstRect, ResizeFilterFunction filterf)
1041        {
1042                // First some sanity checking!
1043                if (!in.getBounds().isInside(inRect) || !dst.getBounds().isInside(dstRect))
1044                        throw new IllegalArgumentException("Bad bounds");
1045
1046                double xscale, yscale; /* zoom scale factors */
1047                int n; /* pixel number */
1048                double center, left, right; /* filter calculation variables */
1049                double width, fscale;
1050                double weight; /* filter calculation variables */
1051                boolean bPelDelta;
1052                float pel, pel2;
1053                PixelContributions contribX;
1054
1055                // This is a convenience
1056                final FImage src = in;
1057                final int srcX = (int) inRect.x;
1058                final int srcY = (int) inRect.y;
1059                final int srcWidth = (int) inRect.width;
1060                final int srcHeight = (int) inRect.height;
1061
1062                final int dstX = (int) dstRect.x;
1063                final int dstY = (int) dstRect.y;
1064                final int dstWidth = (int) dstRect.width;
1065                final int dstHeight = (int) dstRect.height;
1066
1067                final float maxValue = in.max();
1068
1069                /* create intermediate column to hold horizontal dst column zoom */
1070                final Float[] work = new Float[srcHeight];
1071
1072                xscale = (double) dstWidth / (double) srcWidth;
1073
1074                /* Build y weights */
1075                /* pre-calculate filter contributions for a column */
1076                final PixelContributions[] contribY = new PixelContributions[dstHeight];
1077
1078                yscale = (double) dstHeight / (double) srcHeight;
1079                final double fwidth = filterf.getSupport();
1080
1081                if (yscale < 1.0) {
1082                        width = fwidth / yscale;
1083                        fscale = 1.0 / yscale;
1084                        double density = 0;
1085                        for (int i = 0; i < dstHeight; ++i) {
1086                                contribY[i] = new PixelContributions();
1087                                contribY[i].numberOfContributors = 0;
1088                                contribY[i].contributions = new PixelContribution[(int) Math.round(width * 2 + 1)];
1089
1090                                center = i / yscale;
1091                                left = Math.ceil(center - width);
1092                                right = Math.floor(center + width);
1093                                for (int j = (int) left; j <= right; ++j) {
1094                                        weight = center - j;
1095                                        weight = filterf.filter(weight / fscale) / fscale;
1096
1097                                        if (j < 0) {
1098                                                n = -j;
1099                                        } else if (j >= srcHeight) {
1100                                                n = (srcHeight - j) + srcHeight - 1;
1101                                        } else {
1102                                                n = j;
1103                                        }
1104
1105                                        final int k = contribY[i].numberOfContributors++;
1106                                        contribY[i].contributions[k] = new PixelContribution();
1107                                        contribY[i].contributions[k].pixel = n;
1108                                        contribY[i].contributions[k].weight = weight;
1109                                        density += weight;
1110                                }
1111
1112                                if ((density != 0.0) && (density != 1.0)) {
1113                                        // Normalize.
1114                                        density = 1.0 / density;
1115                                        for (int k = 0; k < contribY[i].numberOfContributors; k++) {
1116                                                contribY[i].contributions[k].weight *= density;
1117                                        }
1118                                }
1119                        }
1120                } else {
1121                        for (int i = 0; i < dstHeight; ++i) {
1122                                contribY[i] = new PixelContributions();
1123                                contribY[i].numberOfContributors = 0;
1124                                contribY[i].contributions = new PixelContribution[(int) Math.round(fwidth * 2 + 1)];
1125
1126                                center = i / yscale;
1127                                left = Math.ceil(center - fwidth);
1128                                right = Math.floor(center + fwidth);
1129                                for (int j = (int) left; j <= right; ++j) {
1130                                        weight = center - j;
1131                                        weight = filterf.filter(weight);
1132
1133                                        if (j < 0) {
1134                                                n = -j;
1135                                        } else if (j >= srcHeight) {
1136                                                n = (srcHeight - j) + srcHeight - 1;
1137                                        } else {
1138                                                n = j;
1139                                        }
1140
1141                                        final int k = contribY[i].numberOfContributors++;
1142                                        contribY[i].contributions[k] = new PixelContribution();
1143                                        contribY[i].contributions[k].pixel = n;
1144                                        contribY[i].contributions[k].weight = weight;
1145                                }
1146                        }
1147                }
1148
1149                for (int xx = 0; xx < dstWidth; xx++) {
1150                        contribX = new PixelContributions();
1151                        calc_x_contrib(contribX, xscale, fwidth, dstWidth, srcWidth, filterf, xx);
1152
1153                        /* Apply horz filter to make dst column in tmp. */
1154                        for (int k = 0; k < srcHeight; ++k) {
1155                                weight = 0.0;
1156                                bPelDelta = false;
1157
1158                                pel = src.pixels[k + srcY][contribX.contributions[0].pixel + srcX];
1159
1160                                for (int j = 0; j < contribX.numberOfContributors; ++j) {
1161                                        pel2 = src.pixels[k + srcY][contribX.contributions[j].pixel + srcX];
1162                                        if (pel2 != pel)
1163                                                bPelDelta = true;
1164                                        weight += pel2 * contribX.contributions[j].weight;
1165                                }
1166                                weight = bPelDelta ? Math.round(weight * 255f) / 255f : pel;
1167
1168                                if (weight < 0) {
1169                                        weight = 0;
1170                                }
1171                                else if (weight > maxValue) {
1172                                        weight = maxValue;
1173                                }
1174
1175                                work[k] = (float) weight;
1176                        } /* next row in temp column */
1177
1178                        /*
1179                         * The temp column has been built. Now stretch it vertically into
1180                         * dst column.
1181                         */
1182                        for (int i = 0; i < dstHeight; ++i) {
1183                                weight = 0.0;
1184                                bPelDelta = false;
1185                                pel = work[contribY[i].contributions[0].pixel];
1186
1187                                for (int j = 0; j < contribY[i].numberOfContributors; ++j) {
1188                                        pel2 = work[contribY[i].contributions[j].pixel];
1189                                        if (pel2 != pel)
1190                                                bPelDelta = true;
1191                                        weight += pel2 * contribY[i].contributions[j].weight;
1192                                }
1193
1194                                weight = bPelDelta ? Math.round(weight * 255f) / 255f : pel;
1195
1196                                if (weight < 0) {
1197                                        weight = 0;
1198                                }
1199                                else if (weight > maxValue) {
1200                                        weight = maxValue;
1201                                }
1202
1203                                dst.pixels[i + dstY][xx + dstX] = (float) weight;
1204
1205                        } /* next dst row */
1206                } /* next dst column */
1207
1208                return dst;
1209        } /* zoom */
1210}