Properly handle big PNG files master
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 1 Aug 2026 22:18:40 +0000 (01:18 +0300)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 1 Aug 2026 22:18:40 +0000 (01:18 +0300)
src/main/java/eu/svjatoslav/meviz/htmlindexer/Constants.java
src/main/java/eu/svjatoslav/meviz/htmlindexer/Utils.java

index ea32774..6725f64 100755 (executable)
@@ -10,7 +10,7 @@ public class Constants {
 
     public static final String DEFAULT_GALLERY_TITLE = "Gallery";
 
 
     public static final String DEFAULT_GALLERY_TITLE = "Gallery";
 
-    public static final String METADATA_FILE_NAME = "metadata_7.dat";
+    public static final String METADATA_FILE_NAME = "metadata_8.dat";
 
     public static final String THUMBNAILS_DIRECTORY_NAME = ".thumbnails";
 
 
     public static final String THUMBNAILS_DIRECTORY_NAME = ".thumbnails";
 
@@ -36,6 +36,6 @@ public class Constants {
     public static final String[] SUPPORTED_VIDEO_EXTENSIONS = {
             "avi", "mp4", "mpeg", "mpg", "mkv", "flv", "ogv"};
 
     public static final String[] SUPPORTED_VIDEO_EXTENSIONS = {
             "avi", "mp4", "mpeg", "mpg", "mkv", "flv", "ogv"};
 
-    public static final String THUMBNAIL_VERSION = "3";
+    public static final String THUMBNAIL_VERSION = "4";
 
 }
 
 }
index 78ad63a..98c96f4 100755 (executable)
@@ -34,6 +34,15 @@ import static org.openimaj.image.ImageUtilities.createBufferedImage;
 import static org.openimaj.image.ImageUtilities.readMBF;
 
 public class Utils {
 import static org.openimaj.image.ImageUtilities.readMBF;
 
 public class Utils {
+    /**
+     * Read-ahead limit (in bytes) hard-coded inside OpenIMAJ's
+     * {@code ExtendedImageIO.read()} stream mark. Apache Sanselan scans the
+     * whole file while sniffing the image format, so for files larger than
+     * this limit the stream mark gets invalidated and every subsequent
+     * {@code reset()} throws {@code IOException: Resetting to invalid mark}.
+     */
+    private static final long OPENIMAJ_STREAM_MARK_LIMIT = 104857600L;
+
     private static File lastLoadedFile;
     private static BufferedImage lastLoadedBufferedImage;
 
     private static File lastLoadedFile;
     private static BufferedImage lastLoadedBufferedImage;
 
@@ -53,7 +62,7 @@ public class Utils {
         if ("jxl".equals(extension)) {
             lastLoadedBufferedImage = loadJxlImage(file);
         } else {
         if ("jxl".equals(extension)) {
             lastLoadedBufferedImage = loadJxlImage(file);
         } else {
-            lastLoadedBufferedImage = createBufferedImage(readMBF(file));
+            lastLoadedBufferedImage = loadWithOpenImaj(file);
         }
         lastLoadedFile = file;
 
         }
         lastLoadedFile = file;
 
@@ -67,6 +76,39 @@ public class Utils {
         return lastLoadedBufferedImage;
     }
 
         return lastLoadedBufferedImage;
     }
 
+    /**
+     * Decode an image with OpenIMAJ (which handles CMYK JPEGs and other
+     * exotic variants that plain JDK ImageIO chokes on).
+     *
+     * Files larger than {@link #OPENIMAJ_STREAM_MARK_LIMIT} are decoded
+     * directly with JDK {@link ImageIO#read(File)}, because OpenIMAJ cannot
+     * handle them: its {@code ExtendedImageIO.read()} marks the input stream
+     * with a 100 MB read-ahead limit, Apache Sanselan then scans the entire
+     * file while sniffing the format, the mark gets invalidated and every
+     * subsequent {@code reset()} fails with
+     * {@code IOException: Resetting to invalid mark}.
+     * {@code ImageIO.read(File)} uses a random-access
+     * {@code FileImageInputStream} instead of mark/reset on the raw stream,
+     * so it decodes such files without trouble.
+     *
+     * For smaller files OpenIMAJ stays the primary decoder, with the same
+     * JDK ImageIO fallback in case it still fails.
+     */
+    private static BufferedImage loadWithOpenImaj(final File file)
+            throws IOException {
+        if (file.length() > OPENIMAJ_STREAM_MARK_LIMIT)
+            return ImageIO.read(file);
+
+        try {
+            return createBufferedImage(readMBF(file));
+        } catch (final IOException e) {
+            System.out.println("OpenIMAJ failed to decode \""
+                    + file.getName() + "\" (" + e.getMessage()
+                    + "), retrying with JDK ImageIO.");
+            return ImageIO.read(file);
+        }
+    }
+
     /**
      * Load a JPEG XL (.jxl) image by converting it to PNG using the djxl decoder
      * from libjxl-tools, since Java's ImageIO and OpenIMAJ do not natively
     /**
      * Load a JPEG XL (.jxl) image by converting it to PNG using the djxl decoder
      * from libjxl-tools, since Java's ImageIO and OpenIMAJ do not natively