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; 031 032import java.util.Comparator; 033 034import org.openimaj.image.colour.ColourSpace; 035import org.openimaj.image.pixel.Pixel; 036import org.openimaj.image.renderer.MBFImageRenderer; 037import org.openimaj.image.renderer.RenderHints; 038 039/** 040 * A multiband floating-point image. 041 * 042 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 043 */ 044public class MBFImage extends MultiBandImage<Float, MBFImage, FImage> { 045 private static final long serialVersionUID = 1L; 046 047 /** 048 * Construct an empty MBFImage with a the default RGB colourspace 049 */ 050 public MBFImage() { 051 super(ColourSpace.RGB); 052 } 053 054 /** 055 * Construct an MBFImage from single band images. The given images are used 056 * directly as the bands and are not cloned. 057 * 058 * @param colourSpace 059 * the colourspace 060 * @param images 061 * the bands 062 */ 063 public MBFImage(final ColourSpace colourSpace, final FImage... images) { 064 super(colourSpace, images); 065 } 066 067 /** 068 * Construct an MBFImage from single band images with the default RGB 069 * colourspace if there are three images, RGBA if there are 4 images, or 070 * CUSTOM otherwise. The given images are used directly as the bands and are 071 * not cloned; if you want to create an RGB {@link MBFImage} from a single 072 * {@link FImage}, you would need to clone the {@link FImage} at least 073 * twice. 074 * 075 * @param images 076 * the bands 077 */ 078 public MBFImage(final FImage... images) { 079 super(images.length == 3 ? ColourSpace.RGB : images.length == 4 ? ColourSpace.RGBA : ColourSpace.CUSTOM, images); 080 } 081 082 /** 083 * Construct an empty RGB image (3 bands) 084 * 085 * @param width 086 * Width of image 087 * @param height 088 * Height of image 089 */ 090 public MBFImage(final int width, final int height) { 091 this(width, height, ColourSpace.RGB); 092 } 093 094 /** 095 * Construct an empty image 096 * 097 * @param width 098 * Width of image 099 * @param height 100 * Height of image 101 * @param colourSpace 102 * the colourspace 103 */ 104 public MBFImage(final int width, final int height, final ColourSpace colourSpace) { 105 this.colourSpace = colourSpace; 106 107 for (int i = 0; i < colourSpace.getNumBands(); i++) { 108 this.bands.add(new FImage(width, height)); 109 } 110 } 111 112 /** 113 * Construct an empty image. If the number of bands is 3, RGB is assumed, if 114 * the number is 4, then RGBA is assumed, otherwise the colourspace is set 115 * to CUSTOM. 116 * 117 * @param width 118 * Width of image 119 * @param height 120 * Height of image 121 * @param nbands 122 * number of bands 123 */ 124 public MBFImage(final int width, final int height, final int nbands) { 125 if (nbands == 3) 126 this.colourSpace = ColourSpace.RGB; 127 else if (nbands == 4) 128 this.colourSpace = ColourSpace.RGBA; 129 130 for (int i = 0; i < nbands; i++) { 131 this.bands.add(new FImage(width, height)); 132 } 133 } 134 135 /** 136 * Create an image from a BufferedImage object. Resultant image have RGB 137 * bands in the 0-1 range. 138 * 139 * @param data 140 * array of packed ARGB pixels 141 * @param width 142 * the image width 143 * @param height 144 * the image height 145 */ 146 public MBFImage(final int[] data, final int width, final int height) { 147 this(data, width, height, false); 148 } 149 150 /** 151 * Create an image from a int[] object. Resultant image will be in the 0-1 152 * range. If alpha is true, bands will be RGBA, otherwise RGB 153 * 154 * @param data 155 * array of packed ARGB pixels 156 * @param width 157 * the image width 158 * @param height 159 * the image height 160 * @param alpha 161 * should we load the alpha channel 162 */ 163 public MBFImage(final int[] data, final int width, final int height, final boolean alpha) { 164 this(width, height, alpha ? 4 : 3); 165 this.internalAssign(data, width, height); 166 } 167 168 /** 169 * Create an MBFImage from an array of double values with the given width 170 * and height. The length of the array must equal the width multiplied by 171 * the height by the number of bands. The values will be downcast to floats. 172 * The data can either be interlaced (rgbrgb...) or band at a time (rrrr... 173 * gggg... bbbb...). 174 * 175 * @param ds 176 * An array of floating point values. 177 * @param width 178 * The width of the resulting image. 179 * @param height 180 * The height of the resulting image. 181 * @param nbands 182 * the number of bands 183 * @param interlaced 184 * if true the data in the array is interlaced 185 */ 186 public MBFImage(double[] ds, int width, int height, int nbands, boolean interlaced) { 187 if (interlaced) { 188 for (int i = 0; i < nbands; i++) { 189 bands.add(new FImage(width, height)); 190 } 191 for (int y = 0, c = 0; y < height; y++) { 192 for (int x = 0; x < width; x++) { 193 for (int i = 0; i < nbands; i++, c++) { 194 bands.get(i).pixels[y][x] = (float) ds[c]; 195 } 196 } 197 } 198 } else { 199 for (int i = 0; i < nbands; i++) { 200 bands.add(new FImage(ds, width, height, i * width * height)); 201 } 202 } 203 } 204 205 /* 206 * (non-Javadoc) 207 * 208 * @see uk.ac.soton.ecs.jsh2.image.MultiBandImage#flattenMax() 209 */ 210 @Override 211 public FImage flattenMax() { 212 final int width = this.getWidth(); 213 final int height = this.getHeight(); 214 215 final FImage out = new FImage(width, height); 216 217 for (int y = 0; y < height; y++) { 218 for (int x = 0; x < width; x++) { 219 float max = (this.bands.get(0)).pixels[y][x]; 220 221 for (int i = 1; i < this.numBands(); i++) 222 if (max < (this.bands.get(i)).pixels[y][x]) 223 max = (this.bands.get(i)).pixels[y][x]; 224 225 out.pixels[y][x] = max; 226 } 227 } 228 229 return out; 230 } 231 232 @Override 233 public FImage flatten() { 234 // overly optimised flatten 235 236 final int width = this.getWidth(); 237 final int height = this.getHeight(); 238 239 final FImage out = new FImage(width, height); 240 final float[][] outp = out.pixels; 241 final int nb = this.numBands(); 242 243 for (int i = 1; i < nb; i++) { 244 final float[][] bnd = this.bands.get(i).pixels; 245 246 for (int y = 0; y < height; y++) { 247 for (int x = 0; x < width; x++) { 248 outp[y][x] += bnd[y][x]; 249 250 } 251 } 252 } 253 254 final float norm = 1f / nb; 255 final float[][] bnd = this.bands.get(0).pixels; 256 for (int y = 0; y < height; y++) { 257 for (int x = 0; x < width; x++) { 258 outp[y][x] = (outp[y][x] + bnd[y][x]) * norm; 259 } 260 } 261 262 return out; 263 } 264 265 /* 266 * (non-Javadoc) 267 * 268 * @see uk.ac.soton.ecs.jsh2.image.Image#getPixel(int, int) 269 */ 270 @Override 271 public Float[] getPixel(final int x, final int y) { 272 final Float[] pixels = new Float[this.bands.size()]; 273 274 for (int i = 0; i < this.bands.size(); i++) { 275 pixels[i] = this.bands.get(i).getPixel(x, y); 276 } 277 278 return pixels; 279 } 280 281 @Override 282 public Comparator<? super Float[]> getPixelComparator() { 283 return new Comparator<Float[]>() { 284 285 @Override 286 public int compare(final Float[] o1, final Float[] o2) { 287 int sumDiff = 0; 288 boolean anyDiff = false; 289 for (int i = 0; i < o1.length; i++) { 290 sumDiff += o1[i] - o2[i]; 291 anyDiff = sumDiff != 0 || anyDiff; 292 } 293 if (anyDiff) { 294 if (sumDiff > 0) 295 return 1; 296 else 297 return -1; 298 } else 299 return 0; 300 } 301 302 }; 303 } 304 305 /* 306 * (non-Javadoc) 307 * 308 * @see uk.ac.soton.ecs.jsh2.image.Image#getPixelInterp(double, double) 309 */ 310 @Override 311 public Float[] getPixelInterp(final double x, final double y) { 312 final Float[] result = new Float[this.bands.size()]; 313 314 for (int i = 0; i < this.bands.size(); i++) { 315 result[i] = this.bands.get(i).getPixelInterp(x, y); 316 } 317 318 return result; 319 } 320 321 /* 322 * (non-Javadoc) 323 * 324 * @see uk.ac.soton.ecs.jsh2.image.Image#getPixelInterp(double, 325 * double,Float[]) 326 */ 327 @Override 328 public Float[] getPixelInterp(final double x, final double y, final Float[] b) { 329 final Float[] result = new Float[this.bands.size()]; 330 331 for (int i = 0; i < this.bands.size(); i++) { 332 result[i] = this.bands.get(i).getPixelInterp(x, y, b[i]); 333 } 334 335 return result; 336 } 337 338 /** 339 * Assign planar RGB bytes (R1G1B1R2G2B2...) to this image. 340 * 341 * @param bytes 342 * the byte array 343 * @param width 344 * the width of the byte image 345 * @param height 346 * the height of the byte image 347 * @return this 348 */ 349 public MBFImage internalAssign(final byte[] bytes, final int width, final int height) { 350 if (this.getWidth() != width || this.getHeight() != height) 351 this.internalAssign(this.newInstance(width, height)); 352 353 final float[][] br = this.bands.get(0).pixels; 354 final float[][] bg = this.bands.get(1).pixels; 355 final float[][] bb = this.bands.get(2).pixels; 356 357 for (int i = 0, y = 0; y < height; y++) { 358 for (int x = 0; x < width; x++) { 359 final int red = (bytes[i++]) & 0xff; 360 final int green = (bytes[i++]) & 0xff; 361 final int blue = bytes[i++] & 0xff; 362 br[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[red]; 363 bg[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[green]; 364 bb[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[blue]; 365 } 366 } 367 368 return this; 369 } 370 371 /* 372 * (non-Javadoc) 373 * 374 * @see org.openimaj.image.Image#internalAssign(int[], int, int) 375 */ 376 @Override 377 public MBFImage internalAssign(final int[] data, final int width, final int height) { 378 if (this.getWidth() != width || this.getHeight() != height) 379 this.internalAssign(this.newInstance(width, height)); 380 381 final float[][] br = this.bands.get(0).pixels; 382 final float[][] bg = this.bands.get(1).pixels; 383 final float[][] bb = this.bands.get(2).pixels; 384 float[][] ba = null; 385 386 if (this.colourSpace == ColourSpace.RGBA) 387 ba = this.bands.get(3).pixels; 388 389 for (int i = 0, y = 0; y < height; y++) { 390 for (int x = 0; x < width; x++, i++) { 391 final int rgb = data[i]; 392 final int alpha = ((rgb >> 24) & 0xff); 393 final int red = ((rgb >> 16) & 0xff); 394 final int green = ((rgb >> 8) & 0xff); 395 final int blue = ((rgb) & 0xff); 396 br[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[red]; 397 bg[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[green]; 398 bb[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[blue]; 399 400 if (ba != null) 401 ba[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[alpha]; 402 } 403 } 404 405 return this; 406 } 407 408 @Override 409 protected Float intToT(final int n) { 410 return (float) n; 411 } 412 413 @Override 414 public FImage newBandInstance(final int width, final int height) { 415 return new FImage(width, height); 416 } 417 418 @Override 419 public MBFImage newInstance() { 420 return new MBFImage(); 421 } 422 423 /* 424 * (non-Javadoc) 425 * 426 * @see uk.ac.soton.ecs.jsh2.image.MultiBandImage#newInstance(int, int) 427 */ 428 @Override 429 public MBFImage newInstance(final int width, final int height) { 430 final MBFImage ret = new MBFImage(width, height, this.bands.size()); 431 432 ret.colourSpace = this.colourSpace; 433 434 return ret; 435 } 436 437 @Override 438 public MBFImageRenderer createRenderer() { 439 return new MBFImageRenderer(this); 440 } 441 442 @Override 443 public MBFImageRenderer createRenderer(final RenderHints options) { 444 return new MBFImageRenderer(this, options); 445 } 446 447 /** 448 * Get the value of the pixel at coordinate p 449 * 450 * @param p 451 * The coordinate to get 452 * 453 * @return The pixel value at (x, y) 454 */ 455 public float[] getPixelNative(final Pixel p) { 456 return this.getPixelNative(p.x, p.y); 457 } 458 459 /** 460 * Get the value of the pixel at coordinate <code>(x, y)</code>. 461 * 462 * @param x 463 * The x-coordinate to get 464 * @param y 465 * The y-coordinate to get 466 * 467 * @return The pixel value at (x, y) 468 */ 469 public float[] getPixelNative(final int x, final int y) { 470 final float[] pixels = new float[this.bands.size()]; 471 472 for (int i = 0; i < this.bands.size(); i++) { 473 pixels[i] = this.bands.get(i).getPixelNative(x, y); 474 } 475 476 return pixels; 477 } 478 479 /** 480 * Returns the pixels in this image as a vector (an array of the pixel 481 * type). 482 * 483 * @param f 484 * The array into which to place the data 485 * @return The pixels in the image as a vector (a reference to the given 486 * array). 487 */ 488 public float[][] getPixelVectorNative(final float[][] f) { 489 for (int y = 0; y < this.getHeight(); y++) 490 for (int x = 0; x < this.getWidth(); x++) 491 f[x + y * this.getWidth()] = this.getPixelNative(x, y); 492 493 return f; 494 } 495 496 /** 497 * Sets the pixel at <code>(x,y)</code> to the given value. Side-affects 498 * this image. 499 * 500 * @param x 501 * The x-coordinate of the pixel to set 502 * @param y 503 * The y-coordinate of the pixel to set 504 * @param val 505 * The value to set the pixel to. 506 */ 507 public void setPixelNative(final int x, final int y, final float[] val) { 508 final int np = this.bands.size(); 509 if (np == val.length) 510 for (int i = 0; i < np; i++) 511 this.bands.get(i).setPixel(x, y, val[i]); 512 else { 513 final int offset = val.length - np; 514 for (int i = 0; i < np; i++) 515 if (i + offset >= 0) 516 this.bands.get(i).setPixel(x, y, val[i + offset]); 517 } 518 } 519 520 /** 521 * {@inheritDoc} 522 * <p> 523 * This method assumes the last band in the multiband image is the alpha 524 * channel. This allows a 2-channel MBFImage where the first image is an 525 * FImage and the second an alpha channel, as well as a standard RGBA image. 526 * 527 * @see org.openimaj.image.Image#overlayInplace(org.openimaj.image.Image, 528 * int, int) 529 */ 530 @Override 531 public MBFImage overlayInplace(final MBFImage image, final int x, final int y) { 532 // Assume the alpha channel is the last band 533 final FImage alpha = image.getBand(image.numBands() - 1); 534 535 for (int i = 0; i < this.numBands(); i++) 536 this.bands.get(i).overlayInplace(image.bands.get(i), alpha, x, y); 537 538 return this; 539 } 540 541 /** 542 * Create a random RGB image. 543 * 544 * @param width 545 * the width 546 * @param height 547 * the height 548 * @return the image 549 */ 550 public static MBFImage randomImage(final int width, final int height) { 551 final MBFImage img = new MBFImage(); 552 img.colourSpace = ColourSpace.RGB; 553 554 for (int i = 0; i < 3; i++) { 555 img.bands.add(FImage.randomImage(width, height)); 556 } 557 558 return img; 559 } 560 561 /** 562 * Convenience method to create an RGB {@link MBFImage} from an 563 * {@link FImage} by cloning the {@link FImage} for each of the R, G and B 564 * bands. 565 * 566 * @param image 567 * the {@link FImage} to convert 568 * @return the new RGB {@link MBFImage} 569 */ 570 public static MBFImage createRGB(final FImage image) { 571 return new MBFImage(image.clone(), image.clone(), image.clone()); 572 } 573 574 @Override 575 public MBFImage fill(final Float[] colour) { 576 return super.fill(this.colourSpace.sanitise(colour)); 577 } 578 579 @Override 580 public void setPixel(final int x, final int y, final Float[] val) { 581 // Check if we have an alpha channel. If we do, we'll use alpha 582 // compositing, otherwise, we'll simply copy the pixel colour into 583 // the pixel position. 584 if (this.colourSpace == ColourSpace.RGBA && this.numBands() >= 4 && val.length >= 4 585 && x >= 0 && x < this.getWidth() && y >= 0 && y < this.getHeight()) 586 { 587 final float[] p = ImageUtilities.alphaCompositePixel(this.getPixel(x, y), val); 588 this.getBand(0).pixels[y][x] = p[0]; 589 this.getBand(1).pixels[y][x] = p[1]; 590 this.getBand(2).pixels[y][x] = p[2]; 591 if (this.numBands() >= 4) 592 this.getBand(3).pixels[y][x] = p[3]; 593 } else 594 super.setPixel(x, y, val); 595 } 596 597 @Override 598 protected Float[] createPixelArray(int n) { 599 return new Float[n]; 600 } 601 602 /** 603 * Returns the pixels of the image as a vector (array) of doubles. 604 * 605 * @return the pixels of the image as a vector (array) of doubles. 606 */ 607 public double[] getDoublePixelVector() { 608 final int height = getHeight(); 609 final int width = getWidth(); 610 final double f[] = new double[width * height * this.numBands()]; 611 for (int i = 0; i < this.bands.size(); i++) { 612 final float[][] pixels = bands.get(i).pixels; 613 614 for (int y = 0; y < height; y++) 615 for (int x = 0; x < width; x++) 616 f[x + y * width + i * height * width] = pixels[y][x]; 617 } 618 return f; 619 } 620}