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     * <LI> {@code m.group(1) => } Image Encoding Type-{@code String} ({@code "gif", "jpg",} etc..)
098     *      <BR /><BR />
099     *      </LI>
100     * <LI>{@code m.gropu(2) => } Base 64 Encoded Image-{@code String}
101     *      <BR />
102     *      </LI>
103     * </OL>
104     */
105    public static final Pattern B64_INIT_STRING = Pattern.compile(
106        "^\\s*data:\\s*image\\/\\s*([A-Za-z]{3,4})\\s*;\\s*base64\\s*,(.*)$",
107        Pattern.CASE_INSENSITIVE
108    );
109
110    private static final IF[] arr = { JPG, GIF, BMP, PNG };
111
112
113    // ********************************************************************************************
114    // ********************************************************************************************
115    // Constructors
116    // ********************************************************************************************
117    // ********************************************************************************************
118
119
120    // Used for GIF, BMP & PNG
121    private IF(String extension)
122    {
123        this.extension          = extension;
124        this.alternateExtension = null;
125    }
126
127    // Used for JPG
128    private IF(String extension, String alternateExtension)
129    {
130        this.extension          = extension;
131        this.alternateExtension = alternateExtension;
132    }
133
134
135    // ********************************************************************************************
136    // ********************************************************************************************
137    // "Guess the Extension" Methods
138    // ********************************************************************************************
139    // ********************************************************************************************
140
141
142    /**
143     * This will extract the file-extension from an image {@code URL}.  Not all images on the
144     * internet have {@code URL's} that end with the actual image-file-type.  In that case, or in
145     * the case that the {@code 'uriStr'} is a pointer to a non-image-file, {@code 'null'} will
146     * be returned.
147     * 
148     * @param uriStr Is the {@code uri} or File-Name of an image. 
149     * 
150     * @return If extension has a file-extension that is listed in the {@code IF[]} Array - that
151     * file-extension will be returned, otherwise {@code 'null'} will be returned.
152     */
153    public static IF getGuess(String uriStr)
154    {
155        if (uriStr == null) return null;
156
157        int pos = uriStr.lastIndexOf(".");
158
159        if (pos == -1) return null;
160        if (pos == uriStr.length() - 1) return null;
161
162        String s = uriStr.substring(pos + 1).toLowerCase().trim();
163
164        // The following array is a private & static array defined above
165        // NOTE: private static final IF[] arr = { JPG, GIF, BMP, PNG };
166
167        for (int i=0; i < arr.length; i++)
168
169            if (arr[i].extension.equals(s)) return arr[i];
170
171            else if (   (arr[i].alternateExtension != null)
172                    &&  (arr[i].alternateExtension.equals(s)))
173
174                return arr[i];
175
176        return null;        
177    }
178
179    /**
180     * Invokes {@link #getGuess(String)}, and returns the results - <I>unless the returned result
181     * would be null, in which case a {@link UnrecognizedImageExtException} is thrown instead</I>.
182     * 
183     * @param uriStr Is the {@code uri} or File-Name of the image. 
184     * 
185     * @return The Image-Format of this Image, based on it's File-Name
186     * 
187     * @throws UnrecognizedImageExtException If the Image-Type cannot be determined (does not match
188     * any) based on its File-Name Extension.  ({@code '.jpg', '.png', '.gif'} etc...)
189     */
190    public static IF guessOrThrow(String uriStr)
191    {
192        IF ret = getGuess(uriStr);
193        if (ret != null) return ret;
194
195        throw new UnrecognizedImageExtException(
196            "The URI or File-Name\n" +
197            "[" + uriStr + "]\n" +
198            "doesn't have a File-Extension that matches any of the recognized Image-Types " +
199            "('.jpg', '.png', '.gif' etc...)"
200        );
201    }
202
203    /**
204     * Converts a {@code String} image-extension to an instance this enumerated type.
205     * @param extension A valid image-format extension
206     * @return An instance of this enumeration, if applicable, or {@code 'null'} otherwise.
207     */
208    public static IF get(String extension)
209    {
210        extension = extension.toLowerCase().trim();
211
212        // The following array is a private & static array defined above
213        // NOTE: private static final IF[] arr = { JPG, GIF, BMP, PNG };
214
215        for (int i=0; i < arr.length; i++)
216
217            if (arr[i].extension.equals(extension)) return arr[i];
218
219            else if (   (arr[i].alternateExtension != null)
220                    &&  (arr[i].alternateExtension.equals(extension)))
221
222                return arr[i];
223
224        return null;
225    }
226
227    /**
228     * This will retrieve the image name from a {@code java.net.URL} object.
229     * 
230     * @param url The {@code url} of the image.
231     * 
232     * @return If this {@code URL} has a file-extension that is listed in the {@code IF[]} Array,
233     * that file-extension will be returned, otherwise {@code 'null'} will be returned.
234     */
235    public static IF getGuess(URL url)
236    {
237        String f = url.getFile();
238
239        return (f != null) ? getGuess(f) : null;
240    }
241
242
243    // ********************************************************************************************
244    // ********************************************************************************************
245    // Decode Base-64 String Methods
246    // ********************************************************************************************
247    // ********************************************************************************************
248
249
250    /**
251     * This will retrieve a Buffered Image from a {@code String} retrieved from a string that
252     * follows this format below.  This is the format usually found inside HTML Image Tags.
253     * 
254     * <BR /><BR /><B>SPECIFICALLY: </B><SPAN STYLE="color: green;">
255     * {@code <IMG SRC="data:image/{png or gif or jpg etc};base64,...">}</SPAN>
256     * 
257     * <BR /><BR />The ellipsis (...) above represents the actual {@code Base-64} encoded
258     * {@code String}.  Many web-sites return HTML image tags with the actual picture/image encoded
259     * into a {@code String} and saved inside the {@code 'SRC'} attribute.  This method will decode
260     * that image-as-a-{@code String} into a {@code java.awt.image.BufferedImage}
261     * 
262     * @param base64EncodedImageWithFormat The best way to obtain this {@code String} is to use the
263     * command [{@code String encoded = imageTag.AV("src"); }], and pass this variable
264     * {@code 'encoded'} to this parameter.  It is important to note that variable
265     * {@code 'imageTag'} must be a {@code public class TagNode}, and that {@code TagNode} must:
266     * 
267     * <BR /><BR /><UL CLASS=JDUL>
268     * <LI> Have {@code public final String tok} equal to {@code 'img'}
269     *      <BR /><BR />
270     *      </LI>
271     * 
272     * <LI> The {@code <IMG>} represented must have a {@code SRC="..."} which contains a 
273     *      {@code Base-64} encoded image.
274     *      </LI>
275     * </UL>
276     * 
277     * @return A decoded image that can be saved to file, and an instance of {@code IF} that
278     * identifies what type of image was specified.
279     * 
280     * <BR /><BR /><UL CLASS=JDUL>
281     * <LI> {@code Ret2.a} (BufferedImage):} The Converted Image
282     *      <BR /><BR />
283     *      </LI>
284     * 
285     * <LI> {@code Ret2.b} (IF):} The Image Type
286     *      </LI>
287     * </UL>
288     */
289    public static Ret2<BufferedImage, IF>
290        decodeBase64ToImage(String base64EncodedImageWithFormat)
291    {
292        // sourceData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSA...==';
293        Matcher m = B64_INIT_STRING.matcher(base64EncodedImageWithFormat);
294
295        if (! m.find()) return null;
296 
297        String  imageFormatStr      = m.group(1);
298        String  base64EncodedImage  = m.group(2);
299
300        IF      imageFormat         = (imageFormatStr != null) ? IF.get(imageFormatStr) : null;
301
302        if (imageFormat == null) return null;
303
304        BufferedImage bi = decodeBase64ToImage(base64EncodedImage, imageFormat);
305
306        if (bi == null) return null;
307
308        return new Ret2<BufferedImage, IF>(bi, imageFormat);
309    }
310
311    /**
312     * This will decode a {@code Base-64 String} into an image.  Here, the decoder used is the one
313     * obtained from a call to: {@code java.util.Base64.getDecoder() }.
314     *
315     * <BR /><BR /><SPAN CLASS=CopiedJDK>Text copied from class:
316     * {@code java.util.Base64}, <B>JDK 1.8</B></SPAN>
317     * 
318     * <BR /><BR /><B>Basic: </B> Uses "The Base64 Alphabet" as specified in <I><B>Table 1 of RFC
319     * 4648 and RFC 2045</B></I> for encoding and decoding operation. The encoder does not add any
320     * line feed (line separator) character. The decoder rejects data that contains characters
321     * outside the base64 alphabet.
322     *
323     * @return A decoded image that can be saved to file.
324     */
325    public static BufferedImage decodeBase64ToImage(String base64EncodedImage, IF imageFormat)
326    {
327        try
328            (ByteArrayInputStream bis = new ByteArrayInputStream
329                (Base64.getDecoder().decode(base64EncodedImage)))
330
331            { return ImageIO.read(bis); }
332
333        catch (IOException e) { return null; }
334    }
335
336    /**
337     * This will decode a base-64 String into an image.  Here, the decoder used is the one obtained
338     * from a call to: {@code java.util.Base64.getURLDecoder() }.
339     *
340     * <BR /><BR /><SPAN CLASS=CopiedJDK>Text copied from class:
341     * {@code java.util.Base64}, <B>JDK 1.8</B></SPAN>
342     * 
343     * <BR /><BR /><B>URL and Filename safe: </B> Uses the "URL and Filename safe Base64 Alphabet"
344     * as specified in <B><I>Table 2 of RFC 4648</B></I> for encoding and decoding. The encoder
345     * does not add any line feed (line separator) character. The decoder rejects data that
346     * contains characters outside the base64 alphabet.
347     *
348     * @return A decoded image that can be saved to file.
349     */
350    public static BufferedImage decodeBase64ToImage_V2(String base64EncodedImage, IF imageFormat)
351    {
352        try
353            (ByteArrayInputStream bis = new ByteArrayInputStream
354                (Base64.getUrlDecoder().decode(base64EncodedImage)))
355
356            { return ImageIO.read(bis); }
357
358        catch (IOException e) { return null; }
359    }
360
361
362    // ********************************************************************************************
363    // ********************************************************************************************
364    // java.lang.Object
365    // ********************************************************************************************
366    // ********************************************************************************************
367
368    
369    /**
370     * Convert an instance of this enumerated-type to a {@code String}.
371     * @return The image-format extension as a {@code String}.
372     */
373    public String toString() { return extension; }
374}