View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package org.openimaj.image;
31  
32  import java.util.Comparator;
33  
34  import org.openimaj.image.colour.ColourSpace;
35  import org.openimaj.image.pixel.Pixel;
36  import org.openimaj.image.renderer.MBFImageRenderer;
37  import org.openimaj.image.renderer.RenderHints;
38  
39  /**
40   * A multiband floating-point image.
41   *
42   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
43   */
44  public class MBFImage extends MultiBandImage<Float, MBFImage, FImage> {
45  	private static final long serialVersionUID = 1L;
46  
47  	/**
48  	 * Construct an empty MBFImage with a the default RGB colourspace
49  	 */
50  	public MBFImage() {
51  		super(ColourSpace.RGB);
52  	}
53  
54  	/**
55  	 * Construct an MBFImage from single band images. The given images are used
56  	 * directly as the bands and are not cloned.
57  	 *
58  	 * @param colourSpace
59  	 *            the colourspace
60  	 * @param images
61  	 *            the bands
62  	 */
63  	public MBFImage(final ColourSpace colourSpace, final FImage... images) {
64  		super(colourSpace, images);
65  	}
66  
67  	/**
68  	 * Construct an MBFImage from single band images with the default RGB
69  	 * colourspace if there are three images, RGBA if there are 4 images, or
70  	 * CUSTOM otherwise. The given images are used directly as the bands and are
71  	 * not cloned; if you want to create an RGB {@link MBFImage} from a single
72  	 * {@link FImage}, you would need to clone the {@link FImage} at least
73  	 * twice.
74  	 *
75  	 * @param images
76  	 *            the bands
77  	 */
78  	public MBFImage(final FImage... images) {
79  		super(images.length == 3 ? ColourSpace.RGB : images.length == 4 ? ColourSpace.RGBA : ColourSpace.CUSTOM, images);
80  	}
81  
82  	/**
83  	 * Construct an empty RGB image (3 bands)
84  	 *
85  	 * @param width
86  	 *            Width of image
87  	 * @param height
88  	 *            Height of image
89  	 */
90  	public MBFImage(final int width, final int height) {
91  		this(width, height, ColourSpace.RGB);
92  	}
93  
94  	/**
95  	 * Construct an empty image
96  	 *
97  	 * @param width
98  	 *            Width of image
99  	 * @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 }