From 0b5e696d79ab1a160293d520b7526e031238989a Mon Sep 17 00:00:00 2001
From: mio <stigma@disroot.org>
Date: Tue, 11 Mar 2025 13:54:49 +1000
Subject: Use JasPer 3 init/cleanup functions

The jas_init() and jas_cleanup() where deprecated in JasPer 3.0. There
was also a functionality change in jas_init() which has stopped
registering jas_cleanup() with 'atexit(3)', meaning that, when compiled
with JasPer 3 or newer, there have been memory leaks in certain
instances.

This commit also introduces a memory limit for JasPer, without which,
JasPer will log warnings to the standard error stream.  We set a
maximum of 512 MB, but will use whatever JasPer was configured with if
it is lower.

Signed-off-by: mio <stigma@disroot.org>
---
 src/libs/dimg/loaders/jp2kloader.cpp | 103 ++++++++++++++++++++++++++++++-----
 1 file changed, 89 insertions(+), 14 deletions(-)

(limited to 'src')

diff --git a/src/libs/dimg/loaders/jp2kloader.cpp b/src/libs/dimg/loaders/jp2kloader.cpp
index 66351c25..016a7196 100644
--- a/src/libs/dimg/loaders/jp2kloader.cpp
+++ b/src/libs/dimg/loaders/jp2kloader.cpp
@@ -58,6 +58,16 @@ extern "C"
 #include "dimgloaderobserver.h"
 #include "jp2kloader.h"
 
+static void cleanup_jasper()
+{
+#if defined(JAS_VERSION_MAJOR) && (JAS_VERSION_MAJOR >= 3)
+    jas_cleanup_thread();
+    jas_cleanup_library();
+#else
+    jas_cleanup();
+#endif
+}
+
 namespace Digikam
 {
 
@@ -69,7 +79,7 @@ JP2KLoader::JP2KLoader(DImg* image)
 }
 
 bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
-{    
+{
     readMetadata(filePath, DImg::JPEG);
 
     FILE *file = fopen(TQFile::encodeName(filePath), "rb");
@@ -109,17 +119,45 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
     jas_stream_t *jp2_stream  = 0;
     jas_matrix_t *pixels[4];
 
-    int init = jas_init();
-    if (init != 0)
+
+#if defined(JAS_VERSION_MAJOR) && (JAS_VERSION_MAJOR >= 3)
+    jas_conf_clear();
+
+    // Limit JasPer memory usage to 512 MB
+    size_t memoryLimit = (512 * 1024) * 1024;
+    size_t jasperTotalMemSize = jas_get_total_mem_size();
+    if (!jasperTotalMemSize)
+    {
+        jasperTotalMemSize = JAS_DEFAULT_MAX_MEM_USAGE;
+    }
+    memoryLimit = (memoryLimit < jasperTotalMemSize) ? memoryLimit : jasperTotalMemSize;
+    jas_conf_set_max_mem_usage(memoryLimit);
+
+    if (jas_init_library())
+    {
+        DDebug() << "Unable to init JPEG2000 decoder" << endl;
+        return false;
+    }
+
+    if (jas_init_thread())
+    {
+        DDebug() << "Unable to init JPEG2000 decoder" << endl;
+        jas_cleanup_library();
+        return false;
+    }
+#else
+    if (jas_init())
     {
         DDebug() << "Unable to init JPEG2000 decoder" << endl;
         return false;
     }
+#endif
 
     jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "rb");
     if (jp2_stream == 0)
     {
         DDebug() << "Unable to open JPEG2000 stream" << endl;
+        cleanup_jasper();
         return false;
     }
 
@@ -127,6 +165,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
     if (jp2_image == 0)
     {
         jas_stream_close(jp2_stream);
+        cleanup_jasper();
         DDebug() << "Unable to decode JPEG2000 image" << endl;
         return false;
     }
@@ -150,6 +189,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
             if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0))
             {
                 jas_image_destroy(jp2_image);
+                cleanup_jasper();
                 DDebug() << "Error parsing JPEG2000 image : Missing Image Channel" << endl;
                 return false;
             }
@@ -169,6 +209,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
             if (components[0] < 0)
             {
                 jas_image_destroy(jp2_image);
+                cleanup_jasper();
                 DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl;
                 return false;
             }
@@ -183,6 +224,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
             if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0))
             {
                 jas_image_destroy(jp2_image);
+                cleanup_jasper();
                 DDebug() << "Error parsing JP2000 image : Missing Image Channel" << endl;
                 return false;
             }
@@ -199,6 +241,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
         default:
         {
             jas_image_destroy(jp2_image);
+            cleanup_jasper();
             DDebug() << "Error parsing JP2000 image : Colorspace Model Is Not Supported" << endl;
             return false;
         }
@@ -221,6 +264,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
             (jas_image_cmptsgnd(jp2_image, components[i]) != false))
         {
             jas_image_destroy(jp2_image);
+            cleanup_jasper();
             DDebug() << "Error parsing JPEG2000 image : Irregular Channel Geometry Not Supported" << endl;
             return false;
         }
@@ -242,6 +286,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
         if (!pixels[i])
         {
             jas_image_destroy(jp2_image);
+            cleanup_jasper();
             DDebug() << "Error decoding JPEG2000 image data : Memory Allocation Failed" << endl;
             return false;
         }
@@ -271,7 +316,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
         for (i = 0 ; i < (long)number_components ; i++)
             jas_matrix_destroy(pixels[i]);
 
-        jas_cleanup();
+        cleanup_jasper();
         return false;
     }
 
@@ -295,7 +340,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
                 for (i = 0 ; i < (long)number_components ; i++)
                     jas_matrix_destroy(pixels[i]);
 
-                jas_cleanup();
+                cleanup_jasper();
                 return false;
             }
         }
@@ -402,7 +447,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
                 for (i = 0 ; i < (long)number_components ; i++)
                     jas_matrix_destroy(pixels[i]);
 
-                jas_cleanup();
+                cleanup_jasper();
 
                 return false;
             }
@@ -452,7 +497,7 @@ bool JP2KLoader::load(const TQString& filePath, DImgLoaderObserver *observer)
     for (i = 0 ; i < (long)number_components ; i++)
         jas_matrix_destroy(pixels[i]);
 
-    jas_cleanup();
+    cleanup_jasper();
 
     return true;
 }
@@ -476,20 +521,48 @@ bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
     jas_matrix_t         *pixels[4];
     jas_image_cmptparm_t  component_info[4];
 
-    int init = jas_init();
-    if (init != 0)
+
+#if defined(JAS_VERSION_MAJOR) && (JAS_VERSION_MAJOR >= 3)
+    jas_conf_clear();
+
+    // Limit JasPer memory usage to 512 MB
+    size_t memoryLimit = (512 * 1024) * 1024;
+    size_t jasperTotalMemSize = jas_get_total_mem_size();
+    if (!jasperTotalMemSize)
+    {
+        jasperTotalMemSize = JAS_DEFAULT_MAX_MEM_USAGE;
+    }
+    memoryLimit = (memoryLimit < jasperTotalMemSize) ? memoryLimit : jasperTotalMemSize;
+    jas_conf_set_max_mem_usage(memoryLimit);
+
+    if (jas_init_library())
+    {
+        DDebug() << "Unable to init JPEG2000 decoder" << endl;
+        return false;
+    }
+
+    if (jas_init_thread())
+    {
+        DDebug() << "Unable to init JPEG2000 decoder" << endl;
+        jas_cleanup_library();
+        return false;
+    }
+#else
+    if (jas_init())
     {
         DDebug() << "Unable to init JPEG2000 decoder" << endl;
         return false;
     }
+#endif
 
     jp2_stream = jas_stream_fopen(TQFile::encodeName(filePath), "wb");
     if (jp2_stream == 0)
     {
         DDebug() << "Unable to open JPEG2000 stream" << endl;
+        cleanup_jasper();
         return false;
     }
-    
+
     number_components = imageHasAlpha() ? 4 : 3;
 
     for (i = 0 ; i < (long)number_components ; i++)
@@ -508,6 +581,7 @@ bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
     if (jp2_image == 0)
     {
         jas_stream_close(jp2_stream);
+        cleanup_jasper();
         DDebug() << "Unable to create JPEG2000 image" << endl;
         return false;
     }
@@ -562,6 +636,7 @@ bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
                 jas_matrix_destroy(pixels[x]);
 
             jas_image_destroy(jp2_image);
+            cleanup_jasper();
             DDebug() << "Error encoding JPEG2000 image data : Memory Allocation Failed" << endl;
             return false;
         }
@@ -583,7 +658,7 @@ bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
                 for (i = 0 ; i < (long)number_components ; i++)
                     jas_matrix_destroy(pixels[i]);
 
-                jas_cleanup();
+                cleanup_jasper();
 
                 return false;
             }
@@ -633,7 +708,7 @@ bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
                 for (i = 0 ; i < (long)number_components ; i++)
                     jas_matrix_destroy(pixels[i]);
 
-                jas_cleanup();
+                cleanup_jasper();
                 return false;
             }
         }
@@ -680,7 +755,7 @@ bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
         for (i = 0 ; i < (long)number_components ; i++)
             jas_matrix_destroy(pixels[i]);
     
-        jas_cleanup();
+        cleanup_jasper();
 
         return false;
     }
@@ -697,7 +772,7 @@ bool JP2KLoader::save(const TQString& filePath, DImgLoaderObserver *observer)
     for (i = 0 ; i < (long)number_components ; i++)
         jas_matrix_destroy(pixels[i]);
 
-    jas_cleanup();
+    cleanup_jasper();
 
     return true;
 }
-- 
cgit v1.2.1