From d3de128e6d55f9f6a1325985695b925a1056c469 Mon Sep 17 00:00:00 2001 From: Tuukka Lehtonen Date: Wed, 10 Oct 2018 13:23:29 +0300 Subject: [PATCH] Ensure ITileProviders return BufferedImages with compatible ColorModel gitlab #12 Change-Id: Ic47ae73cfdf6d71efdf7099fd67b1e674425682c --- org.simantics.district.maps/.classpath | 4 +- .../META-INF/MANIFEST.MF | 6 +- .../maps/debug/DebugTileProvider.java | 6 +- .../maps/eclipse/DiskCachingTileProvider.java | 59 +++++-------------- .../simantics/maps/internal/ImageUtil.java | 58 ++++++++++++++++++ .../simantics/maps/osm/OSMTileProvider.java | 3 +- .../src/org/simantics/maps/sg/MapNode.java | 14 ++--- .../maps/tile/CompoundTileProvider.java | 4 +- .../maps/tile/Shp2ImgTileProvider.java | 11 +--- .../simantics/maps/wms/WMSTileProvider.java | 3 +- 10 files changed, 96 insertions(+), 72 deletions(-) create mode 100644 org.simantics.district.maps/src/org/simantics/maps/internal/ImageUtil.java diff --git a/org.simantics.district.maps/.classpath b/org.simantics.district.maps/.classpath index 751c8f2e..eca7bdba 100644 --- a/org.simantics.district.maps/.classpath +++ b/org.simantics.district.maps/.classpath @@ -1,7 +1,7 @@ - - + + diff --git a/org.simantics.district.maps/META-INF/MANIFEST.MF b/org.simantics.district.maps/META-INF/MANIFEST.MF index 0ea8a6e6..7a2b4fc0 100644 --- a/org.simantics.district.maps/META-INF/MANIFEST.MF +++ b/org.simantics.district.maps/META-INF/MANIFEST.MF @@ -12,7 +12,8 @@ Require-Bundle: org.simantics.scenegraph, org.eclipse.jface, org.eclipse.ui.ide, org.eclipse.ui.workbench, - org.simantics.district.geotools;bundle-version="1.0.0" + org.simantics.district.geotools;bundle-version="1.0.0", + org.slf4j.api;bundle-version="1.7.25" Export-Package: org.simantics.maps, org.simantics.maps.debug, org.simantics.maps.eclipse, @@ -20,5 +21,4 @@ Export-Package: org.simantics.maps, org.simantics.maps.tile, org.simantics.maps.wms Import-Package: org.simantics.scenegraph.g2d -Bundle-RequiredExecutionEnvironment: JavaSE-1.6, - JavaSE-1.7 +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 diff --git a/org.simantics.district.maps/src/org/simantics/maps/debug/DebugTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/debug/DebugTileProvider.java index bb868ffd..1aa50f1d 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/debug/DebugTileProvider.java +++ b/org.simantics.district.maps/src/org/simantics/maps/debug/DebugTileProvider.java @@ -17,12 +17,14 @@ import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; +import java.awt.Transparency; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.net.URI; import java.net.URISyntaxException; import org.simantics.maps.ProvisionException; +import org.simantics.maps.internal.ImageUtil; import org.simantics.maps.tile.ITileProvider; import org.simantics.maps.tile.TileKey; @@ -93,13 +95,13 @@ public class DebugTileProvider implements ITileProvider { ); // System.out.println(yTiles + ", " + key.y + " => " + r); - BufferedImage image = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_3BYTE_BGR); + BufferedImage image = ImageUtil.createScreenCompatibleImage(tileSize, tileSize, Transparency.OPAQUE); Graphics2D g = image.createGraphics(); try { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 32)); + g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 16)); g.setColor(Color.BLACK); g.fillRect(0, 0, tileSize, tileSize); diff --git a/org.simantics.district.maps/src/org/simantics/maps/eclipse/DiskCachingTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/eclipse/DiskCachingTileProvider.java index 2bef3eeb..418b1285 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/eclipse/DiskCachingTileProvider.java +++ b/org.simantics.district.maps/src/org/simantics/maps/eclipse/DiskCachingTileProvider.java @@ -18,6 +18,7 @@ import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.net.URI; +import java.util.concurrent.ForkJoinPool; import javax.imageio.ImageIO; @@ -25,14 +26,19 @@ import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Platform; import org.osgi.framework.Bundle; import org.simantics.maps.ProvisionException; +import org.simantics.maps.internal.ImageUtil; import org.simantics.maps.tile.ITileProvider; import org.simantics.maps.tile.TileKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @author Tuukka Lehtonen */ public class DiskCachingTileProvider implements ITileProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(DiskCachingTileProvider.class); + private final String CACHED_IMAGE_FORMAT = "png"; ITileProvider provider; @@ -41,15 +47,6 @@ public class DiskCachingTileProvider implements ITileProvider { boolean supportAlpha; -// private ImageWriter writer; -// -// private ImageReader reader; -// -// private ImageTypeSpecifier noAlphaType = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); -// -// private ImageTypeSpecifier alphaType = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); - - public DiskCachingTileProvider(ITileProvider provider, Boolean supportAlpha) { this.provider = provider; this.supportAlpha = supportAlpha; @@ -79,8 +76,7 @@ public class DiskCachingTileProvider implements ITileProvider { if (!f.exists()) { f.mkdirs(); } else if (!f.isDirectory()) { -// FIXME -// ErrorLogger.defaultLogError("Cache directory " + f.toString() + " already exists as a file, cannot use it for cache", new Exception()); + LOGGER.error("Tile cache directory " + f.toString() + " already exists as a file, cannot use it as a cache directory", new Exception()); return; } @@ -104,40 +100,18 @@ public class DiskCachingTileProvider implements ITileProvider { IPath cachePath = toTileCachePath(key).addFileExtension(CACHED_IMAGE_FORMAT); File f = cachePath.toFile(); if (f.exists()) { -// ImageInputStream iis = null; try { -// iis = ImageIO.createImageInputStream(f); -// ImageReadParam param = reader.getDefaultReadParam(); -// param.setDestinationType(supportAlpha ? alphaType : noAlphaType); -// reader.setInput(iis, true, true); -// BufferedImage img = reader.read(0, param); BufferedImage img = ImageIO.read(f); if (img != null) { //System.out.println("[" + provider + "] Disk cache hit: " + key); //System.out.println("[" + provider + "] image: " + img); // Need to make sure that the type of the loaded image is // something that can be rendered in an accelerated fashion. - if (img.getType() != BufferedImage.TYPE_CUSTOM) - return img; - - BufferedImage img2 = null; - if (img.getAlphaRaster() != null) { - img2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); - } else { - img2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_3BYTE_BGR); - } - //System.out.println("[" + provider + "] image2: " + img2); - - img.copyData(img2.getRaster()); - return img2; + return ImageUtil.toScreenCompatibleImage(img); } } catch (IOException e) { - e.printStackTrace(); + LOGGER.warn("Failed to load cached tile from {}", f.toString(), e); } finally { -// try { -// iis.close(); -// } catch (IOException e) { -// } } // Data is most likely corrupted, just destroy it. f.delete(); @@ -165,20 +139,15 @@ public class DiskCachingTileProvider implements ITileProvider { } @Override - public Image get(final TileKey key) throws ProvisionException { + public Image get(TileKey key) throws ProvisionException { Image img = lookupFromDisk(key); if (img == null) { img = provider.get(key); final Image image = img; - - // Write image to disk in the background - // TODO: use somekind of worker executor system instead of just threads. - new Thread() { - @Override - public void run() { - cacheToDisk(key, image); - } - }.start(); + if (cachePath != null) { + // Write image to disk in the background + ForkJoinPool.commonPool().submit(() -> cacheToDisk(key, image)); + } } return img; } diff --git a/org.simantics.district.maps/src/org/simantics/maps/internal/ImageUtil.java b/org.simantics.district.maps/src/org/simantics/maps/internal/ImageUtil.java new file mode 100644 index 00000000..162097b3 --- /dev/null +++ b/org.simantics.district.maps/src/org/simantics/maps/internal/ImageUtil.java @@ -0,0 +1,58 @@ +package org.simantics.maps.internal; + +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.image.BufferedImage; + +/** + * @author Tuukka Lehtonen + * @since 1.35.0 + */ +public class ImageUtil { + + public static BufferedImage createScreenCompatibleImage(int width, int height, int transparency) { + return createCompatibleImage( + GraphicsEnvironment + .getLocalGraphicsEnvironment() + .getDefaultScreenDevice() + .getDefaultConfiguration(), + width, height, transparency); + } + + public static BufferedImage createCompatibleImage(GraphicsConfiguration conf, int width, int height, int transparency) { + return conf.createCompatibleImage(width, height, transparency); + } + + public static BufferedImage toScreenCompatibleImage(BufferedImage img) { + return toCompatibleImage( + GraphicsEnvironment + .getLocalGraphicsEnvironment() + .getDefaultScreenDevice() + .getDefaultConfiguration(), + img); + } + + public static BufferedImage toCompatibleImage(GraphicsConfiguration conf, BufferedImage img) { + // TODO: add checks to prevent new image creation if the provided image is already compatible with the given GraphicsConfiguration + if (img.getColorModel().equals(conf.getColorModel())) + return img; + +// BufferedImage result = null; +// if (img.getAlphaRaster() != null) { +// result = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); +// } else { +// result = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_3BYTE_BGR); +// } + + BufferedImage result = createCompatibleImage(conf, img.getWidth(), img.getHeight(), img.getTransparency()); + Graphics2D g = result.createGraphics(); + try { + g.drawImage(img, 0, 0, null); + return result; + } finally { + g.dispose(); + } + } + +} diff --git a/org.simantics.district.maps/src/org/simantics/maps/osm/OSMTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/osm/OSMTileProvider.java index 2b29d20f..a428a2df 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/osm/OSMTileProvider.java +++ b/org.simantics.district.maps/src/org/simantics/maps/osm/OSMTileProvider.java @@ -26,6 +26,7 @@ import javax.imageio.ImageIO; import org.simantics.maps.ProvisionException; import org.simantics.maps.WebService; +import org.simantics.maps.internal.ImageUtil; import org.simantics.maps.tile.ITileProvider; import org.simantics.maps.tile.TileKey; import org.simantics.maps.wms.ServiceException; @@ -136,7 +137,7 @@ public class OSMTileProvider implements ITileProvider { if (img == null) { throw new IIOException("ImageIO returned null, unable to decode stream as image data."); } - return img; + return ImageUtil.toScreenCompatibleImage(img); } } diff --git a/org.simantics.district.maps/src/org/simantics/maps/sg/MapNode.java b/org.simantics.district.maps/src/org/simantics/maps/sg/MapNode.java index 84338bfb..952553b0 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/sg/MapNode.java +++ b/org.simantics.district.maps/src/org/simantics/maps/sg/MapNode.java @@ -25,8 +25,6 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.ImageObserver; import java.awt.image.VolatileImage; -import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -50,6 +48,8 @@ import org.simantics.maps.tile.TileCache; import org.simantics.maps.tile.TileKey; import org.simantics.scenegraph.g2d.G2DNode; import org.simantics.scenegraph.g2d.G2DRenderingHints; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @author Tuukka Lehtonen @@ -58,6 +58,8 @@ public class MapNode extends G2DNode implements ITileListener { private static final long serialVersionUID = 2490944880914577411L; + private static final Logger LOGGER = LoggerFactory.getLogger(MapNode.class); + private final double MAP_SCALE = 1; private final int MAX_TILE_LEVEL = 19; private final int TILE_PIXEL_SIZE = 256; @@ -210,14 +212,8 @@ public class MapNode extends G2DNode implements ITileListener { this.tileCache = new TileCache(job); this.tileCache.addTileListener(this); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (URISyntaxException e) { - // TODO Auto-generated catch block - e.printStackTrace(); } catch(Exception e ) { - e.printStackTrace(); + LOGGER.error("Failed to initialize MapNode", e); } } diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java index 10abef03..e028f93a 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/CompoundTileProvider.java @@ -14,6 +14,7 @@ package org.simantics.maps.tile; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; +import java.awt.Transparency; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.net.URI; @@ -21,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import org.simantics.maps.ProvisionException; +import org.simantics.maps.internal.ImageUtil; /** * @author Tuukka Lehtonen @@ -130,7 +132,7 @@ public class CompoundTileProvider implements ITileProvider { } if (result == null) { - result = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_3BYTE_BGR); + result = ImageUtil.createScreenCompatibleImage(tileSize, tileSize, Transparency.OPAQUE); g = result.createGraphics(); } diff --git a/org.simantics.district.maps/src/org/simantics/maps/tile/Shp2ImgTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/tile/Shp2ImgTileProvider.java index 0431e6fe..fb225387 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/tile/Shp2ImgTileProvider.java +++ b/org.simantics.district.maps/src/org/simantics/maps/tile/Shp2ImgTileProvider.java @@ -26,6 +26,7 @@ import java.util.List; import javax.imageio.ImageIO; import org.simantics.maps.ProvisionException; +import org.simantics.maps.internal.ImageUtil; /** * @author Tuukka Lehtonen @@ -171,7 +172,7 @@ public class Shp2ImgTileProvider implements ITileProvider { return outOfBoundsImage; final int outOfBoundsImageSize = 1; - BufferedImage image = new BufferedImage(outOfBoundsImageSize, outOfBoundsImageSize, BufferedImage.TYPE_3BYTE_BGR); + BufferedImage image = ImageUtil.createScreenCompatibleImage(outOfBoundsImageSize, outOfBoundsImageSize, BufferedImage.OPAQUE); Graphics2D g = image.createGraphics(); try { g.setColor(Color.PINK); @@ -247,13 +248,7 @@ public class Shp2ImgTileProvider implements ITileProvider { //System.out.println("img = " + img); if (img == null) throw new ProvisionException("Failed to provide image for rectangle: " + r); - - if (BufferedImage.TYPE_4BYTE_ABGR != img.getType()) { - BufferedImage img2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); - img.copyData(img2.getRaster()); - return img2; - } - return img; + return ImageUtil.toScreenCompatibleImage(img); } finally { if (tempFile.exists()) { // System.out.println("DELETING TEMP FILE: " + tempFile); diff --git a/org.simantics.district.maps/src/org/simantics/maps/wms/WMSTileProvider.java b/org.simantics.district.maps/src/org/simantics/maps/wms/WMSTileProvider.java index 60e02067..696fbcf9 100644 --- a/org.simantics.district.maps/src/org/simantics/maps/wms/WMSTileProvider.java +++ b/org.simantics.district.maps/src/org/simantics/maps/wms/WMSTileProvider.java @@ -26,6 +26,7 @@ import javax.imageio.ImageIO; import org.simantics.maps.ProvisionException; import org.simantics.maps.WebService; +import org.simantics.maps.internal.ImageUtil; import org.simantics.maps.tile.ITileProvider; import org.simantics.maps.tile.TileKey; @@ -159,7 +160,7 @@ public class WMSTileProvider implements ITileProvider { if (img == null) { throw new IIOException("ImageIO returned null, unable to decode stream as image data."); } - return img; + return ImageUtil.toScreenCompatibleImage(img); } } -- 2.47.1