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.image.FImage;
33  import org.openimaj.image.processing.resize.ResizeProcessor.PixelContribution;
34  import org.openimaj.image.processing.resize.ResizeProcessor.PixelContributions;
35  import org.openimaj.image.processing.resize.filters.TriangleFilter;
36  import org.openimaj.image.processor.SinglebandImageProcessor;
37  
38  
39  /**
40   * A copy of the {@link ResizeProcessor} which speeds up the resize operation
41   * between images of a given size to another fixed size by caching the contribution
42   * calculations
43   * 
44   * @author Sina Samangooei (ss@ecs.soton.ac.uk)
45   */
46  public class FixedResizeProcessor implements SinglebandImageProcessor<Float, FImage> {
47  	
48  	private float newX;
49  	private float newY;
50  	private ResizeFilterFunction filterFunction;
51  	private float srcX;
52  	private float srcY;
53  	private ImageContributions ic;
54  	final float[] work;
55  	
56  	/**
57  	 * The default {@link TriangleFilter} (bilinear-interpolation filter) used
58  	 * by instances of {@link ResizeProcessor}, unless otherwise specified.
59  	 */
60  	public static final ResizeFilterFunction DEFAULT_FILTER = TriangleFilter.INSTANCE;
61  	
62  	/**
63  	 * Construct a fixed resize processor that will rescale the image to the given
64  	 * width and height with the given filter function. By default, this method
65  	 * will retain the image's aspect ratio.
66  	 * @param srcX 
67  	 * 			The expected width of input images
68  	 * @param srcY 
69  	 * 			The expected with of output images
70  	 * @param newX
71  	 *            The new width of the image.
72  	 * @param newY
73  	 *            The new height of the image.
74  	 * @param ff
75  	 *            The filter function to use.
76  	 */
77  	public FixedResizeProcessor(float srcX, float srcY, float newX, float newY, ResizeFilterFunction ff) {
78  		this.srcX = srcX;
79  		this.srcY = srcY;
80  		this.newX = newX;
81  		this.newY = newY;
82  		this.filterFunction = ff;
83  		prepareResample(true);
84  		this.work = new float[(int)newY];
85  	}
86  
87  	/**
88  	 * Construct a fixed resize processor that will rescale the image to the given
89  	 * width and height with the default filter function. By default, this
90  	 * method will retain the image's aspect ratio which means that the
91  	 * resulting image may have dimensions less than those specified here.
92  	 * @param srcX 
93  	 * 			  The expected width of input images
94  	 * @param srcY
95  	 * 			  The expected height of input images
96  	 * @param newX
97  	 *            The new width of the image.
98  	 * @param newY
99  	 *            The new height of the image.
100 	 */
101 	public FixedResizeProcessor(float srcX, float srcY, float newX, float newY) {
102 		this(srcX,srcY,newX, newY, DEFAULT_FILTER);
103 	}
104 	
105 	/**
106 	 * @param image
107 	 * 			The expected width and height of input images
108 	 * @param newX
109 	 * 			The new width of the image
110 	 * @param newY
111 	 * 			The new height of the image
112 	 */
113 	public FixedResizeProcessor(FImage image, int newX, int newY) {
114 		this(image.width,image.height,newX,newY);
115 	}
116 
117 	private void prepareResample(boolean aspect) {
118 		// Work out the size of the resampled image
119 		// if the aspect ratio is set to true
120 		int nx = (int)newX;
121 		int ny = (int)newY;
122 		if (aspect) {
123 			if (ny > nx)
124 				nx = (int) Math.round((this.srcX * ny) / (double) this.srcY);
125 			else
126 				ny = (int) Math.round((this.srcY * nx) / (double) this.srcX);
127 		}
128 		this.newX = nx;
129 		this.newY = ny;
130 
131 		this.ic = FixedResizeProcessor.prepareZoom((int)srcX,(int)srcY,(int)newX,(int)newY,this.filterFunction);
132 	}
133 	static class ImageContributions{
134 		PixelContributions[] xContributions;
135 		PixelContributions[] yContributions;
136 	}
137 	private static ImageContributions prepareZoom(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ResizeFilterFunction filterf) {
138 		final double xscale = (double) dstWidth / (double) srcWidth;
139 		final double yscale = (double) dstHeight / (double) srcHeight;
140 		
141 		final PixelContributions[] contribY = new PixelContributions[dstHeight];
142 		for (int i = 0; i < contribY.length; i++) {
143 			contribY[i] = new PixelContributions();
144 		}
145 		
146 		final PixelContributions[] contribX = new PixelContributions[dstWidth];
147 		for (int i = 0; i < contribX.length; i++) {
148 			contribX[i] = new PixelContributions();
149 		}
150 		
151 		final double fwidth = filterf.getSupport();
152 		if (yscale < 1.0) {
153 			double width = fwidth / yscale;
154 			double fscale = 1.0 / yscale;
155 
156 			if (width <= .5) {
157 				// Reduce to point sampling.
158 				width = .5 + 1.0e-6;
159 				fscale = 1.0;
160 			}
161 
162 			for (int i = 0; i < dstHeight; i++) {
163 				contribY[i].contributions = new PixelContribution[(int) (width * 2.0 + 1)];
164 				contribY[i].numberOfContributors = 0;
165 
166 				final double center = i / yscale;
167 				final int left = (int) Math.ceil(center - width);
168 				final int right = (int) Math.floor(center + width);
169 
170 				double density = 0.0;
171 
172 				for (int j = left; j <= right; j++) {
173 					double weight = center - j;
174 					weight = filterf.filter(weight / fscale) / fscale;
175 					int n;
176 					if (j < 0) {
177 						n = -j;
178 					}
179 					else if (j >= srcHeight) {
180 						n = (srcHeight - j) + srcHeight - 1;
181 					}
182 					else {
183 						n = j;
184 					}
185 
186 					/**/
187 					if (n >= srcHeight) {
188 						n = n % srcHeight;
189 					}
190 					else if (n < 0) {
191 						n = srcHeight - 1;
192 					}
193 					/**/
194 
195 					final int k = contribY[i].numberOfContributors++;
196 					contribY[i].contributions[k] = new PixelContribution();
197 					contribY[i].contributions[k].pixel = n;
198 					contribY[i].contributions[k].weight = weight;
199 
200 					density += weight;
201 				}
202 
203 				if ((density != 0.0) && (density != 1.0)) {
204 					// Normalize.
205 					density = 1.0 / density;
206 					for (int k = 0; k < contribY[i].numberOfContributors; k++) {
207 						contribY[i].contributions[k].weight *= density;
208 					}
209 				}
210 			}
211 		}
212 		else {
213 			for (int i = 0; i < dstHeight; ++i) {
214 				contribY[i].contributions = new PixelContribution[(int) (fwidth * 2 + 1)];
215 				contribY[i].numberOfContributors = 0;
216 
217 				final double center = i / yscale;
218 				final double left = Math.ceil(center - fwidth);
219 				final double right = Math.floor(center + fwidth);
220 				for (int j = (int) left; j <= right; ++j) {
221 					double weight = center - j;
222 					weight = filterf.filter(weight);
223 					int n;
224 					if (j < 0) {
225 						n = -j;
226 					}
227 					else if (j >= srcHeight) {
228 						n = (srcHeight - j) + srcHeight - 1;
229 					}
230 					else {
231 						n = j;
232 					}
233 
234 					/**/
235 					if (n >= srcHeight) {
236 						n = n % srcHeight;
237 					}
238 					else if (n < 0) {
239 						n = srcHeight - 1;
240 					}
241 					/**/
242 
243 					final int k = contribY[i].numberOfContributors++;
244 					contribY[i].contributions[k] = new PixelContribution();
245 					contribY[i].contributions[k].pixel = n;
246 					contribY[i].contributions[k].weight = weight;
247 				}
248 			}
249 		}
250 		
251 		if (xscale < 1.0) {
252 			for (int i = 0; i < dstWidth; ++i) {
253 				/* Shrinking image */
254 				double width = fwidth / xscale;
255 				double fscale = 1.0 / xscale;
256 	
257 				if (width <= .5) {
258 					// Reduce to point sampling.
259 					width = .5 + 1.0e-6;
260 					fscale = 1.0;
261 				}
262 	
263 				contribX[i].numberOfContributors = 0;
264 				contribX[i].contributions = new PixelContribution[(int) (width * 2.0 + 1.0)];
265 	
266 				double center = i / xscale;
267 				final int left = (int) Math.ceil(center - width);// Note: Assumes
268 																	// width <= .5
269 				final int right = (int) Math.floor(center + width);
270 	
271 				double density = 0.0;
272 	
273 				for (int j = left; j <= right; j++) {
274 					double weight = center - j;
275 					weight = filterf.filter(weight / fscale) / fscale;
276 					int n;
277 					if (j < 0) {
278 						n = -j;
279 					}
280 					else if (j >= srcWidth) {
281 						n = (srcWidth - j) + srcWidth - 1;
282 					}
283 					else {
284 						n = j;
285 					}
286 	
287 					/**/
288 					if (n >= srcWidth) {
289 						n = n % srcWidth;
290 					}
291 					else if (n < 0) {
292 						n = srcWidth - 1;
293 					}
294 					/**/
295 	
296 					final int k = contribX[i].numberOfContributors++;
297 					contribX[i].contributions[k] = new PixelContribution();
298 					contribX[i].contributions[k].pixel = n;
299 					contribX[i].contributions[k].weight = weight;
300 	
301 					density += weight;
302 	
303 				}
304 	
305 				if ((density != 0.0) && (density != 1.0)) {
306 					// Normalize.
307 					density = 1.0 / density;
308 					for (int k = 0; k < contribX[i].numberOfContributors; k++) {
309 						contribX[i].contributions[k].weight *= density;
310 					}
311 				}
312 			}
313 		}
314 		else {
315 			for (int i = 0; i < dstWidth; ++i) {
316 				/* Expanding image */
317 				contribX[i].numberOfContributors = 0;
318 				contribX[i].contributions = new PixelContribution[(int) (fwidth * 2.0 + 1.0)];
319 	
320 				double center = i / xscale;
321 				final int left = (int) Math.ceil(center - fwidth);
322 				final int right = (int) Math.floor(center + fwidth);
323 	
324 				for (int j = left; j <= right; j++) {
325 					double weight = center - j;
326 					weight = filterf.filter(weight);
327 	
328 					int n;
329 					if (j < 0) {
330 						n = -j;
331 					}
332 					else if (j >= srcWidth) {
333 						n = (srcWidth - j) + srcWidth - 1;
334 					}
335 					else {
336 						n = j;
337 					}
338 	
339 					/**/
340 					if (n >= srcWidth) {
341 						n = n % srcWidth;
342 					}
343 					else if (n < 0) {
344 						n = srcWidth - 1;
345 					}
346 					/**/
347 	
348 					final int k = contribX[i].numberOfContributors++;
349 					contribX[i].contributions[k] = new PixelContribution();
350 					contribX[i].contributions[k].pixel = n;
351 					contribX[i].contributions[k].weight = weight;
352 				}
353 			}
354 		}
355 		
356 		ImageContributions ic = new ImageContributions();
357 		ic.xContributions = contribX;
358 		ic.yContributions = contribY;
359 		
360 		return ic;
361 	}
362 
363 	/**
364 	 * {@inheritDoc}
365 	 * 
366 	 * @see org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj.image.Image)
367 	 */
368 	@Override
369 	public void processImage(FImage in) {
370 		if(in.width != this.srcX || in.height != srcY){
371 			throw new RuntimeException("Incompatible image type used with FixedResizeProcessor, try the normal ResizeProcessor");
372 		}
373 		/* create intermediate column to hold horizontal dst column zoom */
374 		
375 		FImage dst = new FImage((int)this.newX,(int)this.newY);
376 		final float maxValue = in.max();
377 		for (int xx = 0; xx < dst.width; xx++) {
378 			final PixelContributions contribX = this.ic.xContributions[xx];
379 
380 			/* Apply horiz filter to make dst column in tmp. */
381 			for (int k = 0; k < in.height; k++) {
382 				double weight = 0.0;
383 				boolean bPelDelta = false;
384 				// TODO: This line throws index out of bounds, if the image
385 				// is smaller than filter.support()
386 				final double pel = in.pixels[k][contribX.contributions[0].pixel];
387 				for (int j = 0; j < contribX.numberOfContributors; j++) {
388 					final double pel2 = j == 0 ? pel : in.pixels[k][contribX.contributions[j].pixel];
389 					if (pel2 != pel) {
390 						bPelDelta = true;
391 					}
392 					weight += pel2 * contribX.contributions[j].weight;
393 				}
394 				weight = bPelDelta ? Math.round(weight * 255) / 255f : pel;
395 
396 				if (weight < 0) {
397 					weight = 0;
398 				}
399 				else if (weight > maxValue) {
400 					weight = maxValue;
401 				}
402 
403 				work[k] = (float) weight;
404 			}/* next row in temp column */
405 
406 			/*
407 			 * The temp column has been built. Now stretch it vertically into
408 			 * dst column.
409 			 */
410 			for (int i = 0; i < dst.height; i++) {
411 				double weight = 0.0;
412 				boolean bPelDelta = false;
413 				final double pel = work[ic.yContributions[i].contributions[0].pixel];
414 
415 				for (int j = 0; j < ic.yContributions[i].numberOfContributors; j++) {
416 					// TODO: This line throws index out of bounds, if the
417 					// image is smaller than filter.support()
418 					final double pel2 = j == 0 ? pel : work[ic.yContributions[i].contributions[j].pixel];
419 					if (pel2 != pel) {
420 						bPelDelta = true;
421 					}
422 					weight += pel2 * ic.yContributions[i].contributions[j].weight;
423 				}
424 				weight = bPelDelta ? Math.round(weight * 255) / 255f : pel;
425 
426 				if (weight < 0) {
427 					weight = 0;
428 				}
429 				else if (weight > maxValue) {
430 					weight = maxValue;
431 				}
432 
433 				dst.pixels[i][xx] = (float) weight;
434 			} /* next dst row */
435 		} /* next dst column */
436 		
437 		in.internalAssign(dst);
438 	}
439 
440 }