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}