001package Torello.HTML.Tools.Images;
002
003import java.util.regex.*;
004import java.io.*;
005
006import java.awt.image.BufferedImage;
007import javax.imageio.ImageIO;
008import java.util.Base64;
009import java.net.URL;
010
011import Torello.Java.Additional.Ret2;
012
013/**
014 * An enumeration of the primary image-types available on the internet.
015 * 
016 * <BR /><BR />This is just an enumerated-type used to ensure proper parameter-requests when
017 * downloading images.  The type provides a simple means for storing words such as <code>'jpg,'
018 * 'png,' 'gif,' etc...</code> when attempting to download images.
019 *
020 * @see ImageScrape
021 * @see ImageScraper
022 */
023public enum IF
024{
025    // ********************************************************************************************
026    // ********************************************************************************************
027    // The Constants
028    // ********************************************************************************************
029    // ********************************************************************************************
030
031
032    /**
033     * Used to indicate a picture using the common {@code '.jpg'} image format.
034     * According to a <B>Yahoo! Search</B> link:
035     * 
036     * <BR /><BR /><CODE>The JPEG file extension is used interchangeably
037     * with JPG. JPEG stands for Joint Photographic Experts Group who created the standard. 
038     * JPG files have 2 sub-formats, JPG/ Exif (often used in digital cameras and photographic 
039     * equipment), and JPG/ JFIF (often used on the World Wide Web).</CODE>
040     * 
041     * <BR /><BR />What is JPG? What Opens a JPG? Exact Link:
042     * 
043     * <BR /><BR /><A HREF="http://whatis.techtarget.com/fileformat/JPG-JPEG-bitmap" TARGET=_blank>
044     * http://whatis.techtarget.com/fileformat/JPG-JPEG-bitmap</A>
045     */
046    JPG("jpg", "jpeg"),
047
048    /** 
049     * Used to indicate a picture using the common ommon {@code '.gif'} image format.
050     * Short for <CODE><B>"Graphics Interchange Format".</B></CODE>
051     */
052    GIF("gif"),
053
054    /**
055     * Used to indicate a picture using the common ommon {@code '.bmp'} image format.
056     * Abbreviation of the word <CODE><B>'Bit Map'</B></CODE>
057     */
058    BMP("bmp"),
059
060    /**
061     * Used to indicate a picture using the common {@code '.png'} image format.
062     * <CODE><B>PNG</B></CODE> stands for <CODE><B>Portable Network Graphics.</B></CODE>
063     * It is an open source file extension for raster graphics files. 
064     */
065    PNG("png");
066
067
068    // ********************************************************************************************
069    // ********************************************************************************************
070    // Fields
071    // ********************************************************************************************
072    // ********************************************************************************************
073
074
075    /** This is the actual file-name extension saved as a {@code String}.  */
076    public final String extension;
077
078    /**
079     * This field is always just null, except for the case of the {@code 'JPG'} Enumeration
080     * Constant.  For that Image-Format this simply evaluates to the {@code String 'jpeg'}.
081     */
082    public final String alternateExtension;
083
084    /**
085     * This will parse a {@code 'Base64' String} into two groups using Java's RegEx Tools.
086     *
087     * <BR /><DIV CLASS=EXAMPLE>{@code
088     * import java.util.regex.Matcher;
089     * ...
090     * 
091     * Matcher m = IF.B64_INIT_STRING.matcher(base64String);
092     * if (m.find())
093     * { ... }
094     * }</DIV>
095     * 
096     * <BR /><BR /><OL CLASS=JDOL>
097     * 
098     * <LI> {@code m.group(1) => } Image Encoding Type-{@code String} ({@code "gif", "jpg",} etc..)
099     *      <BR /><BR />
100     *      </LI>
101     * 
102     * <LI>{@code m.gropu(2) => } Base 64 Encoded Image-{@code String}
103     *      <BR />
104     *      </LI>
105     * </OL>
106     */
107    public static final Pattern B64_INIT_STRING = Pattern.compile(
108        "^\\s*data:\\s*image\\/\\s*([A-Za-z]{3,4})\\s*;\\s*base64\\s*,(.*)$",
109        Pattern.CASE_INSENSITIVE
110    );
111
112    private static final IF[] arr = { JPG, GIF, BMP, PNG };
113
114
115    // ********************************************************************************************
116    // ********************************************************************************************
117    // Constructors
118    // ********************************************************************************************
119    // ********************************************************************************************
120
121
122    // Used for GIF, BMP & PNG
123    private IF(String extension)
124    {
125        this.extension          = extension;
126        this.alternateExtension = null;
127    }
128
129    // Used for JPG
130    private IF(String extension, String alternateExtension)
131    {
132        this.extension          = extension;
133        this.alternateExtension = alternateExtension;
134    }
135
136
137    // ********************************************************************************************
138    // ********************************************************************************************
139    // "Guess the Extension" Methods
140    // ********************************************************************************************
141    // ********************************************************************************************
142
143
144    /**
145     * This will extract the file-extension from an image {@code URL}.  Not all images on the
146     * internet have {@code URL's} that end with the actual image-file-type.  In that case, or in
147     * the case that the {@code 'uriStr'} is a pointer to a non-image-file, {@code 'null'} will
148     * be returned.
149     * 
150     * @param uriStr Is the {@code uri} or File-Name of an image. 
151     * 
152     * @return If extension has a file-extension that is listed in the {@code IF[]} Array - that
153     * file-extension will be returned, otherwise {@code 'null'} will be returned.
154     */
155    public static IF getGuess(String uriStr)
156    {
157        if (uriStr == null) return null;
158
159        int pos = uriStr.lastIndexOf(".");
160
161        if (pos == -1) return null;
162        if (pos == uriStr.length() - 1) return null;
163
164        String s = uriStr.substring(pos + 1).toLowerCase().trim();
165
166
167        // The following array is a private & static array defined above
168        // NOTE: private static final IF[] arr = { JPG, GIF, BMP, PNG };
169
170        for (int i=0; i < arr.length; i++)
171
172            if (arr[i].extension.equals(s)) return arr[i];
173
174            else if (   (arr[i].alternateExtension != null)
175                    &&  (arr[i].alternateExtension.equals(s)))
176
177                return arr[i];
178
179        return null;        
180    }
181
182    /**
183     * Invokes {@link #getGuess(String)}, and returns the results - <I>unless the returned result
184     * would be null, in which case a {@link UnrecognizedImageExtException} is thrown instead</I>.
185     * 
186     * @param uriStr Is the {@code uri} or File-Name of the image. 
187     * 
188     * @return The Image-Format of this Image, based on it's File-Name
189     * 
190     * @throws UnrecognizedImageExtException If the Image-Type cannot be determined (does not match
191     * any) based on its File-Name Extension.  ({@code '.jpg', '.png', '.gif'} etc...)
192     */
193    public static IF guessOrThrow(String uriStr)
194    {
195        IF ret = getGuess(uriStr);
196        if (ret != null) return ret;
197
198        throw new UnrecognizedImageExtException(
199            "The URI or File-Name\n" +
200            "[" + uriStr + "]\n" +
201            "doesn't have a File-Extension that matches any of the recognized Image-Types " +
202            "('.jpg', '.png', '.gif' etc...)"
203        );
204    }
205
206    /**
207     * Converts a {@code String} image-extension to an instance this enumerated type.
208     * @param extension A valid image-format extension
209     * @return An instance of this enumeration, if applicable, or {@code 'null'} otherwise.
210     */
211    public static IF get(String extension)
212    {
213        extension = extension.toLowerCase().trim();
214
215        // The following array is a private & static array defined above
216        // NOTE: private static final IF[] arr = { JPG, GIF, BMP, PNG };
217
218        for (int i=0; i < arr.length; i++)
219
220            if (arr[i].extension.equals(extension)) return arr[i];
221
222            else if (   (arr[i].alternateExtension != null)
223                    &&  (arr[i].alternateExtension.equals(extension)))
224
225                return arr[i];
226
227        return null;
228    }
229
230    /**
231     * This will retrieve the image name from a {@code java.net.URL} object.
232     * 
233     * @param url The {@code url} of the image.
234     * 
235     * @return If this {@code URL} has a file-extension that is listed in the {@code IF[]} Array,
236     * that file-extension will be returned, otherwise {@code 'null'} will be returned.
237     */
238    public static IF getGuess(URL url)
239    {
240        String f = url.getFile();
241
242        return (f != null) ? getGuess(f) : null;
243    }
244
245
246    // ********************************************************************************************
247    // ********************************************************************************************
248    // Decode Base-64 String Methods
249    // ********************************************************************************************
250    // ********************************************************************************************
251
252
253    /**
254     * This will retrieve a Buffered Image from a {@code String} retrieved from a string that
255     * follows this format below.  This is the format usually found inside HTML Image Tags.
256     * 
257     * <BR /><BR /><DIV CLASS=JDHint>
258     * <B STYLE='color:red;'>Specifically:</B> <SPAN STYLE="color: green;">
259     * {@code <IMG SRC="data:image/{png or gif or jpg etc};base64,...">}</SPAN>
260     * </DIV>
261     * 
262     * <BR />The ellipsis (...) above represents the actual {@code Base-64} encoded {@code String}.
263     * Many web-sites return HTML image tags with the actual picture/image encoded into a
264     * {@code String} and saved inside the {@code 'SRC'} attribute.  This method will decode that
265     * image-as-a-{@code String} into a {@code java.awt.image.BufferedImage}
266     * 
267     * @param base64EncodedImageWithFormat The best way to obtain this {@code String} is to use the
268     * command [{@code String encoded = imageTag.AV("src"); }], and pass this variable
269     * {@code 'encoded'} to this parameter.  It is important to note that variable
270     * {@code 'imageTag'} must be a {@code public class TagNode}, and that {@code TagNode} must:
271     * 
272     * <BR /><BR /><UL CLASS=JDUL>
273     * <LI> Have {@code public final String tok} equal to {@code 'img'}
274     *      <BR /><BR />
275     *      </LI>
276     * 
277     * <LI> The {@code <IMG>} represented must have a {@code SRC="..."} which contains a 
278     *      {@code Base-64} encoded image.
279     *      </LI>
280     * </UL>
281     * 
282     * @return A decoded image that can be saved to file, and an instance of {@code IF} that
283     * identifies what type of image was specified.
284     * 
285     * <BR /><BR /><UL CLASS=JDUL>
286     * 
287     * <LI> {@code Ret2.a} (BufferedImage):} The Converted Image
288     *      <BR /><BR />
289     *      </LI>
290     * 
291     * <LI> {@code Ret2.b} (IF):} The Image Type
292     *      </LI>
293     * </UL>
294     */
295    public static Ret2<BufferedImage, IF>
296        decodeBase64ToImage(String base64EncodedImageWithFormat)
297    {
298        // sourceData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSA...==';
299        final Matcher m = B64_INIT_STRING.matcher(base64EncodedImageWithFormat);
300
301        if (! m.find()) return null;
302 
303        final String  imageFormatStr      = m.group(1);
304        final String  base64EncodedImage  = m.group(2);
305
306        final IF imageFormat = (imageFormatStr != null)
307            ? IF.get(imageFormatStr)
308            : null;
309
310        if (imageFormat == null) return null;
311
312        final BufferedImage bi =
313            decodeBase64ToImage(base64EncodedImage, imageFormat);
314
315        if (bi == null) return null;
316
317        return new Ret2<BufferedImage, IF>(bi, imageFormat);
318    }
319
320    /**
321     * This will decode a {@code Base-64 String} into an image.  Here, the decoder used is the one
322     * obtained from a call to: {@code java.util.Base64.getDecoder() }.
323     *
324     * <BR /><BR /><SPAN CLASS=CopiedJDK>Text copied from class:
325     * {@code java.util.Base64}, <B>JDK 1.8</B></SPAN>
326     * 
327     * <BR /><DIV CLASS=JDHint>
328     * <B STYLE='color:red;'>Basic: </B> Uses "The Base64 Alphabet" as specified in <I><B>Table 1 of RFC
329     * 4648 and RFC 2045</B></I> for encoding and decoding operation. The encoder does not add any
330     * line feed (line separator) character. The decoder rejects data that contains characters
331     * outside the base64 alphabet.
332     * </DIV>
333     *
334     * @return A decoded image that can be saved to file.
335     */
336    public static BufferedImage decodeBase64ToImage(String base64EncodedImage, IF imageFormat)
337    {
338        try
339            (ByteArrayInputStream bis = new ByteArrayInputStream
340                (Base64.getDecoder().decode(base64EncodedImage)))
341
342            { return ImageIO.read(bis); }
343
344        catch (IOException e)
345            { return null; }
346    }
347
348    /**
349     * This will decode a base-64 String into an image.  Here, the decoder used is the one obtained
350     * from a call to: {@code java.util.Base64.getURLDecoder() }.
351     *
352     * <BR /><BR /><SPAN CLASS=CopiedJDK>Text copied from class:
353     * {@code java.util.Base64}, <B>JDK 1.8</B></SPAN>
354     * 
355     * <BR /><DIV CLASS=JDHint>
356     * <B STYLE='color:red;'>URL and Filename safe:</B> Uses the "URL and Filename safe Base64
357     * Alphabet" as specified in <B><I>Table 2 of RFC 4648</B></I> for encoding and decoding. The
358     * encoder does not add any line feed (line separator) character. The decoder rejects data that
359     * contains characters outside the base64 alphabet.
360     * </DIV>
361     *
362     * @return A decoded image that can be saved to file.
363     */
364    public static BufferedImage decodeBase64ToImage_V2(String base64EncodedImage, IF imageFormat)
365    {
366        try
367            (ByteArrayInputStream bis = new ByteArrayInputStream
368                (Base64.getUrlDecoder().decode(base64EncodedImage)))
369
370            { return ImageIO.read(bis); }
371
372        catch (IOException e)
373            { return null; }
374    }
375
376
377    // ********************************************************************************************
378    // ********************************************************************************************
379    // java.lang.Object
380    // ********************************************************************************************
381    // ********************************************************************************************
382
383    
384    /**
385     * Convert an instance of this enumerated-type to a {@code String}.
386     * @return The image-format extension as a {@code String}.
387     */
388    public String toString() { return extension; }
389}