Opened 8 years ago
Last modified 8 years ago
#15574 new enhancement
[patch] [experimental] make large jpeg loading work through the use of JNI bridge to turbojpeg system library — at Version 1
| Reported by: | cmuelle8 | Owned by: | team |
|---|---|---|---|
| Priority: | normal | Milestone: | |
| Component: | Core image mapping | Version: | latest |
| Keywords: | jpeg geoimage turbojpeg large images libjpeg-turbo jni | Cc: |
Description (last modified by )
Image loading in JOSM is currently done with
Image img = Toolkit.getDefaultToolkit().createImage(file.getPath()); [..] tracker.addImage(img, 1);
This may cause issues when trying to load images with large dimensions, if the heap space cannot hold the buffer of a full decode of the image. JPEG loading however may be done in a way that the native decoding library decodes and scales the image on the fly (before the pixels are being sent to a BufferedImage).
JDK8 and JDK9 already use jpeg or turbojpeg system libraries in the background to do the heavy lifting, but there does not seem to be a proper way in those JDKs to delegate the scaling wish down to the JNI level. The imageio framework has canSetSourceRenderSize() to check if a plugin can handle a custom source render size, but this is currently (JDK7, JDK8, JDK9) not implemented for the JPEG decoder, see
- https://docs.oracle.com/javase/9/docs/api/javax/imageio/ImageReadParam.html#canSetSourceRenderSize--
- https://docs.oracle.com/javase/9/docs/api/index.html?javax/imageio/plugins/jpeg/JPEGImageReadParam.html
One solution is to employ the JNI bridge to turbojpeg system library.
- https://libjpeg-turbo.org/Documentation/Documentation
- https://cdn.rawgit.com/libjpeg-turbo/libjpeg-turbo/dev/java/doc/index.html
Problems (caveats) with this approach are
- libjpeg-turbo on the host system must have been compiled with the
--with-javaswith to./configure- debian/ubuntu precompiled versions do not have java enabled by default, see debian/rules
- the library needs to be found and readable by the JVM to load (general JNI problem)
- as the loaded image will be a scaled version, zooming in 1:1 will not be possible
- this may be overcome by reloading subregions of the source image from file as needed
- in principle this should be possible using this decompress variant instead of the convenience function)
Test data (a jpeg image with 30.000x21.952 pixels) may be found under
Trying to load this file without the proposed patch results in
- in
java.lang.OutOfMemoryError: Java heap spaceor - if JOSM is started with
-Xmx4G(or a variant) to supply more heap space- in a terminated JVM and a segfaulting Xorg (libpthread related)
- serious bug related to OOM conditions in openjdk (?)
The proposed patch makes an effort to determine the presumably available memory before loading an image. It reads width and height of an image without loading it and then checks
- if the image will presumable fit into half of the avail memory to the JVM it will let the default toolkit load it as usual
- otherwise an instance of TJDecoder is used to load a scaled version (that will also not employ more than half of the available JVM heap space memory)
Not taking more than half of the memory available is especially necessary for the bilinear scaling to work as it duplicates the image buffer (worst case) in best fit mode.
In general, because of the deliberate checking for possible OOM conditions, JOSM will be less likely to hit such exceptions if large images are loaded (this applies to all image formats, not just JPEG, but for JPEGs the code will make a second attempt in loading the image instead of giving up on it).
Future work:
Considering that bilinear scaling may be turned off through prefs, the constraint of not using more than half the available memory may be loosened. However, a dynamic check depending on the state bilin* options are in, might be a false friend: If this option is changed at runtime, but the image is not reloaded yet, it might have better been a static check (as is employed now in the patch).
Pledge for further testing:
This patch had limited testing on a single dev machine and a single OS.
It has not been tested, whether libjpeg-turbo is properly loaded under Windows or MacOS, e.g.


