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}