3333import com .sun .javafx .iio .ios .IosImageLoaderFactory ;
3434import com .sun .javafx .iio .jpeg .JPEGImageLoaderFactory ;
3535import com .sun .javafx .iio .png .PNGImageLoaderFactory ;
36+ import com .sun .javafx .logging .PlatformLogger ;
3637import com .sun .javafx .util .DataURI ;
38+ import com .sun .javafx .util .Logging ;
3739
3840import java .io .ByteArrayInputStream ;
41+ import java .io .EOFException ;
3942import java .io .IOException ;
4043import java .io .InputStream ;
4144import java .io .SequenceInputStream ;
@@ -110,20 +113,32 @@ public static enum ImageType {
110113 */
111114 RGBA_PRE
112115 };
113- /**
114- * A mapping of lower case file extensions to loader factories.
115- */
116+ // /**
117+ // * A mapping of lower case file extensions to loader factories.
118+ // */
116119// private static HashMap<String, ImageLoaderFactory> loaderFactoriesByExtension;
117120 /**
118121 * A mapping of format signature byte sequences to loader factories.
119122 */
120- private static final HashMap <Signature , ImageLoaderFactory > loaderFactoriesBySignature ;
121- private static final ImageLoaderFactory [] loaderFactories ;
123+ private final HashMap <Signature , ImageLoaderFactory > loaderFactoriesBySignature ;
124+ /**
125+ * A mapping of lower case MIME subtypes to loader factories.
126+ */
127+ private final HashMap <String , ImageLoaderFactory > loaderFactoriesByMimeSubtype ;
128+ private final ImageLoaderFactory [] loaderFactories ;
129+ private int maxSignatureLength ;
130+
122131 private static final boolean isIOS = PlatformUtil .isIOS ();
123132
124- private static int maxSignatureLength ;
133+ private static class InstanceHolder {
134+ static final ImageStorage INSTANCE = new ImageStorage ();
135+ }
136+
137+ public static ImageStorage getInstance () {
138+ return InstanceHolder .INSTANCE ;
139+ }
125140
126- static {
141+ public ImageStorage () {
127142 if (isIOS ) {
128143 //On iOS we have single factory/ native loader
129144 //for all image formats
@@ -141,14 +156,15 @@ public static enum ImageType {
141156 }
142157
143158// loaderFactoriesByExtension = new HashMap(numExtensions);
144- loaderFactoriesBySignature = new HashMap <Signature , ImageLoaderFactory >(loaderFactories .length );
159+ loaderFactoriesBySignature = new HashMap <>(loaderFactories .length );
160+ loaderFactoriesByMimeSubtype = new HashMap <>(loaderFactories .length );
145161
146162 for (int i = 0 ; i < loaderFactories .length ; i ++) {
147163 addImageLoaderFactory (loaderFactories [i ]);
148164 }
149165 }
150166
151- public static ImageFormatDescription [] getSupportedDescriptions () {
167+ public ImageFormatDescription [] getSupportedDescriptions () {
152168 ImageFormatDescription [] formats = new ImageFormatDescription [loaderFactories .length ];
153169 for (int i = 0 ; i < loaderFactories .length ; i ++) {
154170 formats [i ] = loaderFactories [i ].getFormatDescription ();
@@ -162,7 +178,7 @@ public static ImageFormatDescription[] getSupportedDescriptions() {
162178 * @param type the type of image
163179 * @return the number of bands of a raw image of this type
164180 */
165- public static int getNumBands (ImageType type ) {
181+ public int getNumBands (ImageType type ) {
166182 int numBands = -1 ;
167183 switch (type ) {
168184 case GRAY :
@@ -191,12 +207,12 @@ public static int getNumBands(ImageType type) {
191207
192208 /**
193209 * Registers an image loader factory. The factory replaces any other factory
194- * previously registered for the file extensions (converted to lower case)
195- * and signature indicated by the format description.
210+ * previously registered for the file extensions (converted to lower case),
211+ * MIME subtype, and signature indicated by the format description.
196212 *
197213 * @param factory the factory to register.
198214 */
199- public static void addImageLoaderFactory (ImageLoaderFactory factory ) {
215+ public void addImageLoaderFactory (ImageLoaderFactory factory ) {
200216 ImageFormatDescription desc = factory .getFormatDescription ();
201217// String[] extensions = desc.getExtensions();
202218// for (int j = 0; j < extensions.length; j++) {
@@ -207,6 +223,10 @@ public static void addImageLoaderFactory(ImageLoaderFactory factory) {
207223 loaderFactoriesBySignature .put (signature , factory );
208224 }
209225
226+ for (String subtype : desc .getMIMESubtypes ()) {
227+ loaderFactoriesByMimeSubtype .put (subtype .toLowerCase (), factory );
228+ }
229+
210230 // invalidate max signature length
211231 synchronized (ImageStorage .class ) {
212232 maxSignatureLength = -1 ;
@@ -254,7 +274,7 @@ public static void addImageLoaderFactory(ImageLoaderFactory factory) {
254274 * @return the sequence of all images in the specified source or
255275 * <code>null</code> on error.
256276 */
257- public static ImageFrame [] loadAll (InputStream input , ImageLoadListener listener ,
277+ public ImageFrame [] loadAll (InputStream input , ImageLoadListener listener ,
258278 double width , double height , boolean preserveAspectRatio ,
259279 float pixelScale , boolean smooth ) throws ImageStorageException {
260280 ImageLoader loader = null ;
@@ -289,7 +309,7 @@ public static ImageFrame[] loadAll(InputStream input, ImageLoadListener listener
289309 * Load all images present in the specified input. For more details refer to
290310 * {@link #loadAll(InputStream, ImageLoadListener, double, double, boolean, float, boolean)}.
291311 */
292- public static ImageFrame [] loadAll (String input , ImageLoadListener listener ,
312+ public ImageFrame [] loadAll (String input , ImageLoadListener listener ,
293313 double width , double height , boolean preserveAspectRatio ,
294314 float devPixelScale , boolean smooth ) throws ImageStorageException {
295315
@@ -304,38 +324,69 @@ public static ImageFrame[] loadAll(String input, ImageLoadListener listener,
304324 try {
305325 float imgPixelScale = 1.0f ;
306326 try {
307- if (devPixelScale >= 1.5f ) {
308- // Use Mac Retina conventions for >= 1.5f
309- try {
310- String name2x = ImageTools .getScaledImageName (input );
311- theStream = ImageTools .createInputStream (name2x );
312- imgPixelScale = 2.0f ;
313- } catch (IOException ignored ) {
327+ DataURI dataUri = DataURI .tryParse (input );
328+ if (dataUri != null ) {
329+ if (!"image" .equalsIgnoreCase (dataUri .getMimeType ())) {
330+ throw new IllegalArgumentException ("Unexpected MIME type: " + dataUri .getMimeType ());
314331 }
315- }
316332
317- if (theStream == null ) {
318- try {
319- theStream = ImageTools .createInputStream (input );
320- } catch (IOException ex ) {
321- DataURI dataUri = DataURI .tryParse (input );
322- if (dataUri != null ) {
323- String mimeType = dataUri .getMimeType ();
324- if (mimeType != null && !"image" .equalsIgnoreCase (dataUri .getMimeType ())) {
325- throw new IllegalArgumentException ("Unexpected MIME type: " + dataUri .getMimeType ());
333+ // Find a factory that can load images with the specified MIME type.
334+ var factory = loaderFactoriesByMimeSubtype .get (dataUri .getMimeSubtype ().toLowerCase ());
335+ if (factory == null ) {
336+ throw new IllegalArgumentException (
337+ "Unsupported MIME subtype: image/" + dataUri .getMimeSubtype ());
338+ }
339+
340+ // We also inspect the image file signature to confirm that it matches the MIME type.
341+ theStream = new ByteArrayInputStream (dataUri .getData ());
342+ ImageLoader loaderBySignature = getLoaderBySignature (theStream , listener );
343+
344+ if (loaderBySignature != null ) {
345+ // If the MIME type doesn't agree with the file signature, log a warning and
346+ // continue with the image loader that matches the file signature.
347+ boolean imageTypeMismatch = !factory .getFormatDescription ().getFormatName ().equals (
348+ loaderBySignature .getFormatDescription ().getFormatName ());
349+
350+ if (imageTypeMismatch ) {
351+ var logger = Logging .getJavaFXLogger ();
352+ if (logger .isLoggable (PlatformLogger .Level .WARNING )) {
353+ logger .warning (String .format (
354+ "Image format '%s' does not match MIME type '%s/%s' in URI '%s'" ,
355+ loaderBySignature .getFormatDescription ().getFormatName (),
356+ dataUri .getMimeType (), dataUri .getMimeSubtype (), dataUri ));
326357 }
358+ }
327359
328- theStream = new ByteArrayInputStream (dataUri .getData ());
329- } else {
330- throw ex ;
360+ loader = loaderBySignature ;
361+ } else {
362+ // We're here because the image format doesn't have a detectable signature.
363+ // In this case, we need to close the input stream (because we already consumed
364+ // parts of it to detect a potential file signature) and create a new input
365+ // stream for the image loader that matches the MIME type.
366+ theStream .close ();
367+ theStream = new ByteArrayInputStream (dataUri .getData ());
368+ loader = factory .createImageLoader (theStream );
369+ }
370+ } else {
371+ if (devPixelScale >= 1.5f ) {
372+ // Use Mac Retina conventions for >= 1.5f
373+ try {
374+ String name2x = ImageTools .getScaledImageName (input );
375+ theStream = ImageTools .createInputStream (name2x );
376+ imgPixelScale = 2.0f ;
377+ } catch (IOException ignored ) {
331378 }
332379 }
333- }
334380
335- if (isIOS ) {
336- loader = IosImageLoaderFactory .getInstance ().createImageLoader (theStream );
337- } else {
338- loader = getLoaderBySignature (theStream , listener );
381+ if (theStream == null ) {
382+ theStream = ImageTools .createInputStream (input );
383+ }
384+
385+ if (isIOS ) {
386+ loader = IosImageLoaderFactory .getInstance ().createImageLoader (theStream );
387+ } else {
388+ loader = getLoaderBySignature (theStream , listener );
389+ }
339390 }
340391 } catch (Exception e ) {
341392 throw new ImageStorageException (e .getMessage (), e );
@@ -361,7 +412,7 @@ public static ImageFrame[] loadAll(String input, ImageLoadListener listener,
361412 return images ;
362413 }
363414
364- private static synchronized int getMaxSignatureLength () {
415+ private synchronized int getMaxSignatureLength () {
365416 if (maxSignatureLength < 0 ) {
366417 maxSignatureLength = 0 ;
367418 for (final Signature signature :
@@ -376,7 +427,7 @@ private static synchronized int getMaxSignatureLength() {
376427 return maxSignatureLength ;
377428 }
378429
379- private static ImageFrame [] loadAll (ImageLoader loader ,
430+ private ImageFrame [] loadAll (ImageLoader loader ,
380431 double width , double height , boolean preserveAspectRatio ,
381432 float pixelScale , boolean smooth ) throws ImageStorageException {
382433 ImageFrame [] images = null ;
@@ -433,9 +484,14 @@ private static ImageFrame[] loadAll(ImageLoader loader,
433484// return loader;
434485// }
435486
436- private static ImageLoader getLoaderBySignature (InputStream stream , ImageLoadListener listener ) throws IOException {
487+ private ImageLoader getLoaderBySignature (InputStream stream , ImageLoadListener listener ) throws IOException {
437488 byte [] header = new byte [getMaxSignatureLength ()];
438- ImageTools .readFully (stream , header );
489+
490+ try {
491+ ImageTools .readFully (stream , header );
492+ } catch (EOFException ignored ) {
493+ return null ;
494+ }
439495
440496 for (final Entry <Signature , ImageLoaderFactory > factoryRegistration :
441497 loaderFactoriesBySignature .entrySet ()) {
@@ -454,7 +510,4 @@ private static ImageLoader getLoaderBySignature(InputStream stream, ImageLoadLis
454510 // not found
455511 return null ;
456512 }
457-
458- private ImageStorage () {
459- }
460513}
0 commit comments