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 * (non-Javadoc) 170 * 171 * @see uk.ac.soton.ecs.jsh2.image.MultiBandImage#flattenMax() 172 */ 173 @Override 174 public FImage flattenMax() { 175 final int width = this.getWidth(); 176 final int height = this.getHeight(); 177 178 final FImage out = new FImage(width, height); 179 180 for (int y = 0; y < height; y++) { 181 for (int x = 0; x < width; x++) { 182 float max = (this.bands.get(0)).pixels[y][x]; 183 184 for (int i = 1; i < this.numBands(); i++) 185 if (max > (this.bands.get(i)).pixels[y][x]) 186 max = (this.bands.get(i)).pixels[y][x]; 187 188 out.pixels[y][x] = max; 189 } 190 } 191 192 return out; 193 } 194 195 @Override 196 public FImage flatten() { 197 // overly optimised flatten 198 199 final int width = this.getWidth(); 200 final int height = this.getHeight(); 201 202 final FImage out = new FImage(width, height); 203 final float[][] outp = out.pixels; 204 final int nb = this.numBands(); 205 206 for (int i = 1; i < nb; i++) { 207 final float[][] bnd = this.bands.get(i).pixels; 208 209 for (int y = 0; y < height; y++) { 210 for (int x = 0; x < width; x++) { 211 outp[y][x] += bnd[y][x]; 212 213 } 214 } 215 } 216 217 final float norm = 1f / nb; 218 final float[][] bnd = this.bands.get(0).pixels; 219 for (int y = 0; y < height; y++) { 220 for (int x = 0; x < width; x++) { 221 outp[y][x] = (outp[y][x] + bnd[y][x]) * norm; 222 } 223 } 224 225 return out; 226 } 227 228 /* 229 * (non-Javadoc) 230 * 231 * @see uk.ac.soton.ecs.jsh2.image.Image#getPixel(int, int) 232 */ 233 @Override 234 public Float[] getPixel(final int x, final int y) { 235 final Float[] pixels = new Float[this.bands.size()]; 236 237 for (int i = 0; i < this.bands.size(); i++) { 238 pixels[i] = this.bands.get(i).getPixel(x, y); 239 } 240 241 return pixels; 242 } 243 244 @Override 245 public Comparator<? super Float[]> getPixelComparator() { 246 return new Comparator<Float[]>() { 247 248 @Override 249 public int compare(final Float[] o1, final Float[] o2) { 250 int sumDiff = 0; 251 boolean anyDiff = false; 252 for (int i = 0; i < o1.length; i++) { 253 sumDiff += o1[i] - o2[i]; 254 anyDiff = sumDiff != 0 || anyDiff; 255 } 256 if (anyDiff) { 257 if (sumDiff > 0) 258 return 1; 259 else 260 return -1; 261 } else 262 return 0; 263 } 264 265 }; 266 } 267 268 /* 269 * (non-Javadoc) 270 * 271 * @see uk.ac.soton.ecs.jsh2.image.Image#getPixelInterp(double, double) 272 */ 273 @Override 274 public Float[] getPixelInterp(final double x, final double y) { 275 final Float[] result = new Float[this.bands.size()]; 276 277 for (int i = 0; i < this.bands.size(); i++) { 278 result[i] = this.bands.get(i).getPixelInterp(x, y); 279 } 280 281 return result; 282 } 283 284 /* 285 * (non-Javadoc) 286 * 287 * @see uk.ac.soton.ecs.jsh2.image.Image#getPixelInterp(double, 288 * double,Float[]) 289 */ 290 @Override 291 public Float[] getPixelInterp(final double x, final double y, final Float[] b) { 292 final Float[] result = new Float[this.bands.size()]; 293 294 for (int i = 0; i < this.bands.size(); i++) { 295 result[i] = this.bands.get(i).getPixelInterp(x, y, b[i]); 296 } 297 298 return result; 299 } 300 301 /** 302 * Assign planar RGB bytes (R1G1B1R2G2B2...) to this image. 303 * 304 * @param bytes 305 * the byte array 306 * @param width 307 * the width of the byte image 308 * @param height 309 * the height of the byte image 310 * @return this 311 */ 312 public MBFImage internalAssign(final byte[] bytes, final int width, final int height) { 313 if (this.getWidth() != width || this.getHeight() != height) 314 this.internalAssign(this.newInstance(width, height)); 315 316 final float[][] br = this.bands.get(0).pixels; 317 final float[][] bg = this.bands.get(1).pixels; 318 final float[][] bb = this.bands.get(2).pixels; 319 320 for (int i = 0, y = 0; y < height; y++) { 321 for (int x = 0; x < width; x++) { 322 final int blue = bytes[i++] & 0xff; 323 final int green = (bytes[i++]) & 0xff; 324 final int red = (bytes[i++]) & 0xff; 325 br[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[red]; 326 bg[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[green]; 327 bb[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[blue]; 328 } 329 } 330 331 return this; 332 } 333 334 /* 335 * (non-Javadoc) 336 * 337 * @see org.openimaj.image.Image#internalAssign(int[], int, int) 338 */ 339 @Override 340 public MBFImage internalAssign(final int[] data, final int width, final int height) { 341 if (this.getWidth() != width || this.getHeight() != height) 342 this.internalAssign(this.newInstance(width, height)); 343 344 final float[][] br = this.bands.get(0).pixels; 345 final float[][] bg = this.bands.get(1).pixels; 346 final float[][] bb = this.bands.get(2).pixels; 347 float[][] ba = null; 348 349 if (this.colourSpace == ColourSpace.RGBA) 350 ba = this.bands.get(3).pixels; 351 352 for (int i = 0, y = 0; y < height; y++) { 353 for (int x = 0; x < width; x++, i++) { 354 final int rgb = data[i]; 355 final int alpha = ((rgb >> 24) & 0xff); 356 final int red = ((rgb >> 16) & 0xff); 357 final int green = ((rgb >> 8) & 0xff); 358 final int blue = ((rgb) & 0xff); 359 br[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[red]; 360 bg[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[green]; 361 bb[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[blue]; 362 363 if (ba != null) 364 ba[y][x] = ImageUtilities.BYTE_TO_FLOAT_LUT[alpha]; 365 } 366 } 367 368 return this; 369 } 370 371 @Override 372 protected Float intToT(final int n) { 373 return (float) n; 374 } 375 376 @Override 377 public FImage newBandInstance(final int width, final int height) { 378 return new FImage(width, height); 379 } 380 381 @Override 382 public MBFImage newInstance() { 383 return new MBFImage(); 384 } 385 386 /* 387 * (non-Javadoc) 388 * 389 * @see uk.ac.soton.ecs.jsh2.image.MultiBandImage#newInstance(int, int) 390 */ 391 @Override 392 public MBFImage newInstance(final int width, final int height) { 393 final MBFImage ret = new MBFImage(width, height, this.bands.size()); 394 395 ret.colourSpace = this.colourSpace; 396 397 return ret; 398 } 399 400 @Override 401 public MBFImageRenderer createRenderer() { 402 return new MBFImageRenderer(this); 403 } 404 405 @Override 406 public MBFImageRenderer createRenderer(final RenderHints options) { 407 return new MBFImageRenderer(this, options); 408 } 409 410 /** 411 * Get the value of the pixel at coordinate p 412 * 413 * @param p 414 * The coordinate to get 415 * 416 * @return The pixel value at (x, y) 417 */ 418 public float[] getPixelNative(final Pixel p) { 419 return this.getPixelNative(p.x, p.y); 420 } 421 422 /** 423 * Get the value of the pixel at coordinate <code>(x, y)</code>. 424 * 425 * @param x 426 * The x-coordinate to get 427 * @param y 428 * The y-coordinate to get 429 * 430 * @return The pixel value at (x, y) 431 */ 432 public float[] getPixelNative(final int x, final int y) { 433 final float[] pixels = new float[this.bands.size()]; 434 435 for (int i = 0; i < this.bands.size(); i++) { 436 pixels[i] = this.bands.get(i).getPixel(x, y); 437 } 438 439 return pixels; 440 } 441 442 /** 443 * Returns the pixels in this image as a vector (an array of the pixel 444 * type). 445 * 446 * @param f 447 * The array into which to place the data 448 * @return The pixels in the image as a vector (a reference to the given 449 * array). 450 */ 451 public float[][] getPixelVectorNative(final float[][] f) { 452 for (int y = 0; y < this.getHeight(); y++) 453 for (int x = 0; x < this.getWidth(); x++) 454 f[x + y * this.getWidth()] = this.getPixelNative(x, y); 455 456 return f; 457 } 458 459 /** 460 * Sets the pixel at <code>(x,y)</code> to the given value. Side-affects 461 * this image. 462 * 463 * @param x 464 * The x-coordinate of the pixel to set 465 * @param y 466 * The y-coordinate of the pixel to set 467 * @param val 468 * The value to set the pixel to. 469 */ 470 public void setPixelNative(final int x, final int y, final float[] val) { 471 final int np = this.bands.size(); 472 if (np == val.length) 473 for (int i = 0; i < np; i++) 474 this.bands.get(i).setPixel(x, y, val[i]); 475 else { 476 final int offset = val.length - np; 477 for (int i = 0; i < np; i++) 478 if (i + offset >= 0) 479 this.bands.get(i).setPixel(x, y, val[i + offset]); 480 } 481 } 482 483 /** 484 * {@inheritDoc} 485 * <p> 486 * This method assumes the last band in the multiband image is the alpha 487 * channel. This allows a 2-channel MBFImage where the first image is an 488 * FImage and the second an alpha channel, as well as a standard RGBA image. 489 * 490 * @see org.openimaj.image.Image#overlayInplace(org.openimaj.image.Image, 491 * int, int) 492 */ 493 @Override 494 public MBFImage overlayInplace(final MBFImage image, final int x, final int y) { 495 // Assume the alpha channel is the last band 496 final FImage alpha = image.getBand(image.numBands() - 1); 497 498 for (int i = 0; i < this.numBands(); i++) 499 this.bands.get(i).overlayInplace(image.bands.get(i), alpha, x, y); 500 501 return this; 502 } 503 504 /** 505 * Create a random RGB image. 506 * 507 * @param width 508 * the width 509 * @param height 510 * the height 511 * @return the image 512 */ 513 public static MBFImage randomImage(final int width, final int height) { 514 final MBFImage img = new MBFImage(); 515 img.colourSpace = ColourSpace.RGB; 516 517 for (int i = 0; i < 3; i++) { 518 img.bands.add(FImage.randomImage(width, height)); 519 } 520 521 return img; 522 } 523 524 /** 525 * Convenience method to create an RGB {@link MBFImage} from an 526 * {@link FImage} by cloning the {@link FImage} for each of the R, G and B 527 * bands. 528 * 529 * @param image 530 * the {@link FImage} to convert 531 * @return the new RGB {@link MBFImage} 532 */ 533 public static MBFImage createRGB(final FImage image) { 534 return new MBFImage(image.clone(), image.clone(), image.clone()); 535 } 536 537 @Override 538 public MBFImage fill(final Float[] colour) 539 { 540 return super.fill(this.colourSpace.sanitise(colour)); 541 } 542 543 @Override 544 public void setPixel(final int x, final int y, final Float[] val) 545 { 546 // Check if we have an alpha channel. If we do, we'll use alpha 547 // compositing, otherwise, we'll simply copy the pixel colour into 548 // the pixel position. 549 if (this.colourSpace == ColourSpace.RGBA && this.numBands() >= 4 && val.length >= 4 550 && x >= 0 && x < this.getWidth() && y >= 0 && y < this.getHeight()) 551 { 552 final float[] p = ImageUtilities.alphaCompositePixel(this.getPixel(x, y), val); 553 this.getBand(0).pixels[y][x] = p[0]; 554 this.getBand(1).pixels[y][x] = p[1]; 555 this.getBand(2).pixels[y][x] = p[2]; 556 if (this.numBands() >= 4) 557 this.getBand(3).pixels[y][x] = p[3]; 558 } 559 else 560 super.setPixel(x, y, val); 561 } 562 563 @Override 564 protected Float[] createPixelArray(int n) { 565 return new Float[n]; 566 } 567}