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}