001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009package imagingbook.common.image; 010 011import ij.ImageStack; 012import ij.process.ByteProcessor; 013import ij.process.ColorProcessor; 014import ij.process.FloatProcessor; 015import ij.process.ImageProcessor; 016import ij.process.ShortProcessor; 017import imagingbook.common.color.RgbUtils; 018import imagingbook.common.filter.generic.GenericFilter; 019import imagingbook.common.image.access.ImageAccessor; 020 021import java.util.Arrays; 022 023/** 024 * <p> 025 * This class defines a generic data container for scalar and vector-valued images, using float-values throughout. Its 026 * primary use is in the {@link GenericFilter} framework. A {@link PixelPack} may represent images with an arbitrary 027 * number of components. Scalar images (such as {@link ByteProcessor}, {@link ShortProcessor} and 028 * {@link FloatProcessor}) have 1 component, color images (such as {@link ColorProcessor}) typically have 3 components. 029 * Conversion methods to and from ImageJ's processor classes are provided, with optional scaling of pixel component 030 * values. 031 * </p> 032 * <p> 033 * Internally, pixel data are stored as 1-dimensional {@code float} arrays, one array for each component. Method 034 * {@link #getData()} may be used to access the internal data directly. Individual components may be extracted as a 035 * {@link PixelSlice} using method {@link #getSlice(int)}. 036 * </p> 037 * <p> 038 * Methods {@link #getPix(int, int)} and {@link #setPix(int, int, float...)} are provided to read and write individual 039 * pixels, which are ALWAYS of type {@code float[]} (even if the underlying image is scalar-valued). Pixel values 040 * returned for positions outside the image boundaries depend on the {@link OutOfBoundsStrategy} specified by the 041 * constructor (e.g., {@link #PixelPack(ImageProcessor, double, OutOfBoundsStrategy)}). 042 * </p> 043 * <p>Here is a simple usage example:</p> 044 * <pre> 045 * ColorProcessor ip1 = ... ; // some color image 046 * PixelPack pack = new PixelPack(ip1); 047 * // process pack: 048 * float[] val = pack.getPix(0, 0); 049 * pack.setPix(0, 0, 128, 19, 255); 050 * ... 051 * ColorProcessor ip2 = pack.toColorProcessor(); 052 * </pre> 053 * <p> 054 * A related concept for providing unified access to images is {@link ImageAccessor}. In contrast to {@link PixelPack}, 055 * {@link ImageAccessor} does not duplicate any data but reads and writes the original {@link ImageProcessor} pixel data 056 * directly. 057 * </p> 058 * 059 * @author WB 060 * @version 2022/09/03 061 * @see ImageAccessor 062 */ 063public class PixelPack { 064 065 /** The default out-of-bounds strategy (see {@link OutOfBoundsStrategy}). */ 066 public static final OutOfBoundsStrategy DefaultOutOfBoundsStrategy = OutOfBoundsStrategy.NearestBorder; 067 068 protected final int width; 069 protected final int height; 070 protected final int depth; 071 protected final float[][] data; 072 protected final int length; 073 private final GridIndexer2D indexer; 074 075 // -------------------------------------------------------------------- 076 077 /** 078 * Constructor, creates a blank (zero-valued) pack of pixel data. 079 * 080 * @param width the image width 081 * @param height the image height 082 * @param depth the number of channels (slices) 083 * @param obs strategy to be used when reading from out-of-bounds coordinates (pass {@code null} for default) 084 */ 085 public PixelPack(int width, int height, int depth, OutOfBoundsStrategy obs) { 086 this.width = width; 087 this.height = height; 088 this.depth = depth; 089 this.length = width * height; 090 this.data = new float[depth][length]; 091 this.indexer = GridIndexer2D.create(width, height, obs); 092 } 093 094 /** 095 * Constructor, creates a pack of pixel data from the given {@link ImageProcessor} object. Does not scale pixel 096 * values and uses {@link #DefaultOutOfBoundsStrategy} as the out-of-bounds strategy (see 097 * {@link OutOfBoundsStrategy}). 098 * 099 * @param ip the source image 100 */ 101 public PixelPack(ImageProcessor ip) { 102 this(ip, 1.0, DefaultOutOfBoundsStrategy); 103 } 104 105 /** 106 * Constructor, reates a pack of pixel data from the given {@link ImageProcessor} object, using the specified 107 * out-of-bounds strategy. 108 * 109 * @param ip the source image 110 * @param scale scale factor applied to pixel components 111 * @param obs strategy to be used when reading from out-of-bounds coordinates (pass {@code null} for default) 112 */ 113 public PixelPack(ImageProcessor ip, double scale, OutOfBoundsStrategy obs) { 114 this(ip.getWidth(), ip.getHeight(), ip.getNChannels(), obs); 115 copyFromImageProcessor(ip, scale); 116 } 117 118 /** 119 * Constructor, creates a new {@link PixelPack} with the same dimension as the original without copying the 120 * contained pixel data (initialized to zero). 121 * 122 * @param orig the original {@link PixelPack} 123 */ 124 public PixelPack(PixelPack orig) { 125 this(orig, false); 126 } 127 128 /** 129 * Constructor, creates a new {@link PixelPack} with the same dimension as the original. Optionally the original 130 * pixel data are copied, otherwise they are initialized to zero values. 131 * 132 * @param orig the original {@link PixelPack} 133 * @param copyData set true to copy pixel data 134 */ 135 public PixelPack(PixelPack orig, boolean copyData) { 136 this(orig.getWidth(), orig.getHeight(), orig.getDepth(), orig.indexer.getOutOfBoundsStrategy()); 137 if (copyData) { 138 orig.copyTo(this); 139 } 140 } 141 142 // -------------------------------------------------------------------- 143 144 @Deprecated // use getPix(int u, int v, float[] vals) 145 public float[] getVec(int u, int v, float[] vals) { 146 getPix(u, v, vals); 147 return vals; 148 } 149 150 @Deprecated // use getPix(int u, int v) 151 public float[] getVec(int u, int v) { 152 return getPix(u, v); 153 } 154 155 @Deprecated // use setPix(int u, int v, float ... vals) 156 public void setVec(int u, int v, float ... vals) { 157 setPix(u, v, vals); 158 } 159 160 // -------------------------------------------------------------------- 161 162 /** 163 * Reads the pixel data at the specified image position. The supplied array is filled. The length of this array must 164 * match corresponds the number of slices in this pixel pack. The values returned for out-of-bounds positions depend 165 * on this pixel-pack's out-of-bounds strategy. 166 * 167 * @param u the x-position 168 * @param v the y-position 169 * @param vals a suitable array of pixel data 170 */ 171 public void getPix(int u, int v, float[] vals) { 172 if (vals == null) 173 vals = new float[depth]; 174 final int idx = indexer.getIndex(u, v); 175 if (idx < 0) { // i = -1 --> default value (zero) 176 Arrays.fill(vals, 0); 177 } 178 else { 179// for (int k = 0; k < depth; k++) { 180// vals[k] = data[k][i]; 181// } 182 getPix(idx, vals); 183 } 184 } 185 186 /** 187 * Returns the pixel data at the specified position as a {@code float[]}. The values returned for out-of-bounds 188 * positions depend on this pixel-pack's out-of-bounds strategy. 189 * 190 * @param u the x-position 191 * @param v the y-position 192 * @return the array of pixel component values 193 */ 194 public float[] getPix(int u, int v) { 195 float[] vals = new float[depth]; 196 getPix(u, v, vals); 197 return vals; 198 } 199 200 /** 201 * Reads the pixel data at the specified 1D index. The supplied array is filled. The length of this array must match 202 * corresponds the number of slices in this pixel pack. The index is not checked, the corresponding pixel must 203 * always be inside the image bounds, otherwise an exception will be thrown. 204 * 205 * @param idx a valid 1D pixel index (in row-major order) 206 * @param vals a suitable array of pixel data 207 */ 208 public void getPix(int idx, float[] vals) { 209 for (int k = 0; k < depth; k++) { 210 vals[k] = data[k][idx]; 211 } 212 } 213 214 /** 215 * Returns the pixel data at the specified position as a {@code float[]}. The index is not checked, the 216 * corresponding pixel must always be inside the image bounds, otherwise an exception will be thrown. 217 * 218 * @param idx a valid 1D pixel index (in row-major order) 219 * @return the array of pixel component values 220 */ 221 public float[] getPix(int idx) { 222 float[] vals = new float[depth]; 223 getPix(idx, vals); 224 return vals; 225 } 226 227 228 /** 229 * Sets the pixel data at the specified pixel position. The length of the value array corresponds to the number of 230 * slices (components) in this pixel pack. 231 * 232 * @param u the x-position 233 * @param v the y-position 234 * @param vals the pixel's component values (may also be a {@code float[]}) 235 */ 236 public void setPix(int u, int v, float ... vals) { 237 final int idx = indexer.getIndex(u, v); 238 if (idx >= 0) { 239// for (int k = 0; k < depth && k < vals.length; k++) { 240// data[k][i] = vals[k]; 241// } 242 setPix(idx, vals); 243 } 244 } 245 246 public void setPix(int idx, float ... vals) { 247 for (int k = 0; k < depth && k < vals.length; k++) { 248 data[k][idx] = vals[k]; 249 } 250 } 251 252 /** 253 * Copies the contents of one pixel pack to another. The involved pixel packs must have the same dimensions. 254 * 255 * @param other another pixel pack 256 */ 257 public void copyTo(PixelPack other) { 258 if (!this.isCompatibleTo(other)) { 259 throw new IllegalArgumentException("pixel packs of incompatible size, cannot copy"); 260 } 261 for (int k = 0; k < this.depth; k++) { 262 System.arraycopy(this.data[k], 0, other.data[k], 0, this.length); 263 } 264 } 265 266 /** 267 * Checks is this pixel pack has the same dimensions as another pixel pack, i.e., can be copied to it. 268 * 269 * @param other the other pixel pack 270 * @return true if both have the same dimensions 271 */ 272 public boolean isCompatibleTo(PixelPack other) { 273 if (this.data.length == other.data.length && this.length == other.length) { // TODO: check width/height too 274 return true; 275 } 276 else 277 return false; 278 } 279 280 /** 281 * Checks is this pixel pack has the same dimensions as the specified {@link ImageProcessor} instance, i.e., can be 282 * copied to it. 283 * 284 * @param ip the image processor instance 285 * @return true if compatible 286 */ 287 public boolean isCompatibleTo(ImageProcessor ip) { 288 if (this.getWidth() != ip.getWidth() || this.getHeight() != ip.getHeight()) { 289 return false; 290 } 291 if (this.getDepth() != ip.getNChannels()) { 292 return false; 293 } 294 return true; 295 } 296 297 /** 298 * Returns the kth {@link PixelSlice}. An exception is thrown if the specified slice does not exist. 299 * 300 * @param k the slice index (0,...,K-1) 301 * @return the kth {@link PixelSlice} 302 * @throws IllegalArgumentException if slice with the given index does not exist 303 */ 304 public PixelSlice getSlice(int k) throws IllegalArgumentException { 305 if (k < 0 || k >= depth) { 306 throw new IllegalArgumentException("illegal slice number " + k); 307 } 308 return new PixelSlice(k); 309 } 310 311 /** 312 * Creates and returns a new {@link PixelSlice} with the same dimensions and out-of-bounds strategy as this 313 * {@link PixelPack}. 314 * 315 * @return a new pixel slice 316 */ 317 public PixelSlice getEmptySlice() { 318 return new PixelSlice(); 319 } 320 321 /** 322 * Returns an array of {@link PixelSlice} instances for this {@link PixelPack}. All pixel data are shared. 323 * 324 * @return an array of {@link PixelSlice} 325 */ 326 public PixelSlice[] getSlices() { 327 PixelSlice[] slices = new PixelSlice[depth]; 328 for (int k = 0; k < depth; k++) { 329 slices[k] = getSlice(k); 330 } 331 return slices; 332 } 333 334 /** 335 * Returns the {@link FloatProcessor} for the kth pixel slice. An exception is thrown if the specified slice does 336 * not exist. 337 * 338 * @param k the slice index (0,...,K-1) 339 * @return the kth {@link FloatProcessor} 340 */ 341 public FloatProcessor getFloatProcessor(int k) { 342 return getSlice(k).getFloatProcessor(); 343 } 344 345 346 /** 347 * Returns an array of {@link FloatProcessor} instances for this {@link PixelPack}. All pixel data are shared. 348 * 349 * @return an array of {@link FloatProcessor} 350 */ 351 public FloatProcessor[] getFloatProcessors() { 352 FloatProcessor[] processors = new FloatProcessor[depth]; 353 for (int k = 0; k < depth; k++) { 354 processors[k] = getFloatProcessor(k); 355 } 356 return processors; 357 } 358 359 /** 360 * Returns a reference to this {@link PixelPack}'s internal data array, which is always two-dimensional: dimension 1 361 * is the slice (component) index, dimension 2 is the pixel index (each slice is a 1D array). 362 * 363 * @return the pixel pack's data array 364 */ 365 public float[][] getData() { 366 return data; 367 } 368 369 /** 370 * Returns the width of the associated image. 371 * 372 * @return the image width 373 */ 374 public int getWidth() { 375// return this.indexer.getWidth(); 376 return this.width; 377 } 378 379 /** 380 * Returns the height of the associated image. 381 * 382 * @return the image height 383 */ 384 public int getHeight() { 385// return this.indexer.getHeight(); 386 return this.height; 387 } 388 389 /** 390 * Returns the depth (number of slices) of the associated image. 391 * @return the image depth 392 */ 393 public int getDepth() { 394 return this.depth; 395 } 396 397 /** 398 * Returns the out-of-bounds strategy. 399 * @return the out-of-bounds strategy 400 */ 401 public OutOfBoundsStrategy getOutOfBoundsStrategy() { 402 return this.indexer.getOutOfBoundsStrategy(); 403 } 404 405 /** 406 * Sets all values of this pixel pack to zero. 407 */ 408 public void zero() { 409 for (int k = 0; k < depth; k++) { 410 getSlice(k).zero(); 411 } 412 } 413 414 /** 415 * Returns the pixel values in the 3x3 neighborhood around the specified position. The returned float-array has the 416 * structure {@code [x][y][k]}, with x,y = 0,...,2 and k is the slice index. 417 * 418 * @param uc the center x-position 419 * @param vc the center x-position 420 * @param nh a float array to be filled in (or null) 421 * @return the neighborhood array 422 */ 423 public float[][][] get3x3Neighborhood(int uc, int vc, float[][][] nh) { 424 if (nh == null) 425 nh = new float[3][3][depth]; 426 for (int i = 0; i < 3; i++) { 427 int u = uc - 1 + i; 428 for (int j = 0; j < 3; j++) { 429 int v = vc - 1 + j; 430 nh[i][j] = getPix(u, v); 431 } 432 } 433 return nh; 434 } 435 436// /** 437// * Copies the contents of this pixel pack to the supplied {@link ImageProcessor} 438// * instance, if compatible. Otherwise an exception is thrown. 439// * 440// * @param ip the target image processor 441// */ 442// public void copyToImageProcessor(ImageProcessor ip) { 443// copyToImageProcessor(this, ip); 444// } 445 446 // ------------------------------------------------------------------- 447 448 /** 449 * Inner class representing a single (scalar-valued) component of a (vector-valued) {@link PixelPack}. 450 */ 451 public class PixelSlice { 452 private final int idx; 453 private final float[] vals; 454 455 /** 456 * Constructor, creates a pixel slice for the specified component. 457 * @param idx the slice (component) index 458 */ 459 PixelSlice(int idx) { 460 this.idx = idx; 461 this.vals = data[idx]; 462 } 463 464 /** 465 * Constructor, creates an empty (zero-valued) pixel slice with the same properties as the containing pixel pack 466 * but not associated with it. 467 */ 468 PixelSlice() { 469 this.idx = -1; 470 this.vals = new float[length]; 471 } 472 473 /** 474 * Returns the pixel value for the specified image position. 475 * @param u the x-position 476 * @param v the y-position 477 * @return the slice value 478 */ 479 public float getVal(int u, int v) { 480 int i = indexer.getIndex(u, v); 481 return (i >= 0) ? vals[i] : 0; 482 } 483 484 /** 485 * Sets the pixel value at the specified image position. 486 * @param u the x-position 487 * @param v the y-position 488 * @param val the new value 489 */ 490 public void setVal(int u, int v, float val) { 491 int i = indexer.getIndex(u, v); 492 if (i >= 0) { 493 vals[i] = val; 494 } 495 } 496 497 /** 498 * Returns the slice index for this pixel slice, i.e, the component number in the containing pixel pack. -1 is 499 * returned if the pixel slice is not associated with a pixel pack. 500 * 501 * @return the slice index 502 */ 503 public int getIndex() { 504 return idx; 505 } 506 507 /** 508 * Returns a reference to the data array associated with this pixel slice. 509 * @return the data array 510 */ 511 public float[] getArray() { 512 return vals; 513 } 514 515 /** 516 * Returns a {@link FloatProcessor} for this {@link PixelSlice} sharing the internal pixel data (nothing is 517 * copied). 518 * 519 * @return a {@link FloatProcessor} 520 */ 521 public FloatProcessor getFloatProcessor() { 522 return new FloatProcessor(getWidth(), getHeight(), vals); 523 } 524 525 /** 526 * Returns a {@link FloatProcessor} for this {@link PixelSlice} by copying the internal pixel data. 527 * 528 * @return a {@link FloatProcessor} 529 */ 530 public FloatProcessor getFloatProcessorCopy() { 531 return new FloatProcessor(getWidth(), getHeight(), Arrays.copyOf(vals, vals.length)); 532 } 533 534 /** 535 * Returns the length (number of pixels) of the associated 1D pixel array. 536 * @return the length of the image array 537 */ 538 public int getLength() { 539 return vals.length; 540 } 541 542 /** 543 * Returns the width of the associated image (see also {@link GridIndexer2D}). 544 * 545 * @return the image width 546 */ 547 public int getWidth() { 548 return PixelPack.this.getWidth(); 549 } 550 551 /** 552 * Returns the height of the associated image (see also {@link GridIndexer2D}). 553 * 554 * @return the image height 555 */ 556 public int getHeight() { 557 return PixelPack.this.getHeight(); 558 } 559 560 /** 561 * Sets all pixel values to zero. 562 */ 563 public void zero() { 564 Arrays.fill(this.vals, 0); 565 } 566 567 /** 568 * Copies the contents of this pixel slice to another pixel slice. 569 * 570 * @param other the pixel slice to modified 571 */ 572 public void copyTo(PixelSlice other) { 573 System.arraycopy(this.vals, 0, other.vals, 0, this.vals.length); 574 } 575 576 /** 577 * Returns the pixel values from the 3x3 neighborhood centered at the specified position. The 3x3 array 578 * {@code nh[x][y]} has the coordinates x = 0,..,2 and y = 0,..,2; the value at position {@code [1][1]} belongs 579 * to the specified position. If a non-null array is supplied, it is filled and returned. If null, a new array 580 * is created and returned. 581 * 582 * @param uc the center x-position 583 * @param vc the center y-position 584 * @param nh a 3x3 array or null 585 * @return a 3x3 array of pixel values 586 */ 587 public float[][] get3x3Neighborhood(int uc, int vc, float[][] nh) { 588 if (nh == null) 589 nh = new float[3][3]; 590 for (int i = 0; i < 3; i++) { 591 int u = uc - 1 + i; 592 for (int j = 0; j < 3; j++) { 593 int v = vc - 1 + j; 594 nh[i][j] = getVal(u, v); 595 } 596 } 597 return nh; 598 } 599 } 600 601 // ------------------------------------------------------------------- 602 603 /** 604 * Copies the contents of an image processor to an existing pixel pack, which must be compatible w.r.t. size and 605 * depth. Does not scale pixel values. 606 * 607 * @param ip the image processor to be copied 608 */ 609 public void copyFromImageProcessor(ImageProcessor ip) { 610 copyFromImageProcessor(ip, 1.0); 611 } 612 613 /** 614 * Copies the contents of an image processor to an existing pixel pack, which must be compatible w.r.t. size and 615 * depth. Applies the specified scale factor to the pixel component values. 616 * 617 * @param ip the image processor to be copied 618 * @param scale scale factor applied to pixel component values 619 */ 620 public void copyFromImageProcessor(ImageProcessor ip, double scale) { 621 if (!isCompatibleTo(ip) ){ 622 throw new IllegalArgumentException("copyFromImageProcessor(): incompatible ImageProcessor/PixelPack)"); 623 } 624 if (ip instanceof ByteProcessor) 625 copyFromByteProcessor((ByteProcessor)ip, (float) scale); 626 else if (ip instanceof ShortProcessor) 627 copyFromShortProcessor((ShortProcessor)ip, (float) scale); 628 else if (ip instanceof FloatProcessor) 629 copyFromFloatProcessor((FloatProcessor)ip, (float) scale); 630 else if (ip instanceof ColorProcessor) 631 copyFromColorProcessor((ColorProcessor)ip, (float) scale); 632 else 633 throw new IllegalArgumentException("unknown processor type " + ip.getClass().getSimpleName()); 634 } 635 636 private void copyFromByteProcessor(ByteProcessor ip, final float scale) { 637 byte[] pixels = (byte[]) ip.getPixels(); 638 for (int i = 0; i < pixels.length; i++) { 639 data[0][i] = scale * (0xff & pixels[i]); 640 } 641 } 642 643 private void copyFromShortProcessor(ShortProcessor ip, final float scale) { 644 short[] pixels = (short[]) ip.getPixels(); 645 for (int i = 0; i < pixels.length; i++) { 646 data[0][i] = scale * (0xffff & pixels[i]); 647 } 648 } 649 650 private void copyFromFloatProcessor(FloatProcessor ip, final float scale) { 651 float[] pixels = (float[]) ip.getPixels(); 652// System.arraycopy(pixels, 0, data[0], 0, pixels.length); 653 for (int i = 0; i < pixels.length; i++) { 654 data[0][i] = scale * pixels[i]; 655 } 656 } 657 658 private void copyFromColorProcessor(ColorProcessor ip, final float scale) { 659 final int[] pixels = (int[]) ip.getPixels(); 660 final int[] rgb = new int[3]; 661 for (int i = 0; i < pixels.length; i++) { 662 RgbUtils.decodeIntToRgb(pixels[i], rgb); 663 data[0][i] = scale * rgb[0]; 664 data[1][i] = scale * rgb[1]; 665 data[2][i] = scale * rgb[2]; 666 } 667 } 668 669 // -------------------------------------------------------------------- 670 671 /** 672 * Converts this {@link PixelPack} to a new {@link ByteProcessor} instance. An exception is thrown if the depth of 673 * the pack is not equal 1. Pixel values are rounded, no scale factor is applied. 674 * 675 * @return a new {@link ByteProcessor} instance 676 */ 677 public ByteProcessor toByteProcessor() { 678 return toByteProcessor(1.0); 679 } 680 681 /** 682 * Converts this {@link PixelPack} to a new {@link ByteProcessor} instance. An exception is thrown if the depth of 683 * the pack is not equal 1. Applies the specified scale factor to pixel values. 684 * 685 * @param scale scale factor applied to pixel values (before rounding) 686 * @return a new {@link ByteProcessor} instance 687 */ 688 public ByteProcessor toByteProcessor(double scale) { 689 if (depth != 1) { 690 throw new UnsupportedOperationException("cannot convert to ByteProcessor, depth = " + depth); 691 } 692 ByteProcessor ip = new ByteProcessor(width, height); 693 copyToByteProcessor(ip, (float)scale); 694 return ip; 695 } 696 697 /** 698 * Converts this {@link PixelPack} to a new {@link ShortProcessor} instance. An exception is thrown if the depth of 699 * the pack is not equal 1. Pixel values are rounded, no scale factor is applied. 700 * 701 * @return a new {@link ShortProcessor} instance 702 */ 703 public ShortProcessor toShortProcessor() { 704 return toShortProcessor(1.0); 705 } 706 707 /** 708 * Converts this {@link PixelPack} to a new {@link ShortProcessor} instance. An exception is thrown if the depth of 709 * the pack is not equal 1. Applies the specified scale factor to pixel values. 710 * 711 * @param scale scale factor applied to pixel values (before rounding) 712 * @return a new {@link ShortProcessor} instance 713 */ 714 public ShortProcessor toShortProcessor(double scale) { 715 if (depth != 1) { 716 throw new UnsupportedOperationException("cannot convert to ShortProcessor, depth = " + depth); 717 } 718 ShortProcessor ip = new ShortProcessor(width, height); 719 copyToShortProcessor(ip, (float)scale); 720 return ip; 721 } 722 723 /** 724 * Converts this {@link PixelPack} to a new {@link FloatProcessor} instance. An exception is thrown if the depth of 725 * the pack is not equal 1. No scale factor is applied to pixel values. 726 * 727 * @return a new {@link FloatProcessor} instance 728 */ 729 public FloatProcessor toFloatProcessor() { 730 return toFloatProcessor(1.0); 731 } 732 733 /** 734 * Converts this {@link PixelPack} to a new {@link FloatProcessor} instance. An exception is thrown if the depth of 735 * the pack is not equal 1. 736 * 737 * @param scale scale factor applied to pixel values 738 * @return a new {@link FloatProcessor} instance 739 */ 740 public FloatProcessor toFloatProcessor(double scale) { 741 if (depth != 1) { 742 throw new UnsupportedOperationException("cannot convert to FloatProcessor, depth = " + depth); 743 } 744 FloatProcessor ip = new FloatProcessor(width, height); 745 copyToFloatProcessor(ip, (float)scale); 746 return ip; 747 } 748 749 /** 750 * Converts this {@link PixelPack} to a new {@link ColorProcessor} instance. An exception is thrown if the depth of 751 * the pack is not equal 3. Component values are rounded, no scale factor is applied. 752 * 753 * @return a new {@link ColorProcessor} instance 754 */ 755 public ColorProcessor toColorProcessor() { 756 return toColorProcessor(1.0); 757 } 758 759 /** 760 * Converts this {@link PixelPack} to a new {@link ColorProcessor} instance. An exception is thrown if the depth of 761 * the pack is not equal 3. 762 * 763 * @param scale scale factor applied to component values (before rounding) 764 * @return a new {@link ColorProcessor} instance 765 */ 766 public ColorProcessor toColorProcessor(double scale) { 767 if (depth != 3) { 768 throw new UnsupportedOperationException("cannot convert to ColorProcessor, depth = " + depth); 769 } 770 ColorProcessor ip = new ColorProcessor(width, height); 771 copyToColorProcessor(ip, (float)scale); 772 return ip; 773 } 774 775 // -------------------------------------------------------------------- 776 777 /** 778 * Converts this {@link PixelPack} to a new {@link ImageStack} with the same number of slices (see 779 * {@link #getDepth()}). The stack images are of type {@link FloatProcessor}. The resulting {@link ImageStack} does 780 * not share any pixel data with this {@link PixelPack}. 781 * 782 * @return a new {@link ImageStack} instance 783 */ 784 public ImageStack toImageStack() { 785 ImageStack stack = new ImageStack(width, height); 786 for (int k = 0; k < depth; k++) { 787 stack.addSlice(getSlice(k).getFloatProcessorCopy()); 788 } 789 return stack; 790 } 791 792 // -------------------------------------------------------------------- 793 794 /** 795 * Copies the contents of a pixel pack to an existing image processor. They must be compatible w.r.t. size and 796 * depth. Component values are rounded if necessary, no scale factor is applied. 797 * 798 * @param ip the receiving image processor 799 */ 800 public void copyToImageProcessor(ImageProcessor ip) { 801 copyToImageProcessor(ip, 1.0); 802 } 803 804 /** 805 * Copies the contents of a pixel pack to an existing image processor. They must be compatible w.r.t. size and 806 * depth. Component values are rounded if necessary, after the specified scale factor is applied. 807 * 808 * @param ip the receiving image processor 809 * @param scale scale factor applied to pixel values 810 */ 811 public void copyToImageProcessor(ImageProcessor ip, double scale) { 812 if (!this.isCompatibleTo(ip) ){ 813 throw new IllegalArgumentException("copyToImageProcessor(): incompatible ImageProcessor/PixelPack)"); 814 } 815 if (ip instanceof ByteProcessor) 816 copyToByteProcessor((ByteProcessor)ip, (float)scale); 817 else if (ip instanceof ShortProcessor) 818 copyToShortProcessor((ShortProcessor)ip, (float)scale); 819 else if (ip instanceof FloatProcessor) 820 copyToFloatProcessor((FloatProcessor)ip, (float)scale); 821 else if (ip instanceof ColorProcessor) 822 copyToColorProcessor((ColorProcessor)ip, (float)scale); 823 else 824 throw new IllegalArgumentException("unknown processor type " + ip.getClass().getSimpleName()); 825 } 826 827 private void copyToByteProcessor(ByteProcessor ip, float scale) { 828 byte[] pixels = (byte[]) ip.getPixels(); 829 float[] P = this.data[0]; 830 for (int i = 0; i < pixels.length; i++) { 831 int val = clipByte(Math.round(scale * P[i])); 832 pixels[i] = (byte) val; 833 } 834 } 835 836 private void copyToShortProcessor(ShortProcessor ip, float scale) { 837 short[] pixels = (short[]) ip.getPixels(); 838 float[] P = this.data[0]; 839 for (int i = 0; i < pixels.length; i++) { 840 int val = clipShort(Math.round(scale * P[i])); 841 pixels[i] = (short) val; 842 } 843 } 844 845 private void copyToFloatProcessor(FloatProcessor ip, float scale) { 846 float[] pixels = (float[]) ip.getPixels(); 847 float[] P = this.data[0]; 848// System.arraycopy(P, 0, pixels, 0, P.length); 849 for (int i = 0; i < pixels.length; i++) { 850 pixels[i] = scale * P[i]; 851 } 852 } 853 854 private void copyToColorProcessor(ColorProcessor ip, float scale) { 855 int[] pixels = (int[]) ip.getPixels(); 856 float[] R = this.data[0]; 857 float[] G = this.data[1]; 858 float[] B = this.data[2]; 859 int[] rgb = new int[3]; 860 for (int i = 0; i < pixels.length; i++) { 861 rgb[0] = clipByte(Math.round(scale * R[i])); 862 rgb[1] = clipByte(Math.round(scale * G[i])); 863 rgb[2] = clipByte(Math.round(scale * B[i])); 864 pixels[i] = RgbUtils.encodeRgbToInt(rgb); 865 } 866 } 867 868 // -------------------------------------------------------------------------- 869 870 private int clipByte(int val) { 871 if (val < 0) return 0; 872 if (val > 255) return 255; 873 return val; 874 } 875 876 private int clipShort(int val) { 877 if (val < 0) return 0; 878 if (val > 65535) return 65535; 879 return val; 880 } 881 882}