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.awt.Graphics2D;
033import java.awt.image.BufferedImage;
034import java.awt.image.ComponentSampleModel;
035import java.awt.image.DataBufferByte;
036import java.awt.image.WritableRaster;
037import java.io.BufferedReader;
038import java.io.ByteArrayInputStream;
039import java.io.ByteArrayOutputStream;
040import java.io.DataInput;
041import java.io.DataOutput;
042import java.io.File;
043import java.io.FileInputStream;
044import java.io.IOException;
045import java.io.InputStream;
046import java.io.InputStreamReader;
047import java.io.OutputStream;
048import java.net.URL;
049import java.util.Map;
050import java.util.StringTokenizer;
051
052import javax.imageio.ImageIO;
053import javax.imageio.stream.ImageOutputStream;
054
055import org.apache.commons.lang.ArrayUtils;
056import org.apache.sanselan.ImageFormat;
057import org.apache.sanselan.Sanselan;
058import org.apache.sanselan.common.byteSources.ByteSource;
059import org.apache.sanselan.common.byteSources.ByteSourceInputStream;
060import org.openimaj.image.colour.ColourSpace;
061import org.openimaj.io.InputStreamObjectReader;
062
063/**
064 * A static utility class with methods for dealing with images.
065 * 
066 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
067 */
068public class ImageUtilities {
069        /**
070         * An {@link InputStreamObjectReader} for reading {@link FImage}s.
071         */
072        public static final InputStreamObjectReader<FImage> FIMAGE_READER = new InputStreamObjectReader<FImage>() {
073                @Override
074                public FImage read(final InputStream stream) throws IOException {
075                        return ImageUtilities.readF(stream);
076                }
077
078                @Override
079                public boolean canRead(final InputStream stream, final String name) {
080                        try {
081                                final ByteSource src = new ByteSourceInputStream(stream, name);
082
083                                return Sanselan.guessFormat(src) != ImageFormat.IMAGE_FORMAT_UNKNOWN;
084                        } catch (final Exception e) {
085                                return false;
086                        }
087                }
088        };
089
090        /**
091         * An {@link InputStreamObjectReader} for reading {@link MBFImage}s.
092         */
093        public static final InputStreamObjectReader<MBFImage> MBFIMAGE_READER = new InputStreamObjectReader<MBFImage>() {
094                @Override
095                public MBFImage read(final InputStream stream) throws IOException {
096                        return ImageUtilities.readMBF(stream);
097                }
098
099                @Override
100                public boolean canRead(final InputStream stream, final String name) {
101                        try {
102                                final ByteSource src = new ByteSourceInputStream(stream, name);
103
104                                return Sanselan.guessFormat(src) != ImageFormat.IMAGE_FORMAT_UNKNOWN;
105                        } catch (final Exception e) {
106                                return false;
107                        }
108                }
109        };
110
111        /** Lookup table for byte->float conversion */
112        public final static float[] BYTE_TO_FLOAT_LUT;
113
114        // Static initialisation
115        static {
116                BYTE_TO_FLOAT_LUT = new float[256];
117                for (int i = 0; i < ImageUtilities.BYTE_TO_FLOAT_LUT.length; i++)
118                        ImageUtilities.BYTE_TO_FLOAT_LUT[i] = i / 255f;
119        }
120
121        private ImageUtilities() {
122                // don't allow instances to be created
123        }
124
125        /**
126         * Calculate normalised RGB planes. Extracts the planes from the given RGB
127         * BufferedImage and returns an array of FImage of length 3. The images are
128         * ordered Red, Green and Blue.
129         * 
130         * @param bimg
131         *            A {@link BufferedImage} from which the planes are extracted.
132         * @return An array of {@link FImage}.
133         */
134        public static FImage[] getNormalisedColourPlanes(final BufferedImage bimg) {
135                final FImage[] images = new FImage[3];
136                final BufferedImage workingImage = ImageUtilities.createWorkingImage(bimg);
137                final int[] data = workingImage.getRGB(0, 0, workingImage.getWidth(), workingImage.getHeight(), null, 0,
138                                workingImage.getWidth());
139
140                images[0] = new FImage(data, bimg.getWidth(), bimg.getHeight(), ARGBPlane.RED);
141                images[1] = new FImage(data, bimg.getWidth(), bimg.getHeight(), ARGBPlane.GREEN);
142                images[2] = new FImage(data, bimg.getWidth(), bimg.getHeight(), ARGBPlane.BLUE);
143
144                int r, c;
145                for (r = 0; r < images[0].height; r++) {
146                        for (c = 0; c < images[0].width; c++) {
147                                final float norm = (float) Math.sqrt(
148                                                (images[0].pixels[r][c] * images[0].pixels[r][c]) +
149                                                                (images[1].pixels[r][c] * images[1].pixels[r][c]) +
150                                                                (images[2].pixels[r][c] * images[2].pixels[r][c])
151                                                );
152
153                                if (norm == 0 && images[0].pixels[r][c] == 0)
154                                        images[0].pixels[r][c] /= (1.0 / Math.sqrt(3.0));
155                                else
156                                        images[0].pixels[r][c] /= norm;
157
158                                if (norm == 0 && images[1].pixels[r][c] == 0)
159                                        images[1].pixels[r][c] /= (1.0 / Math.sqrt(3.0));
160                                else
161                                        images[1].pixels[r][c] /= norm;
162
163                                if (norm == 0 && images[2].pixels[r][c] == 0)
164                                        images[2].pixels[r][c] /= (1.0 / Math.sqrt(3.0));
165                                else
166                                        images[2].pixels[r][c] /= norm;
167                        }
168                }
169
170                return images;
171        }
172
173        /**
174         * Returns a ARGB BufferedImage, even if the input BufferedImage is not ARGB
175         * format.
176         * 
177         * @param bimg
178         *            The {@link BufferedImage} to normalise to ARGB
179         * @return An ARGB {@link BufferedImage}
180         */
181        public static BufferedImage createWorkingImage(final BufferedImage bimg) {
182                // to avoid performance complications in the getRGB method, we
183                // pre-calculate the RGB rep of the image
184                BufferedImage workingImage;
185                if (bimg.getType() == BufferedImage.TYPE_INT_ARGB) {
186                        workingImage = bimg;
187                } else {
188                        workingImage = new BufferedImage(bimg.getWidth(), bimg.getHeight(), BufferedImage.TYPE_INT_ARGB);
189                        final Graphics2D g2d = workingImage.createGraphics();
190                        g2d.drawImage(bimg, null, 0, 0);
191                }
192                return workingImage;
193        }
194
195        /**
196         * Write the given image to the given file with the given format name.
197         * Format names are the same as used by
198         * {@link ImageIO#write(java.awt.image.RenderedImage, String, File)}.
199         * 
200         * @param image
201         *            The image to write.
202         * @param formatName
203         *            a {@link String} containing the informal name of the format.
204         * @param output
205         *            The {@link File} to write the image to.
206         * @throws IOException
207         *             If the image cannot be written to the file.
208         */
209        public static void write(final Image<?, ?> image, final String formatName, final File output) throws IOException {
210                ImageIO.write(ImageUtilities.createBufferedImageForDisplay(image), formatName, output);
211        }
212
213        /**
214         * Write the given image to the given file with the given format name.
215         * Format names are the same as used by
216         * {@link ImageIO#write(java.awt.image.RenderedImage, String, OutputStream)}
217         * .
218         * 
219         * @param image
220         *            The image to write.
221         * @param formatName
222         *            a {@link String} containing the informal name of the format.
223         * @param output
224         *            The {@link OutputStream} to write the image to.
225         * @throws IOException
226         *             If the image cannot be written to the file.
227         */
228        public static void write(final Image<?, ?> image, final String formatName, final OutputStream output)
229                        throws IOException
230        {
231                ImageIO.write(ImageUtilities.createBufferedImageForDisplay(image), formatName, output);
232        }
233
234        /**
235         * Write the given image to the given file with the given format name.
236         * Format names are the same as used by
237         * {@link ImageIO#write(java.awt.image.RenderedImage, String, ImageOutputStream)}
238         * .
239         * 
240         * @param image
241         *            The image to write.
242         * @param formatName
243         *            a {@link String} containing the informal name of the format.
244         * @param output
245         *            The {@link ImageOutputStream} to write the image to.
246         * @throws IOException
247         *             If the image cannot be written to the file.
248         */
249        public static void write(final Image<?, ?> image, final String formatName, final ImageOutputStream output)
250                        throws IOException
251        {
252                ImageIO.write(ImageUtilities.createBufferedImageForDisplay(image), formatName, output);
253        }
254
255        /**
256         * Write the given image to the given file, guessing the format name from
257         * the extension. Format names are the same as used by
258         * {@link ImageIO#write(java.awt.image.RenderedImage, String, File)}.
259         * 
260         * @param image
261         *            The image to write.
262         * @param output
263         *            The {@link File} to write the image to.
264         * @throws IOException
265         *             If the image cannot be written to the file.
266         */
267        public static void write(final Image<?, ?> image, final File output) throws IOException {
268                final String name = output.getName();
269                String format = name.substring(name.lastIndexOf(".") + 1);
270
271                format = format.toLowerCase().trim();
272
273                ImageIO.write(ImageUtilities.createBufferedImageForDisplay(image), format, output);
274        }
275
276        /**
277         * Create an FImage from a buffered image.
278         * 
279         * @param image
280         *            the image
281         * @return an FImage representation of the input image
282         */
283        public static FImage createFImage(final BufferedImage image) {
284                final BufferedImage bimg = ImageUtilities.createWorkingImage(image);
285                final int[] data = bimg.getRGB(0, 0, bimg.getWidth(), bimg.getHeight(), null, 0, bimg.getWidth());
286
287                return new FImage(data, bimg.getWidth(), bimg.getHeight());
288        }
289
290        /**
291         * Create an MBFImage from a buffered image.
292         * 
293         * @param image
294         *            the image
295         * @param alpha
296         *            should the resultant MBFImage have an alpha channel
297         * @return an MBFImage representation of the input image
298         */
299        public static MBFImage createMBFImage(final BufferedImage image, final boolean alpha) {
300                final BufferedImage bimg = ImageUtilities.createWorkingImage(image);
301                final int[] data = bimg.getRGB(0, 0, bimg.getWidth(), bimg.getHeight(), null, 0, bimg.getWidth());
302
303                return new MBFImage(data, bimg.getWidth(), bimg.getHeight(), alpha);
304        }
305
306        /**
307         * Reads an {@link FImage} from the given file.
308         * 
309         * @param input
310         *            The file to read the {@link FImage} from.
311         * @return An {@link FImage}
312         * @throws IOException
313         *             if the file cannot be read
314         */
315        public static FImage readF(final File input) throws IOException {
316                return ImageUtilities.createFImage(ExtendedImageIO.read(input));
317        }
318
319        /**
320         * Reads an {@link FImage} from the given input stream.
321         * 
322         * @param input
323         *            The input stream to read the {@link FImage} from.
324         * @return An {@link FImage}
325         * @throws IOException
326         *             if the stream cannot be read
327         */
328        public static FImage readF(final InputStream input) throws IOException {
329                return ImageUtilities.createFImage(ExtendedImageIO.read(input));
330        }
331
332        /**
333         * Reads an {@link FImage} from the given URL.
334         * 
335         * @param input
336         *            The URL to read the {@link FImage} from.
337         * @return An {@link FImage}
338         * @throws IOException
339         *             if the URL stream cannot be read
340         */
341        public static FImage readF(final URL input) throws IOException {
342                return ImageUtilities.createFImage(ExtendedImageIO.read(input));
343        }
344
345        /**
346         * Reads an {@link MBFImage} from the given file.
347         * 
348         * @param input
349         *            The file to read the {@link MBFImage} from.
350         * @return An {@link MBFImage}
351         * @throws IOException
352         *             if the file cannot be read
353         */
354        public static MBFImage readMBF(final File input) throws IOException {
355                return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), false);
356        }
357
358        /**
359         * Reads an {@link MBFImage} from the given input stream.
360         * 
361         * @param input
362         *            The input stream to read the {@link MBFImage} from.
363         * @return An {@link MBFImage}
364         * @throws IOException
365         *             if the stream cannot be read
366         */
367        public static MBFImage readMBF(final InputStream input) throws IOException {
368                return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), false);
369        }
370
371        /**
372         * Reads an {@link MBFImage} from the given URL.
373         * 
374         * @param input
375         *            The URL to read the {@link MBFImage} from.
376         * @return An {@link MBFImage}
377         * @throws IOException
378         *             if the URL stream cannot be read
379         */
380        public static MBFImage readMBF(final URL input) throws IOException {
381                return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), false);
382        }
383
384        /**
385         * Reads an {@link MBFImage} from the given file. The resultant MBImage will
386         * contain an alpha channel
387         * 
388         * @param input
389         *            The file to read the {@link MBFImage} from.
390         * @return An {@link MBFImage}
391         * @throws IOException
392         *             if the file cannot be read
393         */
394        public static MBFImage readMBFAlpha(final File input) throws IOException {
395                return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), true);
396        }
397
398        /**
399         * Reads an {@link MBFImage} from the given input stream. The resultant
400         * MBImage will contain an alpha channel
401         * 
402         * @param input
403         *            The input stream to read the {@link MBFImage} from.
404         * @return An {@link MBFImage}
405         * @throws IOException
406         *             if the stream cannot be read
407         */
408        public static MBFImage readMBFAlpha(final InputStream input) throws IOException {
409                return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), true);
410        }
411
412        /**
413         * Reads an {@link MBFImage} from the given URL. The resultant MBImage will
414         * contain an alpha channel
415         * 
416         * @param input
417         *            The URL to read the {@link MBFImage} from.
418         * @return An {@link MBFImage}
419         * @throws IOException
420         *             if the URL stream cannot be read
421         */
422        public static MBFImage readMBFAlpha(final URL input) throws IOException {
423                return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), true);
424        }
425
426        /**
427         * Checks whether the width and height of all the given images match.
428         * 
429         * @param images
430         *            The images to compare sizes.
431         * @return TRUE if all the images are the same size; FALSE otherwise
432         */
433        protected static boolean checkSameSize(final Image<?, ?>... images) {
434                if (images == null || images.length == 0)
435                        return true;
436
437                final Image<?, ?> image = images[0];
438                final int w = image.getWidth();
439                final int h = image.getHeight();
440
441                return ImageUtilities.checkSize(h, w, images);
442        }
443
444        /**
445         * Checks whether the width and height of all the given images match the
446         * given width and height.
447         * 
448         * @param h
449         *            The height to match against all the images
450         * @param w
451         *            The width to match against all the images
452         * @param images
453         *            The images to compare sizes.
454         * @return TRUE if all the images are <code>wxh</code> in size; FALSE
455         *         otherwise
456         */
457        protected static boolean checkSize(final int h, final int w, final Image<?, ?>... images) {
458                for (final Image<?, ?> image : images)
459                        if (image.getHeight() != h || image.getWidth() != w)
460                                return false;
461                return true;
462        }
463
464        /**
465         * Checks whether the width and height of all the given images match the
466         * given width and height.
467         * 
468         * @param h
469         *            The height to match against all the images
470         * @param w
471         *            The width to match against all the images
472         * @param images
473         *            The images to compare sizes.
474         * @return TRUE if all the images are <code>wxh</code> in size; FALSE
475         *         otherwise
476         */
477        protected static boolean checkSize(final int h, final int w, final Iterable<? extends Image<?, ?>> images) {
478                for (final Image<?, ?> image : images)
479                        if (image.getHeight() != h || image.getWidth() != w)
480                                return false;
481                return true;
482        }
483
484        /**
485         * Reads a PNM header from the byte array containing the PNM binary data.
486         * The <code>headerData</code> variable will be populated with the header
487         * information. Returns the number of bytes read from the array.
488         * 
489         * @param data
490         *            The PNM binary data.
491         * @param headerData
492         *            A {@link Map} to populate with header information.
493         * @return The number of bytes read from the array.
494         * @throws IOException
495         *             if the byte array does not contain PNM information.
496         */
497        protected static int pnmReadHeader(final byte[] data, final Map<String, Integer> headerData) throws IOException {
498                final ByteArrayInputStream bais = new ByteArrayInputStream(data);
499                final InputStreamReader isr = new InputStreamReader(bais);
500                final BufferedReader br = new BufferedReader(isr);
501                int count = 0, bytesRead = 0;
502                while (count < 4) {
503                        final String line = br.readLine();
504
505                        final StringTokenizer st = new StringTokenizer(line);
506
507                        while (st.hasMoreTokens()) {
508                                final String tok = st.nextToken();
509
510                                if (tok.startsWith("#"))
511                                        break;
512                                else {
513                                        switch (count) {
514                                        case 0: // magic
515                                                headerData.put("magic", Integer.decode(tok.substring(1)));
516                                                break;
517                                        case 1: // width
518                                                headerData.put("width", Integer.decode(tok));
519                                                break;
520                                        case 2: // height
521                                                headerData.put("height", Integer.decode(tok));
522                                                break;
523                                        case 3: // maxval
524                                                headerData.put("maxval", Integer.decode(tok));
525                                                break;
526                                        }
527                                        count++;
528                                }
529                        }
530                        bytesRead += (line.length() + 1); // +1 for the newlines
531                }
532
533                return bytesRead;
534        }
535
536        /**
537         * Returns the contents of a file in a byte array.
538         * 
539         * @param file
540         *            The file to read
541         * @return A byte array representation of the file.
542         * @throws IOException
543         *             if the file cannot be read fully.
544         */
545        protected static byte[] getBytes(final File file) throws IOException {
546                InputStream is = null;
547                try {
548                        is = new FileInputStream(file);
549
550                        // Get the size of the file
551                        final long length = file.length();
552
553                        // You cannot create an array using a long type.
554                        // It needs to be an int type.
555                        // Before converting to an int type, check
556                        // to ensure that file is not larger than Integer.MAX_VALUE.
557                        if (length > Integer.MAX_VALUE) {
558                                // File is too large
559                        }
560
561                        // Create the byte array to hold the data
562                        final byte[] bytes = new byte[(int) length];
563
564                        // Read in the bytes
565                        int offset = 0;
566                        int numRead = 0;
567                        while (offset < bytes.length
568                                        && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)
569                        {
570                                offset += numRead;
571                        }
572
573                        // Ensure all the bytes have been read in
574                        if (offset < bytes.length) {
575                                throw new IOException("Could not completely read file " + file.getName());
576                        }
577
578                        return bytes;
579                } finally {
580                        // Close the input stream and return bytes
581                        if (is != null)
582                                is.close();
583                }
584        }
585
586        /**
587         * Converts the input stream to a byte array. The input stream is fully
588         * read.
589         * 
590         * @param stream
591         *            The {@link InputStream} to convert to byte array
592         * @return A byte array representation of the {@link InputStream} data.
593         * @throws IOException
594         *             if the input stream cannot be fully read.
595         */
596        protected static byte[] getBytes(final InputStream stream) throws IOException {
597                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
598                final byte[] b = new byte[4096];
599
600                while (stream.read(b) > 0)
601                        baos.write(b);
602
603                return baos.toByteArray();
604        }
605
606        /**
607         * Convert any image to a {@link BufferedImage}.
608         * 
609         * @param img
610         *            image to convert
611         * @return BufferedImage representation
612         */
613        public static BufferedImage createBufferedImage(final Image<?, ?> img) {
614                return ImageUtilities.createBufferedImage(img, null);
615        }
616
617        /**
618         * Convert any image to a {@link BufferedImage}.
619         * 
620         * @param img
621         *            image to convert
622         * @param bimg
623         *            BufferedImage to draw into if possible. Can be null.
624         * @return BufferedImage representation
625         */
626        public static BufferedImage createBufferedImage(final Image<?, ?> img, BufferedImage bimg) {
627                if (bimg == null || bimg.getWidth() != img.getWidth() || bimg.getHeight() != img.getHeight()
628                                || bimg.getType() != BufferedImage.TYPE_INT_ARGB)
629                        bimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
630
631                bimg.setRGB(0, 0, img.getWidth(), img.getHeight(), img.toPackedARGBPixels(), 0, img.getWidth());
632
633                return bimg;
634        }
635
636        /**
637         * Convert any image to a {@link BufferedImage}.
638         * 
639         * @param img
640         *            image to convert
641         * @return BufferedImage representation
642         */
643        public static BufferedImage createBufferedImageForDisplay(final Image<?, ?> img) {
644                if (img instanceof MBFImage)
645                        return ImageUtilities.createBufferedImageForDisplay((MBFImage) img);
646                else if (img instanceof FImage)
647                        return ImageUtilities.createBufferedImage((FImage) img);
648                return ImageUtilities.createBufferedImage(img);
649        }
650
651        /**
652         * Convert any image to a {@link BufferedImage}.
653         * 
654         * @param img
655         *            image to convert
656         * @param bimg
657         *            BufferedImage to draw into if possible. Can be null.
658         * @return BufferedImage representation
659         */
660        public static BufferedImage createBufferedImageForDisplay(final Image<?, ?> img, final BufferedImage bimg) {
661                if (img instanceof MBFImage)
662                        return ImageUtilities.createBufferedImageForDisplay((MBFImage) img, bimg);
663                else if (img instanceof FImage)
664                        return ImageUtilities.createBufferedImage((FImage) img, bimg);
665                return ImageUtilities.createBufferedImage(img, bimg);
666        }
667
668        /**
669         * Efficiently create a TYPE_3BYTE_BGR for display if possible. This is
670         * typically much faster than to create and display than an ARGB buffered
671         * image. If the input image is not in RGB format, then the ARGB form will
672         * be returned instead.
673         * 
674         * @param img
675         *            the image to convert
676         * @return the converted image
677         */
678        public static BufferedImage createBufferedImageForDisplay(final MBFImage img) {
679                return ImageUtilities.createBufferedImageForDisplay(img, null);
680        }
681
682        /**
683         * Efficiently create a TYPE_3BYTE_BGR for display if possible. This is
684         * typically much faster than to create and display than an ARGB buffered
685         * image. If the input image is not in RGB format, then the ARGB form will
686         * be returned instead.
687         * 
688         * @param img
689         *            the image to convert
690         * @param ret
691         *            the image to draw into if possible. Can be null.
692         * @return the converted image. Might not be the same as the ret parameter.
693         */
694        public static BufferedImage createBufferedImageForDisplay(final MBFImage img, BufferedImage ret) {
695                if (img.colourSpace != ColourSpace.RGB)
696                        return ImageUtilities.createBufferedImage(img, ret);
697
698                final int width = img.getWidth();
699                final int height = img.getHeight();
700
701                if (ret == null || ret.getWidth() != width || ret.getHeight() != height
702                                || ret.getType() != BufferedImage.TYPE_3BYTE_BGR)
703                        ret = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
704
705                final WritableRaster raster = ret.getRaster();
706
707                final float[][] r = img.getBand(0).pixels;
708                final float[][] g = img.getBand(1).pixels;
709                final float[][] b = img.getBand(2).pixels;
710
711                final ComponentSampleModel sm = (ComponentSampleModel) raster.getSampleModel();
712                final DataBufferByte db = (DataBufferByte) raster.getDataBuffer();
713                final int scanlineStride = sm.getScanlineStride();
714                final int pixelStride = sm.getPixelStride();
715
716                final byte[] data = db.getData();
717                for (int y = 0; y < height; y++) {
718                        for (int x = 0; x < width; x++) {
719                                data[y * scanlineStride + x * pixelStride + 2] = (byte)
720                                                Math.max(0, Math.min(255, (int) (r[y][x] * 255)));
721                                data[y * scanlineStride + x * pixelStride + 1] = (byte)
722                                                Math.max(0, Math.min(255, (int) (g[y][x] * 255)));
723                                data[y * scanlineStride + x * pixelStride] = (byte)
724                                                Math.max(0, Math.min(255, (int) (b[y][x] * 255)));
725                        }
726                }
727
728                return ret;
729        }
730
731        /**
732         * Efficiently create a TYPE_BYTE_GRAY for display. This is typically much
733         * faster than to create and display than an ARGB buffered image.
734         * 
735         * @param img
736         *            the image to convert
737         * @return the converted image
738         */
739        public static BufferedImage createBufferedImage(final FImage img) {
740                return ImageUtilities.createBufferedImage(img, null);
741        }
742
743        /**
744         * Efficiently create a TYPE_BYTE_GRAY for display. This is typically much
745         * faster than to create and display than an ARGB buffered image.
746         * 
747         * @param img
748         *            the image to convert
749         * @param ret
750         *            BufferedImage to draw into if possible. Can be null.
751         * @return the converted image
752         */
753        public static BufferedImage createBufferedImage(final FImage img, BufferedImage ret) {
754                final int width = img.getWidth();
755                final int height = img.getHeight();
756
757                if (ret == null || ret.getWidth() != width || ret.getHeight() != height
758                                || ret.getType() != BufferedImage.TYPE_BYTE_GRAY)
759                        ret = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
760
761                final WritableRaster raster = ret.getRaster();
762
763                final float[][] p = img.pixels;
764
765                final ComponentSampleModel sm = (ComponentSampleModel) raster.getSampleModel();
766                final DataBufferByte db = (DataBufferByte) raster.getDataBuffer();
767                final int scanlineStride = sm.getScanlineStride();
768                final int pixelStride = sm.getPixelStride();
769
770                final byte[] data = db.getData();
771                for (int y = 0; y < height; y++) {
772                        for (int x = 0; x < width; x++) {
773                                data[y * scanlineStride + x * pixelStride] = (byte) (Math.max(0, Math.min(255, (int) (p[y][x] * 255))));
774                        }
775                }
776
777                return ret;
778        }
779
780        /**
781         * Write an image to a {@link DataOutput}.
782         * 
783         * @param img
784         *            the image
785         * @param formatName
786         *            the format
787         * @param out
788         *            the output
789         * @throws IOException
790         *             if an error occurs
791         */
792        public static void write(final Image<?, ?> img, final String formatName, final DataOutput out) throws IOException {
793                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
794                ImageUtilities.write(img, formatName, baos);
795                out.writeInt(baos.size());
796                out.write(baos.toByteArray());
797        }
798
799        /**
800         * Read an {@link FImage} from a DataInput
801         * 
802         * @param in
803         *            input
804         * @return new FImage
805         * @throws IOException
806         *             if error occurs
807         */
808        public static FImage readF(final DataInput in) throws IOException {
809                final int sz = in.readInt();
810                final byte[] bytes = new byte[sz];
811                in.readFully(bytes);
812
813                return ImageUtilities.readF(new ByteArrayInputStream(bytes));
814        }
815
816        /**
817         * Assign the contents of a {@link BufferedImage} to an {@link Image}.
818         * 
819         * @param <I>
820         *            the type of {@link Image}
821         * @param img
822         *            the {@link BufferedImage} to copy
823         * @param oiImage
824         *            the {@link Image} to fill
825         * @return the given input image.
826         */
827        public static <I extends Image<?, I>> I assignBufferedImage(final BufferedImage img, final I oiImage) {
828                final BufferedImage bimg = ImageUtilities.createWorkingImage(img);
829                final int[] data = bimg.getRGB(0, 0, bimg.getWidth(), bimg.getHeight(), null, 0, bimg.getWidth());
830
831                return oiImage.internalAssign(data, bimg.getWidth(), bimg.getHeight());
832        }
833
834        /**
835         * Alpha composites the pixel p1 with the pixel p2, returning the value in
836         * pixel p1
837         * 
838         * @param p1
839         *            The first pixel
840         * @param p2
841         *            The second pixel
842         * @return The updates first pixel p1
843         */
844        public static float[] alphaCompositePixel(final float[] p1, final float[] p2)
845        {
846                final float thisR = p1[0];
847                final float thisG = p1[1];
848                final float thisB = p1[2];
849                float thisA = 1f;
850                if (p1.length == 4)
851                        thisA = p1[3];
852                float thatA = 1f;
853                if (p2.length == 4)
854                        thatA = p2[3];
855                final float thatR = p2[0];
856                final float thatG = p2[1];
857                final float thatB = p2[2];
858
859                float a = thatA + thisA * (1 - thatA);
860                a = a > 1.0f ? 1.0f : a;
861                float r = thatR * thatA + (thisR * thisA) * (1 - thatA);
862                r = r > 1.0f ? 1.0f : r;
863                float g = thatG * thatA + (thisG * thisA) * (1 - thatA);
864                g = g > 1.0f ? 1.0f : g;
865                float b = thatB * thatA + (thisB * thisA) * (1 - thatA);
866                b = b > 1.0f ? 1.0f : b;
867
868                p1[0] = r;
869                p1[1] = g;
870                p1[2] = b;
871                if (p1.length == 4)
872                        p1[3] = a;
873
874                return p1;
875        }
876
877        /**
878         * @param out
879         *            where the write the composition
880         * @param thisR
881         * @param thisG
882         * @param thisB
883         * @param thisA
884         * 
885         * @param thatR
886         * @param thatG
887         * @param thatB
888         * @param thatA
889         * 
890         * @return returns out
891         */
892        public static float[] alphaCompositePixel(
893                        float[] out,
894                        float thisR, float thisG, float thisB, float thisA,
895                        float thatR, float thatG, float thatB, float thatA
896                        )
897        {
898
899                float a = thatA + thisA * (1 - thatA);
900                a = a > 1.0f ? 1.0f : a;
901                float r = thatR * thatA + (thisR * thisA) * (1 - thatA);
902                r = r > 1.0f ? 1.0f : r;
903                float g = thatG * thatA + (thisG * thisA) * (1 - thatA);
904                g = g > 1.0f ? 1.0f : g;
905                float b = thatB * thatA + (thisB * thisA) * (1 - thatA);
906                b = b > 1.0f ? 1.0f : b;
907
908                out[0] = r;
909                out[1] = g;
910                out[2] = b;
911                out[3] = a;
912
913                return out;
914        }
915
916        /**
917         * Alpha composites the pixel p1 with the pixel p2, returning the value in
918         * pixel p1
919         * 
920         * @param p1
921         *            The first pixel
922         * @param p2
923         *            The second pixel
924         * @return The updates first pixel p1
925         */
926        public static float[] alphaCompositePixel(final Float[] p1, final Float[] p2)
927        {
928                final float[] p1p = new float[p1.length];
929                for (int b = 0; b < p1.length; b++)
930                        p1p[b] = p1[b];
931                final float[] p2p = new float[p2.length];
932                for (int b = 0; b < p2.length; b++)
933                        p2p[b] = p2[b];
934                return ImageUtilities.alphaCompositePixel(p1p, p2p);
935        }
936
937        /**
938         * Test if a given image output format name is supported
939         * 
940         * @param fmt
941         *            the format name
942         * @return true if supported; false otherwise
943         */
944        public static boolean isWriteFormatSupported(String fmt) {
945                return ArrayUtils.contains(ImageIO.getWriterFormatNames(), fmt);
946        }
947
948        /**
949         * Test if the image output format suggested by the extension of the given
950         * filename is supported
951         * 
952         * @param file
953         *            the file
954         * @return true if supported; false otherwise
955         */
956        public static boolean isWriteFormatSupported(File file) {
957                final String name = file.getName();
958                String format = name.substring(name.lastIndexOf(".") + 1);
959
960                format = format.toLowerCase().trim();
961
962                return ArrayUtils.contains(ImageIO.getWriterFormatNames(), format);
963        }
964}