View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package org.openimaj.image.processing.resize;
31  
32  import org.openimaj.citation.annotation.Reference;
33  import org.openimaj.citation.annotation.ReferenceType;
34  import org.openimaj.image.FImage;
35  import org.openimaj.image.Image;
36  import org.openimaj.image.processing.resize.filters.TriangleFilter;
37  import org.openimaj.image.processor.SinglebandImageProcessor;
38  import org.openimaj.math.geometry.shape.Rectangle;
39  
40  /**
41   * Image processor and utility methods that can resize images.
42   * <p>
43   * Based on <code>filter_rcg.c</code> by Dale Schumacher and Ray Gardener from
44   * Graphics Gems III, with improvements from TwelveMonkeys and ImageMagick,
45   * which in-particular fix normalisation problems.
46   *
47   * @author David Dupplaw (dpd@ecs.soton.ac.uk)
48   * @author Sina Samangooei (ss@ecs.soton.ac.uk)
49   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
50   */
51  @Reference(
52  		type = ReferenceType.Incollection,
53  		author = { "Schumacher, Dale" },
54  		title = "Graphics Gems III",
55  		year = "1992",
56  		pages = { "8", "", "16" },
57  		chapter = "General Filtered Image Rescaling",
58  		url = "http://dl.acm.org/citation.cfm?id=130745.130747",
59  		editor = { "Kirk, David" },
60  		publisher = "Academic Press Professional, Inc.",
61  		customData = {
62  				"isbn", "0-12-409671-9",
63  				"numpages", "9",
64  				"acmid", "130747",
65  				"address", "San Diego, CA, USA"
66  		})
67  public class ResizeProcessor implements SinglebandImageProcessor<Float, FImage> {
68  	/**
69  	 * The resize mode to use.
70  	 *
71  	 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
72  	 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
73  	 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
74  	 *
75  	 * @created 4 Apr 2011
76  	 */
77  	public static enum Mode {
78  		/** Double the size of the image using bilinear interpolation */
79  		DOUBLE,
80  		/** Halve the size of the image, by sampling alternate pixels */
81  		HALF,
82  		/** Scale the image using the given factors */
83  		SCALE,
84  		/** Resize the image preserving aspect ratio */
85  		ASPECT_RATIO,
86  		/** Resize the image to fit */
87  		FIT,
88  		/**
89  		 * Resize to so that the longest side is at most the given maximum.
90  		 * Images smaller than the max size are unchanged.
91  		 */
92  		MAX,
93  		/**
94  		 * Resize to so that the area is at most the given maximum. Images with
95  		 * an area smaller than the max area are unchanged.
96  		 */
97  		MAX_AREA,
98  		/** Lazyness operator to allow the quick switching off of resize filters **/
99  		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 }