diff --git a/dao/pom.xml b/dao/pom.xml
index 6f5ec41672..a5a0719ae7 100644
--- a/dao/pom.xml
+++ b/dao/pom.xml
@@ -246,12 +246,8 @@
hypersistence-utils-hibernate-63
- org.apache.xmlgraphics
- batik-transcoder
-
-
- org.apache.xmlgraphics
- batik-codec
+ com.github.weisj
+ jsvg
com.drewnoakes
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java b/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java
index 19649655a3..9903fe44da 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/util/ImageUtils.java
@@ -21,37 +21,36 @@ import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.github.weisj.jsvg.SVGDocument;
+import com.github.weisj.jsvg.attributes.ViewBox;
+import com.github.weisj.jsvg.geometry.size.FloatSize;
+import com.github.weisj.jsvg.parser.DefaultParserProvider;
+import com.github.weisj.jsvg.parser.LoaderContext;
+import com.github.weisj.jsvg.parser.SVGLoader;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.With;
import lombok.extern.slf4j.Slf4j;
-import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
-import org.apache.batik.bridge.BridgeContext;
-import org.apache.batik.bridge.DocumentLoader;
-import org.apache.batik.bridge.GVTBuilder;
-import org.apache.batik.bridge.UserAgent;
-import org.apache.batik.bridge.UserAgentAdapter;
-import org.apache.batik.gvt.GraphicsNode;
-import org.apache.batik.transcoder.TranscoderInput;
-import org.apache.batik.transcoder.TranscoderOutput;
-import org.apache.batik.transcoder.image.PNGTranscoder;
-import org.apache.batik.util.XMLResourceDescriptor;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.common.data.StringUtils;
-import org.w3c.dom.Document;
+import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
-import java.awt.Color;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
+import java.util.regex.Pattern;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
@@ -144,52 +143,32 @@ public class ImageUtils {
BufferedImage thumbnail = new BufferedImage(preview.getWidth(), preview.getHeight(), BufferedImage.TYPE_INT_ARGB);
thumbnail.getGraphics().drawImage(bufferedImage, 0, 0, preview.getWidth(), preview.getHeight(), null);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- ImageIO.write(thumbnail, "png", out);
+
+ byte[] pngThumbnail = toCompressedPngData(thumbnail);
preview.setMediaType("image/png");
- preview.setData(out.toByteArray());
- preview.setSize(preview.getData().length);
+ preview.setData(pngThumbnail);
+ preview.setSize(pngThumbnail.length);
image.setPreview(preview);
return image;
}
public static ProcessedImage processSvgImage(byte[] data, String mediaType, int thumbnailMaxDimension) throws Exception {
- SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());
- Document document = factory.createDocument(null, new ByteArrayInputStream(data));
+ var imageData = removeScadaSymbolMetadata(data);
+
+ SVGLoader loader = new SVGLoader();
+ SVGDocument svgDocument = loader.load(new ByteArrayInputStream(imageData), null, LoaderContext.builder()
+ .parserProvider(new DefaultParserProvider())
+ .build());
+
Integer width = null;
Integer height = null;
- String strWidth = document.getDocumentElement().getAttribute("width");
- String strHeight = document.getDocumentElement().getAttribute("height");
- if (StringUtils.isNotEmpty(strWidth) && StringUtils.isNotEmpty(strHeight)) {
- try {
- width = (int) Double.parseDouble(strWidth);
- height = (int) Double.parseDouble(strHeight);
- } catch (NumberFormatException ignored) {} // in case width and height are in %, mm, etc.
- }
- if (width == null || height == null) {
- String viewBox = document.getDocumentElement().getAttribute("viewBox");
- if (StringUtils.isNotEmpty(viewBox)) {
- String[] viewBoxValues = viewBox.split(" ");
- if (viewBoxValues.length > 3) {
- width = (int) Double.parseDouble(viewBoxValues[2]);
- height = (int) Double.parseDouble(viewBoxValues[3]);
- }
- }
- }
- if (width == null) {
- UserAgent agent = new UserAgentAdapter();
- DocumentLoader loader = new DocumentLoader(agent);
- BridgeContext context = new BridgeContext(agent, loader);
- context.setDynamic(true);
- GVTBuilder builder = new GVTBuilder();
- GraphicsNode root = builder.build(context, document);
- var bounds = root.getPrimitiveBounds();
- if (bounds != null) {
- width = (int) bounds.getWidth();
- height = (int) bounds.getHeight();
- }
+ if (svgDocument != null) {
+ FloatSize imageSize = svgDocument.size();
+ width = (int) imageSize.width;
+ height = (int) imageSize.height;
}
+
ProcessedImage image = new ProcessedImage();
image.setMediaType(mediaType);
image.setWidth(width == null ? 0 : width);
@@ -197,26 +176,28 @@ public class ImageUtils {
image.setData(data);
image.setSize(data.length);
- PNGTranscoder transcoder = new PNGTranscoder();
- if (image.getSize() < 10240) { // if SVG is smaller than 10kB (average 250x250 PNG preview size)
- return withPreviewAsOriginalImage(image);
+ if (imageData.length < 10240 || svgDocument == null) { // if SVG is smaller than 10kB (average 250x250 PNG preview size)
+ return withPreviewAsOriginalImage(image, imageData);
} else if (image.getSize() > 102400 && image.getWidth() != 0) { // considering SVG image detailed after 100kB
- // increasing preview dimensions
thumbnailMaxDimension = 512;
- int[] thumbnailDimensions = getThumbnailDimensions(image.getWidth(), image.getHeight(), thumbnailMaxDimension, false);
- transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) thumbnailDimensions[0]);
- transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) thumbnailDimensions[1]);
- } else {
- transcoder.addTranscodingHint(PNGTranscoder.KEY_MAX_WIDTH, (float) thumbnailMaxDimension);
- transcoder.addTranscodingHint(PNGTranscoder.KEY_MAX_HEIGHT, (float) thumbnailMaxDimension);
}
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- transcoder.transcode(new TranscoderInput(new ByteArrayInputStream(data)), new TranscoderOutput(out));
- byte[] pngThumbnail = out.toByteArray();
-
ProcessedImage preview = new ProcessedImage();
- preview.setWidth(thumbnailMaxDimension);
- preview.setHeight(thumbnailMaxDimension);
+ int[] thumbnailDimensions = getThumbnailDimensions(image.getWidth(), image.getHeight(), thumbnailMaxDimension, false);
+ preview.setWidth(thumbnailDimensions[0]);
+ preview.setHeight(thumbnailDimensions[1]);
+
+ BufferedImage thumbnail = new BufferedImage(preview.getWidth(), preview.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphics = thumbnail.createGraphics();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ svgDocument.render((Component)null,graphics, new ViewBox(0, 0, preview.getWidth(), preview.getHeight()));
+ graphics.dispose();
+
+ byte[] pngThumbnail = toCompressedPngData(thumbnail);
+
+ if (imageData.length < pngThumbnail.length) { // set preview as original SVG if PNG thumbnail size is greater
+ return withPreviewAsOriginalImage(image, imageData);
+ }
+
preview.setMediaType("image/png");
preview.setData(pngThumbnail);
preview.setSize(pngThumbnail.length);
@@ -224,6 +205,27 @@ public class ImageUtils {
return image;
}
+ public static byte[] toCompressedPngData(BufferedImage image) throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(image);
+ ImageWriter writer = ImageIO.getImageWriters(type, "png").next();
+ ImageWriteParam param = writer.getDefaultWriteParam();
+ if (param.canWriteCompressed()) {
+ param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+ param.setCompressionQuality(0.0f);
+ }
+ var output = ImageIO.createImageOutputStream(out);
+ writer.setOutput(output);
+ try {
+ writer.write(null, new IIOImage(image, null, null), param);
+ } finally {
+ writer.dispose();
+ output.flush();
+ }
+ return out.toByteArray();
+ }
+
+
private static ProcessedImage previewAsOriginalImage(byte[] data, String mediaType) {
ProcessedImage image = new ProcessedImage();
image.setMediaType(mediaType);
@@ -235,21 +237,34 @@ public class ImageUtils {
}
public static ProcessedImage withPreviewAsOriginalImage(ProcessedImage originalImage) {
- originalImage.setPreview(originalImage.withData(null));
+ return withPreviewAsOriginalImage(originalImage, null);
+ }
+
+ public static ProcessedImage withPreviewAsOriginalImage(ProcessedImage originalImage, byte[] previewData) {
+ originalImage.setPreview(originalImage.withData(previewData));
+ if (previewData != null) {
+ originalImage.getPreview().setSize(previewData.length);
+ }
return originalImage;
}
public static ScadaSymbolMetadataInfo processScadaSymbolMetadata(String fileName, byte[] data) throws Exception {
- SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());
- Document document = factory.createDocument(null, new ByteArrayInputStream(data));
- var metaElements = document.getElementsByTagName("tb:metadata");
JsonNode metaData = null;
- if (metaElements.getLength() > 0) {
- metaData = JacksonUtil.toJsonNode(metaElements.item(0).getTextContent());
+ String contents = new String(data, StandardCharsets.UTF_8);
+ var matcher = Pattern.compile("(?s)]*><\\/tb:metadata>").matcher(contents);
+ if (matcher.find()) {
+ var metadataContent = matcher.group(1);
+ metaData = JacksonUtil.toJsonNode(metadataContent);
}
return new ScadaSymbolMetadataInfo(fileName, metaData);
}
+ public static byte[] removeScadaSymbolMetadata(byte[] data) {
+ String contents = new String(data, StandardCharsets.UTF_8);
+ contents = contents.replaceFirst("(?s)]*>.*<\\/tb:metadata>", "");
+ return contents.getBytes(StandardCharsets.UTF_8);
+ }
+
private static int[] getThumbnailDimensions(int originalWidth, int originalHeight, int maxDimension, boolean originalIfSmaller) {
if (originalWidth <= maxDimension && originalHeight <= maxDimension && originalIfSmaller) {
return new int[]{originalWidth, originalHeight};
diff --git a/pom.xml b/pom.xml
index 6d0fa6f090..4ea6491eb2 100755
--- a/pom.xml
+++ b/pom.xml
@@ -163,7 +163,7 @@
1.39.0
6.6.0
1.35.0
- 1.17
+ 1.6.1
2.19.0
9.2.0
@@ -2262,20 +2262,9 @@
${google-oauth-client.version}
- org.apache.xmlgraphics
- batik-transcoder
- ${apache-xmlgraphics.version}
-
-
- commons-logging
- commons-logging
-
-
-
-
- org.apache.xmlgraphics
- batik-codec
- ${apache-xmlgraphics.version}
+ com.github.weisj
+ jsvg
+ ${weisj-jsvg.version}
com.drewnoakes