001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.image; 020 021import java.awt.AlphaComposite; 022import java.awt.Graphics2D; 023import java.awt.Point; 024import java.awt.Rectangle; 025import java.awt.RenderingHints; 026import java.awt.image.BufferedImage; 027import java.util.List; 028 029import javax.media.jai.BorderExtender; 030import javax.media.jai.Interpolation; 031import javax.media.jai.JAI; 032import javax.media.jai.RenderedOp; 033import javax.media.jai.operator.RotateDescriptor; 034import javax.media.jai.operator.ScaleDescriptor; 035import javax.swing.SwingConstants; 036 037import icy.image.lut.LUT; 038import icy.math.Scaler; 039import icy.type.DataType; 040import icy.type.collection.array.Array1DUtil; 041import icy.type.collection.array.ArrayType; 042import icy.type.collection.array.ArrayUtil; 043 044/** 045 * {@link IcyBufferedImage} utilities class.<br> 046 * You can find here tools to clone, manipulate the image data type, its size... 047 * 048 * @author Stephane 049 */ 050public class IcyBufferedImageUtil 051{ 052 public static enum FilterType 053 { 054 NEAREST, BILINEAR, BICUBIC 055 }; 056 057 /** 058 * used for getARGBImage method (fast conversion) 059 */ 060 private static ARGBImageBuilder argbImageBuilder = new ARGBImageBuilder(); 061 062 /** 063 * @deprecated Use {@link IcyBufferedImage#createFrom(BufferedImage)} instead. 064 */ 065 @Deprecated 066 public static IcyBufferedImage toIcyBufferedImage(BufferedImage image) 067 { 068 return IcyBufferedImage.createFrom(image); 069 } 070 071 /** 072 * @deprecated Use {@link IcyBufferedImage#createFrom(List)} instead. 073 */ 074 @Deprecated 075 public static IcyBufferedImage toIcyBufferedImage(List<BufferedImage> images) 076 { 077 return IcyBufferedImage.createFrom(images); 078 } 079 080 /** 081 * Draw the source {@link IcyBufferedImage} into the destination {@link BufferedImage}<br> 082 * If <code>dest</code> is <code>null</code> then a new TYPE_INT_ARGB {@link BufferedImage} is 083 * returned.<br> 084 * 085 * @param source 086 * source image 087 * @param dest 088 * destination image 089 * @param lut 090 * {@link LUT} is used for color calculation (internal lut is used if null). 091 */ 092 public static BufferedImage toBufferedImage(IcyBufferedImage source, BufferedImage dest, LUT lut) 093 { 094 final BufferedImage result; 095 096 if (dest == null) 097 result = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_INT_ARGB); 098 else 099 result = dest; 100 101 // else we need to convert to wanted type... 102 final Graphics2D g = result.createGraphics(); 103 // we don't want to blend over previous image (if any) 104 g.setComposite(AlphaComposite.Src); 105 g.drawImage(getARGBImage(source, lut), 0, 0, null); 106 g.dispose(); 107 108 return result; 109 } 110 111 /** 112 * Draw the source {@link IcyBufferedImage} into the destination {@link BufferedImage}<br> 113 * If <code>dest</code> is null then a new TYPE_INT_ARGB {@link BufferedImage} is returned. 114 * 115 * @param source 116 * source image 117 * @param dest 118 * destination image 119 */ 120 public static BufferedImage toBufferedImage(IcyBufferedImage source, BufferedImage dest) 121 { 122 return toBufferedImage(source, dest, null); 123 } 124 125 /** 126 * Convert the current {@link IcyBufferedImage} into a {@link BufferedImage} of the specified 127 * type. 128 * 129 * @param source 130 * source image 131 * @param imageType 132 * wanted image type, only the following is accepted :<br> 133 * BufferedImage.TYPE_INT_ARGB<br> 134 * BufferedImage.TYPE_INT_RGB<br> 135 * BufferedImage.TYPE_BYTE_GRAY<br> 136 * @param lut 137 * lut used for color calculation (source image lut is used if null) 138 * @return BufferedImage 139 */ 140 public static BufferedImage toBufferedImage(IcyBufferedImage source, int imageType, LUT lut) 141 { 142 if (source == null) 143 return null; 144 145 final BufferedImage outImg = new BufferedImage(source.getWidth(), source.getHeight(), imageType); 146 147 toBufferedImage(source, outImg, lut); 148 149 return outImg; 150 } 151 152 /** 153 * Convert the current {@link IcyBufferedImage} into a {@link BufferedImage} of the specified 154 * type. 155 * 156 * @param source 157 * source image 158 * @param imageType 159 * wanted image type, only the following is accepted :<br> 160 * BufferedImage.TYPE_INT_ARGB<br> 161 * BufferedImage.TYPE_INT_RGB<br> 162 * BufferedImage.TYPE_BYTE_GRAY<br> 163 * @return BufferedImage 164 */ 165 public static BufferedImage toBufferedImage(IcyBufferedImage source, int imageType) 166 { 167 return toBufferedImage(source, imageType, null); 168 } 169 170 /** 171 * Draw the source {@link IcyBufferedImage} into the destination ARGB {@link BufferedImage}<br> 172 * If <code>dest</code> is <code>null</code> then a new ARGB {@link BufferedImage} is 173 * returned.<br> 174 * This function is faster for ARGB conversion than 175 * {@link #toBufferedImage(IcyBufferedImage, BufferedImage, LUT)} 176 * but the output {@link BufferedImage} is fixed to ARGB type (TYPE_INT_ARGB) and the image 177 * cannot be volatile, use {@link #toBufferedImage(IcyBufferedImage, BufferedImage, LUT)} for no 178 * ARGB image or if you want volatile accelerated image. 179 * 180 * @param source 181 * source image 182 * @param dest 183 * destination image. Note that we access image data so it can't be volatile anymore 184 * which may result in slower drawing 185 * @param lut 186 * {@link LUT} is used for color calculation (internal lut is used if null). 187 * @deprecated Use {@link #toBufferedImage(IcyBufferedImage, BufferedImage, LUT)} instead. 188 */ 189 @Deprecated 190 public static BufferedImage getARGBImage(IcyBufferedImage source, LUT lut, BufferedImage dest) 191 { 192 if (source == null) 193 return null; 194 195 // use image lut when no specific lut 196 if (lut == null) 197 { 198 // manually update bounds if needed before doing RGB conversion from internal LUT 199 if (!source.getAutoUpdateChannelBounds()) 200 source.updateChannelsBounds(); 201 202 return argbImageBuilder.buildARGBImage(source, source.createCompatibleLUT(false), dest); 203 } 204 205 return argbImageBuilder.buildARGBImage(source, lut, dest); 206 } 207 208 /** 209 * Draw the source {@link IcyBufferedImage} into the destination ARGB {@link BufferedImage}<br> 210 * If <code>dest</code> is null then a new ARGB {@link BufferedImage} is returned.<br> 211 * <br> 212 * This function is faster for ARGB conversion than 213 * {@link #toBufferedImage(IcyBufferedImage, BufferedImage)} 214 * but the output {@link BufferedImage} is fixed to ARGB type (TYPE_INT_ARGB) and the image 215 * cannot be volatile, use {@link #toBufferedImage(IcyBufferedImage, BufferedImage)} for no 216 * ARGB image or if you want volatile accelerated image. 217 * 218 * @param source 219 * source image 220 * @param dest 221 * destination image. Note that we access image data so it can't be volatile anymore 222 * which may result in slower drawing 223 * @deprecated Use {@link #toBufferedImage(IcyBufferedImage, BufferedImage)} instead. 224 */ 225 @Deprecated 226 public static BufferedImage getARGBImage(IcyBufferedImage source, BufferedImage dest) 227 { 228 return getARGBImage(source, null, dest); 229 } 230 231 /** 232 * Convert the current {@link IcyBufferedImage} into a ARGB {@link BufferedImage}.<br> 233 * Note that we access image data so it can't be volatile anymore which may result in slower 234 * drawing. 235 * 236 * @param source 237 * source image 238 * @param lut 239 * {@link LUT} is used for color calculation (internal lut is used if null). 240 */ 241 public static BufferedImage getARGBImage(IcyBufferedImage source, LUT lut) 242 { 243 if (source == null) 244 return null; 245 246 // use image lut when no specific lut 247 if (lut == null) 248 { 249 // manually update bounds if needed before doing RGB conversion from internal LUT 250 if (!source.getAutoUpdateChannelBounds()) 251 source.updateChannelsBounds(); 252 253 return argbImageBuilder.buildARGBImage(source, source.createCompatibleLUT(false)); 254 } 255 256 return argbImageBuilder.buildARGBImage(source, lut); 257 } 258 259 /** 260 * Convert the current {@link IcyBufferedImage} into a ARGB {@link BufferedImage}.<br> 261 * Note that we access image data so it can't be volatile anymore which may result in slower 262 * drawing. 263 * 264 * @param source 265 * source image 266 */ 267 public static BufferedImage getARGBImage(IcyBufferedImage source) 268 { 269 return getARGBImage(source, (LUT) null); 270 } 271 272 /** 273 * Convert the source image to the specified data type.<br> 274 * This method returns a new image (the source image is not modified). 275 * 276 * @param source 277 * source image 278 * @param dataType 279 * data type wanted 280 * @param scalers 281 * scalers for scaling internal data during conversion (1 scaler per channel).<br> 282 * Can be set to <code>null</code> to avoid value conversion. 283 * @return converted image 284 */ 285 public static IcyBufferedImage convertType(IcyBufferedImage source, DataType dataType, Scaler[] scalers) 286 { 287 if (source == null) 288 return null; 289 290 final DataType srcDataType = source.getDataType_(); 291 292 // can't convert 293 if ((srcDataType == null) || (srcDataType == DataType.UNDEFINED) || (dataType == null) 294 || (dataType == DataType.UNDEFINED)) 295 return null; 296 297 final boolean srcSigned = srcDataType.isSigned(); 298 final boolean dstSigned = dataType.isSigned(); 299 final int sizeC = source.getSizeC(); 300 final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), sizeC, dataType); 301 302 result.lockRaster(); 303 try 304 { 305 for (int c = 0; c < sizeC; c++) 306 { 307 // no rescale ? 308 if ((scalers == null) || (c >= scalers.length) || scalers[c].isNull()) 309 // simple type change 310 ArrayUtil.arrayToSafeArray(source.getDataXY(c), result.getDataXY(c), srcSigned, dstSigned); 311 else 312 { 313 // first we convert in double 314 final double[] darray = Array1DUtil.arrayToDoubleArray(source.getDataXY(c), srcSigned); 315 // then we scale data 316 scalers[c].scale(darray); 317 // and finally we convert in wanted datatype 318 Array1DUtil.doubleArrayToSafeArray(darray, result.getDataXY(c), dstSigned); 319 } 320 } 321 } 322 finally 323 { 324 result.releaseRaster(true); 325 } 326 327 // copy colormap from source image 328 result.setColorMaps(source); 329 // notify we modified data 330 result.dataChanged(); 331 332 return result; 333 } 334 335 /** 336 * @deprecated Use {@link #convertType(IcyBufferedImage, DataType, Scaler[])} instead. 337 */ 338 @Deprecated 339 public static IcyBufferedImage convertToType(IcyBufferedImage source, DataType dataType, Scaler scaler) 340 { 341 if (source == null) 342 return null; 343 344 final DataType srcDataType = source.getDataType_(); 345 346 // can't convert 347 if ((srcDataType == DataType.UNDEFINED) || (dataType == DataType.UNDEFINED)) 348 return null; 349 350 final boolean srcSigned = srcDataType.isSigned(); 351 final boolean dstSigned = dataType.isSigned(); 352 final int sizeC = source.getSizeC(); 353 final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), sizeC, dataType); 354 355 result.lockRaster(); 356 try 357 { 358 for (int c = 0; c < sizeC; c++) 359 { 360 // no rescale ? 361 if ((scaler == null) || scaler.isNull()) 362 // simple type change 363 ArrayUtil.arrayToSafeArray(source.getDataXY(c), result.getDataXY(c), srcSigned, dstSigned); 364 else 365 { 366 // first we convert in double 367 final double[] darray = Array1DUtil.arrayToDoubleArray(source.getDataXY(c), srcSigned); 368 // then we scale data 369 scaler.scale(darray); 370 // and finally we convert in wanted datatype 371 Array1DUtil.doubleArrayToSafeArray(darray, result.getDataXY(c), dstSigned); 372 } 373 } 374 } 375 finally 376 { 377 result.releaseRaster(true); 378 } 379 380 // copy colormap from source image 381 result.setColorMaps(source); 382 // notify we modified data 383 result.dataChanged(); 384 385 return result; 386 } 387 388 /** 389 * Convert the source image to the specified data type.<br> 390 * This method returns a new image (the source image is not modified). 391 * 392 * @param dataType 393 * Data type wanted 394 * @param rescale 395 * Indicate if we want to scale data value according to data (or data type) range 396 * @param useDataBounds 397 * Only used when <code>rescale</code> parameter is true.<br> 398 * Specify if we use the data bounds for rescaling instead of data type bounds. 399 * @return converted image 400 */ 401 public static IcyBufferedImage convertToType(IcyBufferedImage source, DataType dataType, boolean rescale, 402 boolean useDataBounds) 403 { 404 if (source == null) 405 return null; 406 407 if (!rescale) 408 return convertType(source, dataType, null); 409 410 // convert with rescale 411 final double boundsDst[] = dataType.getDefaultBounds(); 412 final int sizeC = source.getSizeC(); 413 final Scaler[] scalers = new Scaler[sizeC]; 414 415 // build scalers 416 for (int c = 0; c < sizeC; c++) 417 { 418 final double boundsSrc[]; 419 420 if (useDataBounds) 421 boundsSrc = source.getChannelBounds(c); 422 else 423 boundsSrc = source.getChannelTypeBounds(c); 424 425 scalers[c] = new Scaler(boundsSrc[0], boundsSrc[1], boundsDst[0], boundsDst[1], false); 426 } 427 428 // use scaler to scale data 429 return convertType(source, dataType, scalers); 430 } 431 432 /** 433 * Convert the source image to the specified data type.<br> 434 * This method returns a new image (the source image is not modified). 435 * 436 * @param dataType 437 * data type wanted 438 * @param rescale 439 * indicate if we want to scale data value according to data type range 440 * @return converted image 441 */ 442 public static IcyBufferedImage convertToType(IcyBufferedImage source, DataType dataType, boolean rescale) 443 { 444 return convertToType(source, dataType, rescale, false); 445 } 446 447 /** 448 * Create a copy of the specified image. 449 */ 450 public static IcyBufferedImage getCopy(IcyBufferedImage source) 451 { 452 if (source == null) 453 return null; 454 455 // create a compatible image 456 final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), source.getSizeC(), 457 source.getDataType_()); 458 // copy data from this image 459 result.copyData(source); 460 461 return result; 462 } 463 464 /** 465 * Creates a new image from the specified region of the source image. 466 */ 467 public static IcyBufferedImage getSubImage(IcyBufferedImage source, Rectangle region, int c, int sizeC) 468 { 469 if (source == null) 470 return null; 471 472 final int startX; 473 final int endX; 474 final int startY; 475 final int endY; 476 final int startC; 477 final int endC; 478 479 // infinite X dimension ? 480 if ((region.x == Integer.MIN_VALUE) && (region.width == Integer.MAX_VALUE)) 481 { 482 startX = 0; 483 endX = source.getSizeX(); 484 } 485 else 486 { 487 startX = Math.max(0, region.x); 488 endX = Math.min(source.getSizeX(), region.x + region.width); 489 } 490 // infinite Y dimension ? 491 if ((region.y == Integer.MIN_VALUE) && (region.height == Integer.MAX_VALUE)) 492 { 493 startY = 0; 494 endY = source.getSizeY(); 495 } 496 else 497 { 498 startY = Math.max(0, region.y); 499 endY = Math.min(source.getSizeY(), region.y + region.height); 500 } 501 // infinite C dimension ? 502 if ((c == Integer.MIN_VALUE) && (sizeC == Integer.MAX_VALUE)) 503 { 504 startC = 0; 505 endC = source.getSizeC(); 506 } 507 else 508 { 509 startC = Math.max(0, c); 510 endC = Math.min(source.getSizeC(), c + sizeC); 511 } 512 513 final int sizeX = endX - startX; 514 final int sizeY = endY - startY; 515 final int adjSizeC = endC - startC; 516 517 if ((sizeX <= 0) || (sizeY <= 0) || (adjSizeC <= 0)) 518 return null; 519 520 // adjust rectangle 521 final DataType dataType = source.getDataType_(); 522 final boolean signed = dataType.isSigned(); 523 524 final IcyBufferedImage result = new IcyBufferedImage(sizeX, sizeY, adjSizeC, dataType); 525 final int srcSizeX = source.getSizeX(); 526 527 result.lockRaster(); 528 try 529 { 530 for (int ch = startC; ch < endC; ch++) 531 { 532 final Object src = source.getDataXY(ch); 533 final Object dst = result.getDataXY(ch - startC); 534 535 int srcOffset = source.getOffset(startX, startY); 536 int dstOffset = 0; 537 538 for (int curY = 0; curY < sizeY; curY++) 539 { 540 Array1DUtil.arrayToArray(src, srcOffset, dst, dstOffset, sizeX, signed); 541 srcOffset += srcSizeX; 542 dstOffset += sizeX; 543 } 544 } 545 } 546 finally 547 { 548 result.releaseRaster(true); 549 } 550 551 result.dataChanged(); 552 553 return result; 554 } 555 556 /** 557 * Creates a new image which is a sub part of the source image from the specified region. 558 * 559 * @see #getSubImage(IcyBufferedImage, Rectangle, int, int) 560 */ 561 public static IcyBufferedImage getSubImage(IcyBufferedImage source, Rectangle region) 562 { 563 return getSubImage(source, region, 0, source.getSizeC()); 564 } 565 566 /** 567 * @deprecated Use {@link #getSubImage(IcyBufferedImage, Rectangle, int, int)} instead. 568 */ 569 @Deprecated 570 public static IcyBufferedImage getSubImage(IcyBufferedImage source, int x, int y, int c, int sizeX, int sizeY, 571 int sizeC) 572 { 573 return getSubImage(source, new Rectangle(x, y, sizeX, sizeY), c, sizeC); 574 } 575 576 /** 577 * Creates a new image which is a sub part of the source image from the specified 578 * coordinates and dimensions. 579 */ 580 public static IcyBufferedImage getSubImage(IcyBufferedImage source, int x, int y, int w, int h) 581 { 582 return getSubImage(source, new Rectangle(x, y, w, h), 0, source.getSizeC()); 583 } 584 585 /** 586 * Build a new single channel image (greyscale) from the specified source image channel. 587 */ 588 public static IcyBufferedImage extractChannel(IcyBufferedImage source, int channel) 589 { 590 return extractChannels(source, channel); 591 } 592 593 /** 594 * @deprecated Use {@link #extractChannels(IcyBufferedImage, int[])} instead. 595 */ 596 @Deprecated 597 public static IcyBufferedImage extractChannels(IcyBufferedImage source, List<Integer> channelNumbers) 598 { 599 if (source == null) 600 return null; 601 602 // create output 603 final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), 604 channelNumbers.size(), source.getDataType_()); 605 final int sizeC = source.getSizeC(); 606 607 // set data from specified band 608 for (int i = 0; i < channelNumbers.size(); i++) 609 { 610 final int channel = channelNumbers.get(i).intValue(); 611 612 if (channel < sizeC) 613 result.setDataXY(i, source.getDataXY(channel)); 614 } 615 616 return result; 617 } 618 619 /** 620 * Build a new image from the specified source image channels. 621 */ 622 public static IcyBufferedImage extractChannels(IcyBufferedImage source, int... channels) 623 { 624 if ((source == null) || (channels == null) || (channels.length == 0)) 625 return null; 626 627 // create output 628 final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), channels.length, 629 source.getDataType_()); 630 final int sizeC = source.getSizeC(); 631 632 // set data from specified band 633 for (int i = 0; i < channels.length; i++) 634 { 635 final int channel = channels[i]; 636 637 if (channel < sizeC) 638 result.setDataXY(i, source.getDataXY(channel)); 639 } 640 641 return result; 642 } 643 644 /** 645 * Add empty channel(s) to the specified image and return result as a new image. 646 * 647 * @param source 648 * source image. 649 * @param index 650 * position where we want to add channel(s). 651 * @param num 652 * number of channel(s) to add. 653 */ 654 public static IcyBufferedImage addChannels(IcyBufferedImage source, int index, int num) 655 { 656 if (source == null) 657 return null; 658 659 final IcyBufferedImage result = new IcyBufferedImage(source.getSizeX(), source.getSizeY(), 660 source.getSizeC() + num, source.getDataType_()); 661 662 for (int c = 0; c < index; c++) 663 { 664 result.copyData(source, c, c); 665 result.setColorMap(c, source.getColorMap(c), false); 666 } 667 for (int c = index; c < source.getSizeC(); c++) 668 { 669 result.copyData(source, c, c + num); 670 result.setColorMap(c + num, source.getColorMap(c), false); 671 } 672 673 return result; 674 } 675 676 /** 677 * Add an empty channel to the specified image and return result as a new image. 678 * 679 * @param source 680 * source image. 681 * @param index 682 * position where we want to add channel. 683 */ 684 public static IcyBufferedImage addChannel(IcyBufferedImage source, int index) 685 { 686 return addChannels(source, index, 1); 687 } 688 689 /** 690 * Add an empty channel to the specified image and return result as a new image. 691 * 692 * @param source 693 * source image. 694 */ 695 public static IcyBufferedImage addChannel(IcyBufferedImage source) 696 { 697 return addChannels(source, source.getSizeC(), 1); 698 } 699 700 /** 701 * Return a rotated version of the source image with specified parameters. 702 * 703 * @param source 704 * source image 705 * @param xOrigin 706 * X origin for the rotation 707 * @param yOrigin 708 * Y origin for the rotation 709 * @param angle 710 * rotation angle in radian 711 * @param filterType 712 * filter resampling method used 713 */ 714 public static IcyBufferedImage rotate(IcyBufferedImage source, double xOrigin, double yOrigin, double angle, 715 FilterType filterType) 716 { 717 if (source == null) 718 return null; 719 720 final Interpolation interpolation; 721 722 switch (filterType) 723 { 724 default: 725 case NEAREST: 726 interpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST); 727 break; 728 729 case BILINEAR: 730 interpolation = Interpolation.getInstance(Interpolation.INTERP_BILINEAR); 731 break; 732 733 case BICUBIC: 734 interpolation = Interpolation.getInstance(Interpolation.INTERP_BICUBIC); 735 break; 736 } 737 738 // use JAI scaler (use a copy to avoid source alteration) 739 final RenderedOp renderedOp = RotateDescriptor.create(getCopy(source), Float.valueOf((float) xOrigin), 740 Float.valueOf((float) yOrigin), Float.valueOf((float) angle), interpolation, null, 741 new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(BorderExtender.BORDER_ZERO))); 742 743 return IcyBufferedImage.createFrom(renderedOp, source.isSignedDataType()); 744 } 745 746 /** 747 * Return a rotated version of the source image with specified parameters. 748 * 749 * @param source 750 * source image 751 * @param angle 752 * rotation angle in radian 753 * @param filterType 754 * filter resampling method used 755 */ 756 public static IcyBufferedImage rotate(IcyBufferedImage source, double angle, FilterType filterType) 757 { 758 if (source == null) 759 return null; 760 761 return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, filterType); 762 } 763 764 /** 765 * Return a rotated version of the source image with specified parameters. 766 * 767 * @param source 768 * source image 769 * @param angle 770 * rotation angle in radian 771 */ 772 public static IcyBufferedImage rotate(IcyBufferedImage source, double angle) 773 { 774 if (source == null) 775 return null; 776 777 return rotate(source, source.getSizeX() / 2d, source.getSizeY() / 2d, angle, FilterType.BILINEAR); 778 } 779 780 /** 781 * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are 782 * divided by 2).<br> 783 * This function is specifically optimized for factor 2 down scaling. 784 * 785 * @param input 786 * input image byte data array 787 * @param sizeX 788 * width of source image 789 * @param sizeY 790 * height of source image 791 * @param signed 792 * consider input byte data as signed (only meaningful when filter is enabled) 793 * @param filter 794 * enable pixel blending for better representation of the down sampled result image 795 * (otherwise nearest neighbor is used) 796 * @param output 797 * output buffer (single dimension array with same data type as source image data array). 798 */ 799 static void downscaleBy2(byte[] input, int sizeX, int sizeY, boolean signed, boolean filter, byte[] output) 800 { 801 final int halfSizeX = sizeX / 2; 802 final int halfSizeY = sizeY / 2; 803 804 if (filter) 805 { 806 int inOff = 0; 807 int outOff = 0; 808 809 for (int y = 0; y < halfSizeY; y++) 810 { 811 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 812 { 813 int val; 814 815 if (signed) 816 { 817 // accumulate 4 pixels 818 val = input[inOffset + 0]; 819 val += input[inOffset + 1]; 820 val += input[inOffset + sizeX + 0]; 821 val += input[inOffset + sizeX + 1]; 822 } 823 else 824 { 825 // accumulate 4 pixels 826 val = input[inOffset + 0] & 0xFF; 827 val += input[inOffset + 1] & 0xFF; 828 val += input[inOffset + sizeX + 0] & 0xFF; 829 val += input[inOffset + sizeX + 1] & 0xFF; 830 } 831 832 // divide by 4 833 val >>= 2; 834 835 // store result 836 output[outOff++] = (byte) val; 837 } 838 839 inOff += sizeX * 2; 840 } 841 } 842 else 843 { 844 int inOff = 0; 845 int outOff = 0; 846 847 for (int y = 0; y < halfSizeY; y++) 848 { 849 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 850 output[outOff++] = input[inOffset]; 851 852 inOff += sizeX * 2; 853 } 854 } 855 } 856 857 /** 858 * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are 859 * divided by 2).<br> 860 * This function is specifically optimized for factor 2 down scaling. 861 * 862 * @param input 863 * input image byte data array 864 * @param sizeX 865 * width of source image 866 * @param sizeY 867 * height of source image 868 * @param signed 869 * consider input byte data as signed (only meaningful when filter is enabled) 870 * @param filter 871 * enable pixel blending for better representation of the down sampled result image 872 * (otherwise nearest neighbor is used) 873 * @param output 874 * output buffer (single dimension array with same data type as source image data array). 875 */ 876 static void downscaleBy2(short[] input, int sizeX, int sizeY, boolean signed, boolean filter, short[] output) 877 { 878 final int halfSizeX = sizeX / 2; 879 final int halfSizeY = sizeY / 2; 880 881 if (filter) 882 { 883 int inOff = 0; 884 int outOff = 0; 885 886 for (int y = 0; y < halfSizeY; y++) 887 { 888 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 889 { 890 int val; 891 892 if (signed) 893 { 894 // accumulate 4 pixels 895 val = input[inOffset + 0]; 896 val += input[inOffset + 1]; 897 val += input[inOffset + sizeX + 0]; 898 val += input[inOffset + sizeX + 1]; 899 } 900 else 901 { 902 // accumulate 4 pixels 903 val = input[inOffset + 0] & 0xFFFF; 904 val += input[inOffset + 1] & 0xFFFF; 905 val += input[inOffset + sizeX + 0] & 0xFFFF; 906 val += input[inOffset + sizeX + 1] & 0xFFFF; 907 } 908 909 // divide by 4 910 val >>= 2; 911 912 // store result 913 output[outOff++] = (short) val; 914 } 915 916 inOff += sizeX * 2; 917 } 918 } 919 else 920 { 921 int inOff = 0; 922 int outOff = 0; 923 924 for (int y = 0; y < halfSizeY; y++) 925 { 926 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 927 output[outOff++] = input[inOffset]; 928 929 inOff += sizeX * 2; 930 } 931 } 932 } 933 934 /** 935 * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are 936 * divided by 2).<br> 937 * This function is specifically optimized for factor 2 down scaling. 938 * 939 * @param input 940 * input image byte data array 941 * @param sizeX 942 * width of source image 943 * @param sizeY 944 * height of source image 945 * @param signed 946 * consider input byte data as signed (only meaningful when filter is enabled) 947 * @param filter 948 * enable pixel blending for better representation of the down sampled result image 949 * (otherwise nearest neighbor is used) 950 * @param output 951 * output buffer (single dimension array with same data type as source image data array). 952 */ 953 static void downscaleBy2(int[] input, int sizeX, int sizeY, boolean signed, boolean filter, int[] output) 954 { 955 final int halfSizeX = sizeX / 2; 956 final int halfSizeY = sizeY / 2; 957 958 if (filter) 959 { 960 int inOff = 0; 961 int outOff = 0; 962 963 for (int y = 0; y < halfSizeY; y++) 964 { 965 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 966 { 967 long val; 968 969 if (signed) 970 { 971 // accumulate 4 pixels 972 val = input[inOffset + 0]; 973 val += input[inOffset + 1]; 974 val += input[inOffset + sizeX + 0]; 975 val += input[inOffset + sizeX + 1]; 976 } 977 else 978 { 979 // accumulate 4 pixels 980 val = input[inOffset + 0] & 0xFFFFFFFFL; 981 val += input[inOffset + 1] & 0xFFFFFFFFL; 982 val += input[inOffset + sizeX + 0] & 0xFFFFFFFFL; 983 val += input[inOffset + sizeX + 1] & 0xFFFFFFFFL; 984 } 985 986 // divide by 4 987 val >>= 2; 988 989 // store result 990 output[outOff++] = (int) val; 991 } 992 993 inOff += sizeX * 2; 994 } 995 } 996 else 997 { 998 int inOff = 0; 999 int outOff = 0; 1000 1001 for (int y = 0; y < halfSizeY; y++) 1002 { 1003 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 1004 output[outOff++] = input[inOffset]; 1005 1006 inOff += sizeX * 2; 1007 } 1008 } 1009 } 1010 1011 /** 1012 * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are 1013 * divided by 2).<br> 1014 * This function is specifically optimized for factor 2 down scaling. 1015 * 1016 * @param input 1017 * input image byte data array 1018 * @param sizeX 1019 * width of source image 1020 * @param sizeY 1021 * height of source image 1022 * @param filter 1023 * enable pixel blending for better representation of the down sampled result image 1024 * (otherwise nearest neighbor is used) 1025 * @param output 1026 * output buffer (single dimension array with same data type as source image data array). 1027 */ 1028 static void downscaleBy2(float[] input, int sizeX, int sizeY, boolean filter, float[] output) 1029 { 1030 final int halfSizeX = sizeX / 2; 1031 final int halfSizeY = sizeY / 2; 1032 1033 if (filter) 1034 { 1035 int inOff = 0; 1036 int outOff = 0; 1037 1038 for (int y = 0; y < halfSizeY; y++) 1039 { 1040 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 1041 { 1042 float val; 1043 1044 // accumulate 4 pixels 1045 val = input[inOffset + 0]; 1046 val += input[inOffset + 1]; 1047 val += input[inOffset + sizeX + 0]; 1048 val += input[inOffset + sizeX + 1]; 1049 // divide by 4 1050 val /= 4f; 1051 1052 // store result 1053 output[outOff++] = val; 1054 } 1055 1056 inOff += sizeX * 2; 1057 } 1058 } 1059 else 1060 { 1061 int inOff = 0; 1062 int outOff = 0; 1063 1064 for (int y = 0; y < halfSizeY; y++) 1065 { 1066 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 1067 output[outOff++] = input[inOffset]; 1068 1069 inOff += sizeX * 2; 1070 } 1071 } 1072 } 1073 1074 /** 1075 * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are 1076 * divided by 2).<br> 1077 * This function is specifically optimized for factor 2 down scaling. 1078 * 1079 * @param input 1080 * input image byte data array 1081 * @param sizeX 1082 * width of source image 1083 * @param sizeY 1084 * height of source image 1085 * @param filter 1086 * enable pixel blending for better representation of the down sampled result image 1087 * (otherwise nearest neighbor is used) 1088 * @param output 1089 * output buffer (single dimension array with same data type as source image data array). 1090 */ 1091 static void downscaleBy2(double[] input, int sizeX, int sizeY, boolean filter, double[] output) 1092 { 1093 final int halfSizeX = sizeX / 2; 1094 final int halfSizeY = sizeY / 2; 1095 1096 if (filter) 1097 { 1098 int inOff = 0; 1099 int outOff = 0; 1100 1101 for (int y = 0; y < halfSizeY; y++) 1102 { 1103 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 1104 { 1105 double val; 1106 1107 // accumulate 4 pixels 1108 val = input[inOffset + 0]; 1109 val += input[inOffset + 1]; 1110 val += input[inOffset + sizeX + 0]; 1111 val += input[inOffset + sizeX + 1]; 1112 // divide by 4 1113 val /= 4d; 1114 1115 // store result 1116 output[outOff++] = val; 1117 } 1118 1119 inOff += sizeX * 2; 1120 } 1121 } 1122 else 1123 { 1124 int inOff = 0; 1125 int outOff = 0; 1126 1127 for (int y = 0; y < halfSizeY; y++) 1128 { 1129 for (int x = 0, inOffset = inOff; x < halfSizeX; x++, inOffset += 2) 1130 output[outOff++] = input[inOffset]; 1131 1132 inOff += sizeX * 2; 1133 } 1134 } 1135 } 1136 1137 /** 1138 * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are 1139 * divided by 2).<br> 1140 * This function is specifically optimized for factor 2 down scaling. 1141 * 1142 * @param input 1143 * input image data array (single dimension) 1144 * @param sizeX 1145 * width of source image 1146 * @param sizeY 1147 * height of source image 1148 * @param signed 1149 * consider input byte data as signed (only meaningful when filter is enabled) 1150 * @param filter 1151 * enable pixel blending for better representation of the down sampled result image 1152 * (otherwise nearest neighbor is used) 1153 * @param output 1154 * output buffer (single dimension array with same data type as source image data array). 1155 * If set to <code>null</code> a new array is allocated. 1156 * @return output buffer containing the down scaled version of input image data. 1157 */ 1158 public static Object downscaleBy2(Object input, int sizeX, int sizeY, boolean signed, boolean filter, Object output) 1159 { 1160 if (input == null) 1161 return output; 1162 1163 final ArrayType arrayType = ArrayUtil.getArrayType(input); 1164 final Object result = ArrayUtil.allocIfNull(output, arrayType, (sizeX / 2) * (sizeY / 2)); 1165 1166 switch (arrayType.getDataType().getJavaType()) 1167 { 1168 case BYTE: 1169 downscaleBy2((byte[]) input, sizeX, sizeY, signed, filter, (byte[]) result); 1170 break; 1171 1172 case SHORT: 1173 downscaleBy2((short[]) input, sizeX, sizeY, signed, filter, (short[]) result); 1174 break; 1175 1176 case INT: 1177 downscaleBy2((int[]) input, sizeX, sizeY, signed, filter, (int[]) result); 1178 break; 1179 1180 case FLOAT: 1181 downscaleBy2((float[]) input, sizeX, sizeY, filter, (float[]) result); 1182 break; 1183 1184 case DOUBLE: 1185 downscaleBy2((double[]) input, sizeX, sizeY, filter, (double[]) result); 1186 break; 1187 } 1188 1189 return result; 1190 } 1191 1192 /** 1193 * Returns a down scaled by a factor of 2 of the input image data (X and Y resolution are 1194 * divided by 2).<br> 1195 * This function is specifically optimized for factor 2 down scaling. 1196 * 1197 * @param input 1198 * input image data array (single dimension) 1199 * @param sizeX 1200 * width of source image 1201 * @param sizeY 1202 * height of source image 1203 * @param signed 1204 * consider input byte data as signed (only meaningful when filter is enabled) 1205 * @param filter 1206 * enable pixel blending for better representation of the down sampled result image 1207 * (otherwise nearest neighbor is used) 1208 * @return output buffer containing the down scaled version of input image data. 1209 */ 1210 public static Object downscaleBy2(Object input, int sizeX, int sizeY, boolean signed, boolean filter) 1211 { 1212 return downscaleBy2(input, sizeX, sizeY, signed, filter, null); 1213 } 1214 1215 /** 1216 * Returns a down scaled by a factor of 2 of the input image (X and Y resolution are divided by 1217 * 2).<br> 1218 * This function is specifically optimized for factor 2 down scaling. 1219 * 1220 * @param input 1221 * input image 1222 * @param filter 1223 * enable pixel blending for better representation of the down sampled result image 1224 * (otherwise nearest neighbor is used) 1225 * @param output 1226 * output image receiving the result (should be of same type as input image with X and Y 1227 * resolution divided 1228 * by 2). If set to <code>null</code> a new image is created. 1229 */ 1230 public static IcyBufferedImage downscaleBy2(IcyBufferedImage input, boolean filter, IcyBufferedImage output) 1231 { 1232 if (input == null) 1233 return output; 1234 1235 final IcyBufferedImage result; 1236 final int sizeX = input.getSizeX(); 1237 final int sizeY = input.getSizeY(); 1238 final int sizeC = input.getSizeC(); 1239 1240 if (output != null) 1241 result = output; 1242 else 1243 // create an empty image with specified size and current colormodel description 1244 result = new IcyBufferedImage(sizeX / 2, sizeY / 2, input.getIcyColorModel()); 1245 1246 for (int c = 0; c < sizeC; c++) 1247 downscaleBy2(input.getDataXY(c), sizeX, sizeY, input.isSignedDataType(), filter, result.getDataXY(c)); 1248 1249 return result; 1250 } 1251 1252 /** 1253 * Returns a down scaled by a factor of 2 of the input image (X and Y resolution are divided by 1254 * 2).<br> 1255 * This function is specifically optimized for factor 2 down scaling. 1256 * 1257 * @param input 1258 * input image 1259 * @param filter 1260 * enable pixel blending for better representation of the down sampled result image 1261 * (otherwise nearest neighbor is used) 1262 */ 1263 public static IcyBufferedImage downscaleBy2(IcyBufferedImage input, boolean filter) 1264 { 1265 return downscaleBy2(input, filter, null); 1266 } 1267 1268 /** 1269 * Down scale the specified image with the given down scale factor.<br> 1270 * If down scale factor equals <code>0</code> then the input image is directly returned. 1271 * 1272 * @param source 1273 * input image 1274 * @param filter 1275 * enable pixel blending for better representation of the down sampled result image 1276 * (otherwise nearest neighbor is used) 1277 * @param level 1278 * number of downscale to process: scale level = 1/2^level 1279 * @return scaled image or source image is scale level equals <code>0</code> 1280 */ 1281 public static IcyBufferedImage downscaleBy2(IcyBufferedImage source, boolean filter, int level) 1282 { 1283 IcyBufferedImage result = source; 1284 int it = level; 1285 1286 // process fast down scaling 1287 while (it-- > 0) 1288 result = IcyBufferedImageUtil.downscaleBy2(result, true); 1289 1290 return result; 1291 } 1292 1293 /** 1294 * Return a copy of the source image with specified size, alignment rules and filter type. 1295 * 1296 * @param source 1297 * source image 1298 * @param resizeContent 1299 * indicate if content should be resized or not (empty area are 0 filled) 1300 * @param xAlign 1301 * horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br> 1302 * (used only if resizeContent is false) 1303 * @param yAlign 1304 * vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br> 1305 * (used only if resizeContent is false) 1306 * @param filterType 1307 * filter method used for scale (used only if resizeContent is true) 1308 */ 1309 public static IcyBufferedImage scale(IcyBufferedImage source, int width, int height, boolean resizeContent, 1310 int xAlign, int yAlign, FilterType filterType) 1311 { 1312 if (source == null) 1313 return null; 1314 1315 final IcyBufferedImage result; 1316 final int srcW = source.getWidth(); 1317 final int srcH = source.getHeight(); 1318 final boolean resize = resizeContent && ((width != srcW) || (height != srcH)); 1319 1320 // no content resize ? 1321 if (!resize) 1322 { 1323 final int xt; 1324 final int yt; 1325 1326 // calculate translation values 1327 final int dx = width - srcW; 1328 switch (xAlign) 1329 { 1330 default: 1331 case SwingConstants.LEFT: 1332 xt = 0; 1333 break; 1334 1335 case SwingConstants.CENTER: 1336 xt = dx / 2; 1337 break; 1338 1339 case SwingConstants.RIGHT: 1340 xt = dx; 1341 break; 1342 } 1343 1344 final int dy = height - srcH; 1345 switch (yAlign) 1346 { 1347 default: 1348 case SwingConstants.TOP: 1349 yt = 0; 1350 break; 1351 1352 case SwingConstants.CENTER: 1353 yt = dy / 2; 1354 break; 1355 1356 case SwingConstants.BOTTOM: 1357 yt = dy; 1358 break; 1359 } 1360 1361 // create an empty image with specified size and current colormodel description 1362 result = new IcyBufferedImage(width, height, source.getIcyColorModel()); 1363 // copy data from current image to specified destination 1364 result.copyData(source, null, new Point(xt, yt)); 1365 } 1366 else 1367 { 1368 final Float xScale = Float.valueOf((float) width / srcW); 1369 final Float yScale = Float.valueOf((float) height / srcH); 1370 final Interpolation interpolation; 1371 1372 switch (filterType) 1373 { 1374 default: 1375 case NEAREST: 1376 interpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST); 1377 break; 1378 1379 case BILINEAR: 1380 interpolation = Interpolation.getInstance(Interpolation.INTERP_BILINEAR); 1381 break; 1382 1383 case BICUBIC: 1384 interpolation = Interpolation.getInstance(Interpolation.INTERP_BICUBIC); 1385 break; 1386 } 1387 1388 // use a copy as JAI may alter source data 1389 final IcyBufferedImage srcCopy = getCopy(source); 1390 1391 // better to lock raster during JAI operation 1392 srcCopy.lockRaster(); 1393 try 1394 { 1395 // use JAI scaler 1396 final RenderedOp renderedOp = ScaleDescriptor.create(srcCopy, xScale, yScale, Float.valueOf(0f), 1397 Float.valueOf(0f), interpolation, new RenderingHints(JAI.KEY_BORDER_EXTENDER, 1398 BorderExtender.createInstance(BorderExtender.BORDER_COPY))); 1399 1400 // get result 1401 result = IcyBufferedImage.createFrom(renderedOp, source.isSignedDataType()); 1402 } 1403 finally 1404 { 1405 srcCopy.releaseRaster(false); 1406 } 1407 } 1408 1409 return result; 1410 } 1411 1412 /** 1413 * Return a copy of the image with specified size.<br> 1414 * By default the FilterType.BILINEAR is used as filter method if resizeContent is true 1415 * 1416 * @param source 1417 * source image 1418 * @param resizeContent 1419 * indicate if content should be resized or not (empty area are 0 filled) 1420 * @param xAlign 1421 * horizontal image alignment (SwingConstants.LEFT / CENTER / RIGHT)<br> 1422 * (used only if resizeContent is false) 1423 * @param yAlign 1424 * vertical image alignment (SwingConstants.TOP / CENTER / BOTTOM)<br> 1425 * (used only if resizeContent is false) 1426 */ 1427 public static IcyBufferedImage scale(IcyBufferedImage source, int width, int height, boolean resizeContent, 1428 int xAlign, int yAlign) 1429 { 1430 return scale(source, width, height, resizeContent, xAlign, yAlign, FilterType.BILINEAR); 1431 } 1432 1433 /** 1434 * Return a copy of the image with specified size<br> 1435 * 1436 * @param source 1437 * source image 1438 * @param filterType 1439 * filter method used for scale (used only if resizeContent is true) 1440 */ 1441 public static IcyBufferedImage scale(IcyBufferedImage source, int width, int height, FilterType filterType) 1442 { 1443 return scale(source, width, height, true, 0, 0, filterType); 1444 } 1445 1446 /** 1447 * Return a copy of the image with specified size.<br> 1448 * By default the FilterType.BILINEAR is used as filter method. 1449 */ 1450 public static IcyBufferedImage scale(IcyBufferedImage source, int width, int height) 1451 { 1452 return scale(source, width, height, FilterType.BILINEAR); 1453 } 1454 1455 /** 1456 * Translate image internal data of specified channel by the specified (x,y) vector.<br> 1457 * Data going "outside" image bounds is lost. 1458 */ 1459 public static void translate(IcyBufferedImage source, int dx, int dy, int channel) 1460 { 1461 if (source == null) 1462 return; 1463 1464 // nothing to do 1465 if ((dx == 0) && (dy == 0)) 1466 return; 1467 1468 final int sizeX = source.getSizeX(); 1469 final int sizeY = source.getSizeY(); 1470 1471 // limit to sizeX / sizeY 1472 final int adx = Math.min(Math.max(-sizeX, dx), sizeX); 1473 final int ady = Math.min(Math.max(-sizeY, dy), sizeY); 1474 1475 final int fromFill; 1476 final int toFill; 1477 final int fromCopy; 1478 final int toCopy; 1479 final int wCopy; 1480 1481 if (adx < 0) 1482 { 1483 fromCopy = -adx; 1484 toCopy = 0; 1485 wCopy = sizeX + adx; 1486 fromFill = wCopy; 1487 toFill = sizeX; 1488 } 1489 else 1490 { 1491 fromFill = 0; 1492 toFill = adx; 1493 fromCopy = fromFill; 1494 toCopy = toFill; 1495 wCopy = sizeX - adx; 1496 } 1497 1498 source.lockRaster(); 1499 try 1500 { 1501 final Object data = source.getDataXY(channel); 1502 1503 if (ady < 0) 1504 { 1505 final int hCopy = sizeY + ady; 1506 int toOffset = 0; 1507 int fromOffset = -ady * sizeX; 1508 1509 // copy 1510 for (int y = 0; y < hCopy; y++) 1511 { 1512 // copy first 1513 Array1DUtil.innerCopy(data, fromOffset + fromCopy, toOffset + toCopy, wCopy); 1514 // then fill 1515 Array1DUtil.fill(data, toOffset + fromFill, toOffset + toFill, 0d); 1516 // adjust offset 1517 fromOffset += sizeX; 1518 toOffset += sizeX; 1519 } 1520 // fill 1521 Array1DUtil.fill(data, toOffset, fromOffset, 0d); 1522 } 1523 else 1524 { 1525 final int hCopy = sizeY - ady; 1526 int toOffset = (sizeY - 1) * sizeX; 1527 int fromOffset = toOffset - (ady * sizeX); 1528 1529 // copy 1530 for (int y = 0; y < hCopy; y++) 1531 { 1532 // copy first 1533 Array1DUtil.innerCopy(data, fromOffset + fromCopy, toOffset + toCopy, wCopy); 1534 // then fill 1535 Array1DUtil.fill(data, toOffset + fromFill, toOffset + toFill, 0d); 1536 // adjust offset 1537 fromOffset -= sizeX; 1538 toOffset -= sizeX; 1539 } 1540 // fill 1541 Array1DUtil.fill(data, 0, 0 + (ady * sizeX), 0d); 1542 } 1543 } 1544 finally 1545 { 1546 source.releaseRaster(true); 1547 } 1548 1549 // notify data changed 1550 source.dataChanged(); 1551 } 1552 1553 /** 1554 * Translate image internal data (all channels) by the specified (x,y) vector.<br> 1555 * Data going "outside" image bounds is lost. 1556 */ 1557 public static void translate(IcyBufferedImage source, int dx, int dy) 1558 { 1559 if (source != null) 1560 { 1561 final int sizeC = source.getSizeC(); 1562 1563 source.beginUpdate(); 1564 try 1565 { 1566 for (int c = 0; c < sizeC; c++) 1567 translate(source, dx, dy, c); 1568 } 1569 finally 1570 { 1571 source.endUpdate(); 1572 } 1573 } 1574 } 1575}