001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 * image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause).
006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved.
007 * Visit https://imagingbook.com for additional details.
008 ******************************************************************************/
009package imagingbook.core.resource;
010
011import ij.IJ;
012import ij.ImagePlus;
013
014import java.awt.Image;
015import java.util.Arrays;
016import java.util.HashMap;
017import java.util.HashSet;
018
019/**
020 * <p>
021 * Interface to be implemented by named image resources. This indicates (for testing) that the associated resource can
022 * be opened as an image (by ImageJ). Extends interface {@link NamedResource} by adding method {@link #getImagePlus()},
023 * which returns an {@link ImagePlus} instance. By default, image files are assumed to reside in a directory at the same
024 * level and with exactly the same name as the defining enum class itself. E.g., in a standard Maven-setup this is:
025 * </p>
026 * <pre>
027 * .../java/    .../bar/MyImageResource.java          = enum class implementing 'ImageResource'
028 * .../resource/.../bar/MyImageResource/image1.png    = associated image files
029 * .../resource/.../bar/MyImageResource/image2.tif
030 * .../resource/.../bar/MyImageResource/...</pre>
031 * <p>
032 * For example, given a named resource {@code MyImages.image1}, this can be used simply in the form
033 * </p>
034 * <pre>
035 * ImagePlus im = MyImages.image1.getImage();
036 * im.show();</pre>
037 * <p>
038 * By default, resource file names are derived automatically from the enum item's name (by method {@link #autoName()}).
039 * If some other behavior is needed, method {@link #getFileName()} should be overridden.
040 *
041 * @author WB
042 */
043public interface ImageResource extends NamedResource {
044
045        /**
046         * Opens end returns a {@link ImagePlus} instance for this {@link ImageResource}.
047         *
048         * @return a {@link ImagePlus} instance
049         */
050        public default ImagePlus getImagePlus() {
051                // TODO: opening GIF image stacks does not work, returns only a single image
052                return IJ.openImage(getURL().toString());
053        }
054        
055        @Override
056        public default String getFileName() {
057                return this.autoName();
058        }
059        
060        /**
061         * The set of image file extensions supported in {@link #autoName()}.
062         */
063        static final HashSet<String> ValidImageExtensions = 
064                        new HashSet<>(Arrays.asList("png", "tif", "tiff", "jpg", "jpeg", "gif"));
065
066        /**
067         * <p>
068         * Derives and returns a filename for this resource item to be used in parameterless enum constructors. By default
069         * the file name is identical to the name of the enum constant supplemented with a ".png" extension. No separate
070         * file name needs to be supplied. A different file extension may be specified by having the enum name end with an
071         * underscore followed by a valid image file extension, that is, "png", "tif", "tiff", "jpg", "jpeg" or "gif". In
072         * this case, the last underscore of the enum name is replaced by a '.' character to form the file name. (Note that
073         * '.' is no legal character in a Java identifier, thus cannot be used for the enum name directly.) If the last
074         * underscore in a item's name is not followed by a valid extension, the default case is assumed ("png").
075         * </p>
076         * <p>
077         * Examples:
078         * </p>
079         * <pre>
080         * enum DummyNamedResource implements ImageResource {
081         *      a,                  // file "a.png"
082         *      A_png,              // file "A.png"
083         *      foo_tif,            // file "foo.tif"
084         *      foo_tiff,           // file "foo.tiff"
085         *      The_File_jpg,       // file "The_File.jpg"
086         *      The_File_jpeg,      // file "The_File.jpeg"
087         *      _Some____File_bla;  // file "_Some____File_bla.png"
088         * }</pre>
089         *
090         * @return the image filename derived from the enum item's name
091         */
092        public default String autoName() {
093                String itemname = this.toString();
094                int k = itemname.lastIndexOf('_');
095                if (k >= 0) {
096                        String filename = itemname.substring(0, k);
097                        String extension  = itemname.substring(k + 1);
098                        if (ValidImageExtensions.contains(extension)) {
099                                return filename + "." + extension;
100                        }
101                }
102                return itemname + ".png";
103        }
104
105        /**
106         * Returns the names of the actual files contained in the associated resource directory of the specified class,
107         * which must implement the {@link ImageResource} interface. This can be used to check if a given named resource has
108         * a matching file in a case-sensitive way.
109         *
110         * @param clazz the resource class
111         * @return an array of strings
112         */
113        public static String[] getResourceFileNames(Class<? extends ImageResource> clazz) {
114                // return ResourceUtils.getResourceFileNames(clazz, clazz.getSimpleName() + RelativeDirectorySuffix);
115                return ResourceUtils.getResourceFileNames(clazz, NamedResource.getRelativeDirectory(clazz));
116        }
117
118        // ---------------- icon handling --------------------------------------
119
120        /**
121         * The default icon size (maximum width or height).
122         */
123        static final int DefaultIconSize = 128;
124        static final HashMap<String, ImagePlus> IconMap = new HashMap<>();
125
126        /**
127         * Returns an icon for this {@link ImageResource} with the default icon size. All icons are cached, i.e., are only
128         * created once when first requested for a particular size.
129         *
130         * @return an {@link ImagePlus} instance containing the icon image
131         * @see #DefaultIconSize
132         * @see #getImageIcon(int)
133         */
134        public default ImagePlus getImageIcon() {
135                return getImageIcon(DefaultIconSize);
136        }
137
138        /**
139         * Returns an icon for this {@link ImageResource} with the specified size. All icons are cached, i.e., are only
140         * created once when first requested for a particular size.
141         *
142         * @param iconSize
143         * @return an {@link ImagePlus} instance containing the icon image
144         * @see #getImageIcon()
145         */
146        public default ImagePlus getImageIcon(int iconSize) {
147                String key =  this.toString() + iconSize + this.getClass().getCanonicalName();
148                ImagePlus hashedImp = IconMap.get(key);
149                if (hashedImp != null) {
150                        return hashedImp;
151                }
152                else {
153                        Image im = this.getImagePlus().getImage();      // an AWT image!
154                        int w = im.getWidth(null);
155                        int h = im.getHeight(null);
156                        int hints = Image.SCALE_SMOOTH;
157                        Image imScaled = (w >= h) ?
158                                        im.getScaledInstance(iconSize, -1, hints) :
159                                        im.getScaledInstance(-1, iconSize, hints);
160                        ImagePlus icon = new ImagePlus(null, imScaled);
161                        IconMap.put(key, icon);
162                        return icon;
163                }
164        }
165
166}