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.image.FImage; 033import org.openimaj.image.processing.resize.ResizeProcessor.PixelContribution; 034import org.openimaj.image.processing.resize.ResizeProcessor.PixelContributions; 035import org.openimaj.image.processing.resize.filters.TriangleFilter; 036import org.openimaj.image.processor.SinglebandImageProcessor; 037 038 039/** 040 * A copy of the {@link ResizeProcessor} which speeds up the resize operation 041 * between images of a given size to another fixed size by caching the contribution 042 * calculations 043 * 044 * @author Sina Samangooei (ss@ecs.soton.ac.uk) 045 */ 046public class FixedResizeProcessor implements SinglebandImageProcessor<Float, FImage> { 047 048 private float newX; 049 private float newY; 050 private ResizeFilterFunction filterFunction; 051 private float srcX; 052 private float srcY; 053 private ImageContributions ic; 054 final float[] work; 055 056 /** 057 * The default {@link TriangleFilter} (bilinear-interpolation filter) used 058 * by instances of {@link ResizeProcessor}, unless otherwise specified. 059 */ 060 public static final ResizeFilterFunction DEFAULT_FILTER = TriangleFilter.INSTANCE; 061 062 /** 063 * Construct a fixed resize processor that will rescale the image to the given 064 * width and height with the given filter function. By default, this method 065 * will retain the image's aspect ratio. 066 * @param srcX 067 * The expected width of input images 068 * @param srcY 069 * The expected with of output images 070 * @param newX 071 * The new width of the image. 072 * @param newY 073 * The new height of the image. 074 * @param ff 075 * The filter function to use. 076 */ 077 public FixedResizeProcessor(float srcX, float srcY, float newX, float newY, ResizeFilterFunction ff) { 078 this.srcX = srcX; 079 this.srcY = srcY; 080 this.newX = newX; 081 this.newY = newY; 082 this.filterFunction = ff; 083 prepareResample(true); 084 this.work = new float[(int)newY]; 085 } 086 087 /** 088 * Construct a fixed resize processor that will rescale the image to the given 089 * width and height with the default filter function. By default, this 090 * method will retain the image's aspect ratio which means that the 091 * resulting image may have dimensions less than those specified here. 092 * @param srcX 093 * The expected width of input images 094 * @param srcY 095 * The expected height of input images 096 * @param newX 097 * The new width of the image. 098 * @param newY 099 * 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}