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