001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.image;
020
021import icy.gui.util.FontUtil;
022import icy.network.URLUtil;
023import icy.system.thread.ThreadUtil;
024import icy.util.GraphicsUtil;
025
026import java.awt.AlphaComposite;
027import java.awt.Color;
028import java.awt.Font;
029import java.awt.Graphics;
030import java.awt.Graphics2D;
031import java.awt.Image;
032import java.awt.Rectangle;
033import java.awt.RenderingHints;
034import java.awt.Toolkit;
035import java.awt.Transparency;
036import java.awt.geom.Rectangle2D;
037import java.awt.image.BufferedImage;
038import java.awt.image.DataBufferByte;
039import java.awt.image.IndexColorModel;
040import java.awt.image.Raster;
041import java.awt.image.RenderedImage;
042import java.awt.image.WritableRaster;
043import java.io.File;
044import java.io.FileOutputStream;
045import java.io.IOException;
046import java.io.InputStream;
047import java.net.URL;
048import java.util.ArrayList;
049import java.util.List;
050
051import javax.imageio.ImageIO;
052
053/**
054 * Image utilities class.
055 * 
056 * @author stephane
057 */
058public class ImageUtil
059{
060    public static String getImageTypeString(int type)
061    {
062        switch (type)
063        {
064            case BufferedImage.TYPE_CUSTOM:
065                return "TYPE_CUSTOM";
066            case BufferedImage.TYPE_INT_RGB:
067                return "TYPE_INT_RGB";
068            case BufferedImage.TYPE_INT_ARGB:
069                return "TYPE_INT_ARGB";
070            case BufferedImage.TYPE_INT_ARGB_PRE:
071                return "TYPE_INT_ARGB_PRE";
072            case BufferedImage.TYPE_INT_BGR:
073                return "TYPE_INT_BGR";
074            case BufferedImage.TYPE_3BYTE_BGR:
075                return "TYPE_3BYTE_BGR";
076            case BufferedImage.TYPE_4BYTE_ABGR:
077                return "TYPE_4BYTE_ABGR";
078            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
079                return "TYPE_4BYTE_ABGR_PRE";
080            case BufferedImage.TYPE_USHORT_565_RGB:
081                return "TYPE_USHORT_565_RGB";
082            case BufferedImage.TYPE_USHORT_555_RGB:
083                return "TYPE_USHORT_555_RGB";
084            case BufferedImage.TYPE_BYTE_GRAY:
085                return "TYPE_BYTE_GRAY";
086            case BufferedImage.TYPE_USHORT_GRAY:
087                return "TYPE_USHORT_GRAY";
088            case BufferedImage.TYPE_BYTE_BINARY:
089                return "TYPE_BYTE_BINARY";
090            case BufferedImage.TYPE_BYTE_INDEXED:
091                return "TYPE_BYTE_INDEXED";
092            default:
093                return "UNKNOWN TYPE";
094        }
095    }
096
097    public static String getTransparencyString(int transparency)
098    {
099        switch (transparency)
100        {
101            case Transparency.OPAQUE:
102                return "OPAQUE";
103            case Transparency.BITMASK:
104                return "BITMASK";
105            case Transparency.TRANSLUCENT:
106                return "TRANSLUCENT";
107            default:
108                return "UNKNOWN TRANSPARENCY";
109        }
110    }
111
112    /**
113     * Wait for dimension information of specified image being loaded.
114     * 
115     * @param image
116     *        image we are waiting informations for.
117     */
118    public static void waitImageReady(Image image)
119    {
120        if (image != null)
121        {
122            final long st = System.currentTimeMillis();
123
124            // wait 2 seconds max
125            while ((image.getWidth(null) == -1) && ((System.currentTimeMillis() - st) < 2000))
126                ThreadUtil.sleep(1);
127        }
128    }
129
130    /**
131     * Create a 8 bits indexed buffered image from specified <code>IndexColorModel</code><br>
132     * and byte array data.
133     */
134    public static BufferedImage createIndexedImage(int w, int h, IndexColorModel cm, byte[] data)
135    {
136        final WritableRaster raster = Raster.createInterleavedRaster(new DataBufferByte(data, w * h, 0), w, h, w, 1,
137                new int[] {0}, null);
138
139        return new BufferedImage(cm, raster, false, null);
140    }
141
142    /**
143     * Load an image from specified path
144     */
145    public static BufferedImage load(String path, boolean displayError)
146    {
147        return load(URLUtil.getURL(path), displayError);
148    }
149
150    /**
151     * Load an image from specified path
152     */
153    public static BufferedImage load(String path)
154    {
155        return load(path, true);
156    }
157
158    /**
159     * Load an image from specified url
160     */
161    public static BufferedImage load(URL url, boolean displayError)
162    {
163        if (url != null)
164        {
165            try
166            {
167                return ImageIO.read(url);
168            }
169            catch (IOException e)
170            {
171                if (displayError)
172                    System.err.println("Can't load image from " + url);
173            }
174        }
175
176        return null;
177    }
178
179    /**
180     * Asynchronously load an image from specified url.<br/>
181     * Use {@link #waitImageReady(Image)} to know if width and height property
182     */
183    public static Image loadAsync(URL url)
184    {
185        return Toolkit.getDefaultToolkit().createImage(url);
186    }
187
188    /**
189     * Asynchronously load an image from specified path.<br/>
190     * Use {@link #waitImageReady(Image)} to know if width and height property
191     */
192    public static Image loadAsync(String path)
193    {
194        return Toolkit.getDefaultToolkit().createImage(path);
195    }
196
197    /**
198     * Load an image from specified url
199     */
200    public static BufferedImage load(URL url)
201    {
202        return load(url, true);
203    }
204
205    /**
206     * Load an image from specified file
207     */
208    public static BufferedImage load(File file, boolean displayError)
209    {
210        if (file != null)
211        {
212            try
213            {
214                return ImageIO.read(file);
215            }
216            catch (IOException e)
217            {
218                if (displayError)
219                    System.err.println("Can't load image from " + file);
220            }
221        }
222
223        return null;
224    }
225
226    /**
227     * Load an image from specified file
228     */
229    public static BufferedImage load(File file)
230    {
231        return loadImage(file, true);
232    }
233
234    /**
235     * Load an image from specified InputStream
236     */
237    public static BufferedImage load(InputStream input, boolean displayError)
238    {
239        if (input != null)
240        {
241            try
242            {
243                return ImageIO.read(input);
244            }
245            catch (Exception e)
246            {
247                if (displayError)
248                    System.err.println("Can't load image from stream " + input);
249            }
250        }
251
252        return null;
253    }
254
255    /**
256     * Load an image from specified InputStream
257     */
258    public static BufferedImage load(InputStream input)
259    {
260        return load(input, true);
261    }
262
263    /**
264     * @deprecated Use {@link ImageUtil#load(String, boolean)} instead
265     */
266    @Deprecated
267    public static BufferedImage loadImage(String path, boolean displayError)
268    {
269        return load(path, displayError);
270    }
271
272    /**
273     * @deprecated Use {@link ImageUtil#load(String)} instead
274     */
275    @Deprecated
276    public static BufferedImage loadImage(String path)
277    {
278        return load(path);
279    }
280
281    /**
282     * @deprecated Use {@link ImageUtil#load(URL, boolean)} instead
283     */
284    @Deprecated
285    public static BufferedImage loadImage(URL url, boolean displayError)
286    {
287        return load(url, displayError);
288    }
289
290    /**
291     * @deprecated Use {@link ImageUtil#load(URL)} instead
292     */
293    @Deprecated
294    public static Image loadImage(URL url)
295    {
296        return load(url);
297    }
298
299    /**
300     * @deprecated Use {@link ImageUtil#load(File, boolean)} instead
301     */
302    @Deprecated
303    public static BufferedImage loadImage(File file, boolean displayError)
304    {
305        return load(file, displayError);
306    }
307
308    /**
309     * @deprecated Use {@link ImageUtil#load(File)} instead
310     */
311    @Deprecated
312    public static BufferedImage loadImage(File file)
313    {
314        return load(file);
315    }
316
317    /**
318     * @deprecated Use {@link ImageUtil#load(InputStream, boolean)} instead
319     */
320    @Deprecated
321    public static BufferedImage loadImage(InputStream input, boolean displayError)
322    {
323        return load(input, displayError);
324
325    }
326
327    /**
328     * @deprecated Use {@link ImageUtil#load(InputStream)} instead
329     */
330    @Deprecated
331    public static BufferedImage loadImage(InputStream input)
332    {
333        return load(input);
334
335    }
336
337    /**
338     * Save an image to specified path in specified format
339     */
340    public static boolean save(RenderedImage image, String format, String path)
341    {
342        if (path != null)
343        {
344            try
345            {
346                return ImageIO.write(image, format, new FileOutputStream(path));
347            }
348            catch (IOException e)
349            {
350                System.err.println("Can't save image to " + path);
351            }
352        }
353
354        return false;
355    }
356
357    /**
358     * Save an image to specified file in specified format
359     */
360    public static boolean save(RenderedImage image, String format, File file)
361    {
362        if (file != null)
363        {
364            try
365            {
366                return ImageIO.write(image, format, file);
367            }
368            catch (IOException e)
369            {
370                System.err.println("Can't save image to " + file);
371            }
372        }
373
374        return false;
375    }
376
377    /**
378     * @deprecated Use {@link ImageUtil#save(RenderedImage, String, String)} instead
379     */
380    @Deprecated
381    public static boolean saveImage(RenderedImage image, String format, String path)
382    {
383        return save(image, format, path);
384    }
385
386    /**
387     * @deprecated Use {@link ImageUtil#save(RenderedImage, String, File)} instead
388     */
389    @Deprecated
390    public static boolean saveImage(RenderedImage image, String format, File file)
391    {
392        return save(image, format, file);
393    }
394
395    /**
396     * Return a RenderedImage from the given Image object.
397     */
398    public static RenderedImage toRenderedImage(Image image)
399    {
400        return toBufferedImage(image);
401    }
402
403    /**
404     * Return a ARGB BufferedImage from the given Image object.
405     * If the image is already a BufferedImage image then it's directly returned
406     */
407    public static BufferedImage toBufferedImage(Image image)
408    {
409        if (image instanceof BufferedImage)
410            return (BufferedImage) image;
411
412        // be sure image data are ready
413        waitImageReady(image);
414        final BufferedImage bufImage = new BufferedImage(image.getWidth(null), image.getHeight(null),
415                BufferedImage.TYPE_INT_ARGB);
416
417        final Graphics2D g = bufImage.createGraphics();
418        g.drawImage(image, 0, 0, null);
419        g.dispose();
420
421        return bufImage;
422    }
423
424    /**
425     * Scale an image with specified size.
426     */
427    public static BufferedImage scale(Image image, int width, int height)
428    {
429        if (image != null)
430        {
431            final BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
432            final Graphics2D g = result.createGraphics();
433
434            g.setComposite(AlphaComposite.Src);
435            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
436            g.drawImage(image, 0, 0, width, height, null);
437            g.dispose();
438
439            return result;
440        }
441
442        return null;
443    }
444
445    /**
446     * Scale an image with specified size (try to keep best quality).
447     */
448    public static BufferedImage scaleQuality(Image image, int width, int height)
449    {
450        if (image != null)
451        {
452            Image current = image;
453
454            // be sure image data are ready
455            waitImageReady(image);
456
457            int w = image.getWidth(null);
458            int h = image.getHeight(null);
459
460            do
461            {
462                if (w > width)
463                {
464                    w /= 2;
465                    if (w < width)
466                        w = width;
467                }
468                else
469                    w = width;
470
471                if (h > height)
472                {
473                    h /= 2;
474                    if (h < height)
475                        h = height;
476                }
477                else
478                    h = height;
479
480                final BufferedImage result = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
481                final Graphics2D g = result.createGraphics();
482
483                g.setComposite(AlphaComposite.Src);
484                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
485                g.drawImage(current, 0, 0, w, h, null);
486
487                g.dispose();
488
489                current = result;
490            }
491            while (w != width || h != height);
492
493            return (BufferedImage) current;
494        }
495
496        return null;
497    }
498
499    /**
500     * Convert an image to a BufferedImage.<br>
501     * If <code>out</out> is null, by default a <code>BufferedImage.TYPE_INT_ARGB</code> is created.
502     */
503    public static BufferedImage convert(Image in, BufferedImage out)
504    {
505        final BufferedImage result;
506
507        // be sure image data are ready
508        waitImageReady(in);
509
510        // no output type specified ? use ARGB
511        if (out == null)
512            result = new BufferedImage(in.getWidth(null), in.getHeight(null), BufferedImage.TYPE_INT_ARGB);
513        else
514            result = out;
515
516        final Graphics g = result.getGraphics();
517        g.drawImage(in, 0, 0, null);
518        g.dispose();
519
520        return result;
521    }
522
523    /**
524     * Returns <code>true</code> if the specified image is a grayscale image whatever is the image
525     * type (GRAY, RGB, ARGB...)
526     */
527    public static boolean isGray(BufferedImage image)
528    {
529        if (image == null)
530            return false;
531
532        if (image.getType() == BufferedImage.TYPE_BYTE_GRAY)
533            return true;
534        if (image.getType() == BufferedImage.TYPE_USHORT_GRAY)
535            return true;
536
537        final int[] rgbArray = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
538
539        for (int value : rgbArray)
540        {
541            final int c0 = (value >> 0) & 0xFF;
542            final int c1 = (value >> 8) & 0xFF;
543            if (c0 != c1)
544                return false;
545
546            final int c2 = (value >> 16) & 0xFF;
547            if (c0 != c2)
548                return false;
549        }
550
551        return true;
552    }
553
554    /**
555     * Convert an image to grey image (<code>BufferedImage.TYPE_BYTE_GRAY</code>).
556     */
557    public static BufferedImage toGray(Image image)
558    {
559        if (image != null)
560        {
561            // be sure image data are ready
562            waitImageReady(image);
563            return convert(image,
564                    new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_BYTE_GRAY));
565        }
566
567        return null;
568    }
569
570    /**
571     * Convert an image to RGB image (<code>BufferedImage.TYPE_INT_RGB</code>).
572     */
573    public static BufferedImage toRGBImage(Image image)
574    {
575        if (image != null)
576        {
577            // be sure image data are ready
578            waitImageReady(image);
579            return convert(image,
580                    new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB));
581        }
582
583        return null;
584    }
585
586    /**
587     * Convert an image to ARGB image (<code>BufferedImage.TYPE_INT_ARGB</code>).
588     */
589    public static BufferedImage toARGBImage(Image image)
590    {
591        if (image != null)
592        {
593            // be sure image data are ready
594            waitImageReady(image);
595            return convert(image,
596                    new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB));
597        }
598
599        return null;
600    }
601
602    /**
603     * @deprecated Use {@link ImageUtil#scale(Image, int, int)} instead.
604     */
605    @Deprecated
606    public static BufferedImage scaleImage(Image image, int width, int height)
607    {
608        return scale(image, width, height);
609    }
610
611    /**
612     * @deprecated Use {@link ImageUtil#scaleQuality(Image, int, int)} instead.
613     */
614    @Deprecated
615    public static BufferedImage scaleImageQuality(Image image, int width, int height)
616    {
617        return scaleQuality(image, width, height);
618    }
619
620    /**
621     * @deprecated Use {@link ImageUtil#convert(Image, BufferedImage)} instead.
622     */
623    @Deprecated
624    public static BufferedImage convertImage(Image in, BufferedImage out)
625    {
626        return convert(in, out);
627    }
628
629    /**
630     * @deprecated Use {@link ImageUtil#toGray(Image)} instead.
631     */
632    @Deprecated
633    public static BufferedImage toGrayImage(Image image)
634    {
635        return toGray(image);
636    }
637
638    /**
639     * Create a copy of the input image.<br>
640     * Result is always a <code>BufferedImage.TYPE_INT_ARGB</code> type image.
641     */
642    public static BufferedImage getCopy(Image in)
643    {
644        return convert(in, null);
645    }
646
647    /**
648     * Return true if image has the same size
649     */
650    public static boolean sameSize(BufferedImage im1, BufferedImage im2)
651    {
652        return (im1.getWidth() == im2.getWidth()) && (im1.getHeight() == im2.getHeight());
653    }
654
655    /**
656     * Get the list of tiles to cover the given XY region.<br>
657     * Note that the resulting tiles surface may be larger than input region as we enforce using specified tile size / position to cover the whole region.
658     * 
659     * @param region
660     *        the XY region to cover
661     * @param tileW
662     *        tile width
663     * @param tileH
664     *        tile height
665     */
666    public static List<Rectangle> getTileList(Rectangle region, int tileW, int tileH)
667    {
668        final List<Rectangle> result = new ArrayList<Rectangle>();
669
670        if ((tileW <= 0) || (tileH <= 0) || region.isEmpty())
671            return result;
672
673        int startX, startY;
674        int endX, endY;
675
676        startX = (region.x / tileW) * tileW;
677        startY = (region.y / tileH) * tileH;
678        endX = ((region.x + (region.width - 1)) / tileW) * tileW;
679        endY = ((region.y + (region.height - 1)) / tileH) * tileH;
680
681        for (int y = startY; y <= endY; y += tileH)
682            for (int x = startX; x <= endX; x += tileW)
683                result.add(new Rectangle(x, y, tileW, tileH));
684
685        return result;
686    }
687
688    /**
689     * Get the list of tiles to fill the given XY plan size.<br>
690     * Note that the resulting tiles surface may be larger than input region as we enforce using specified tile size / position to cover the whole region.
691     * 
692     * @param sizeX
693     *        plan sizeX
694     * @param sizeY
695     *        plan sizeY
696     * @param tileW
697     *        tile width
698     * @param tileH
699     *        tile height
700     */
701    public static List<Rectangle> getTileList(int sizeX, int sizeY, int tileW, int tileH)
702    {
703        return getTileList(new Rectangle(0, 0, sizeX, sizeY), tileW, tileH);
704    }
705
706    /**
707     * Apply simple color filter with specified alpha factor to the image
708     */
709    public static void applyColorFilter(Image image, Color color, float alpha)
710    {
711        if (image != null)
712        {
713            // be sure image data are ready
714            waitImageReady(image);
715
716            // should be Graphics2D compatible
717            final Graphics2D g = (Graphics2D) image.getGraphics();
718            final Rectangle rect = new Rectangle(image.getWidth(null), image.getHeight(null));
719
720            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
721            g.setColor(color);
722            g.fill(rect);
723            g.dispose();
724        }
725    }
726
727    /**
728     * Return an image which contains specified color depending original alpha intensity image
729     */
730    public static Image getColorImageFromAlphaImage(Image alphaImage, Color color)
731    {
732        return paintColorImageFromAlphaImage(alphaImage, null, color);
733    }
734
735    /**
736     * Paint the specified color in 'out' image depending original alpha intensity from 'alphaImage'
737     */
738    public static Image paintColorImageFromAlphaImage(Image alphaImage, Image out, Color color)
739    {
740        final int w;
741        final int h;
742        final Image result;
743
744        if (out == null)
745        {
746            // be sure image data are ready
747            waitImageReady(alphaImage);
748
749            w = alphaImage.getWidth(null);
750            h = alphaImage.getHeight(null);
751
752            if ((w == -1) || (h == -1))
753                return null;
754
755            result = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
756        }
757        else
758        {
759            // be sure image data are ready
760            waitImageReady(out);
761
762            w = out.getWidth(null);
763            h = out.getHeight(null);
764
765            if ((w == -1) || (h == -1))
766                return null;
767
768            result = out;
769        }
770
771        final Graphics2D g = (Graphics2D) result.getGraphics();
772
773        // clear
774        g.setBackground(new Color(0x00000000, true));
775        g.clearRect(0, 0, w, h);
776
777        // be sure image data are ready
778        waitImageReady(alphaImage);
779        // draw icon
780        g.drawImage(alphaImage, 0, 0, null);
781
782        // set fill color
783        g.setComposite(AlphaComposite.SrcAtop);
784        g.setColor(color);
785        g.fillRect(0, 0, w, h);
786
787        g.dispose();
788
789        return result;
790    }
791
792    /**
793     * Draw text in the specified image with specified parameters.<br>
794     */
795    public static void drawText(Image image, String text, float x, float y, int size, Color color)
796    {
797        final Graphics2D g = (Graphics2D) image.getGraphics();
798
799        // prepare setting
800        g.setColor(color);
801        g.setFont(FontUtil.setSize(g.getFont(), size));
802        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
803
804        // draw icon
805        g.drawString(text, x, y);
806
807        g.dispose();
808    }
809
810    /**
811     * Draw text at top right in the specified image with specified parameters.<br>
812     */
813    public static void drawTextTopRight(Image image, String text, int size, boolean bold, Color color)
814    {
815        final Graphics2D g = (Graphics2D) image.getGraphics();
816
817        // prepare setting
818        g.setColor(color);
819        g.setFont(FontUtil.setSize(g.getFont(), size));
820        if (bold)
821            g.setFont(FontUtil.setStyle(g.getFont(), Font.BOLD));
822        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
823
824        // get string bounds
825        final Rectangle2D bounds = GraphicsUtil.getStringBounds(g, text);
826
827        // be sure image data are ready
828        waitImageReady(image);
829
830        final float w = image.getWidth(null);
831
832        // draw text
833        g.drawString(text, w - ((float) bounds.getWidth()), 0 - (float) bounds.getY());
834
835        g.dispose();
836    }
837}