Skip to content

Commit

Permalink
Merge branch 'feature/improve-images-load' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
paulosousadias committed Jan 18, 2025
2 parents 231dc7b + de08d2e commit e7716b4
Show file tree
Hide file tree
Showing 6 changed files with 481 additions and 11 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,8 @@ project(':core') {

implementation 'com.google.zxing:javase:3.2.1' //3.4.0

implementation 'com.kitfox.svg:svg-salamander:1.0'

// Java 11 related
implementation 'javax.annotation:javax.annotation-api:1.3.2'

Expand Down
30 changes: 19 additions & 11 deletions src/java/pt/lsts/neptus/util/FileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -1042,9 +1043,9 @@ public static String getResourceAsFile(String name) {
return null;

try {
return StreamUtil.copyStreamToTempFile(inStream).getPath();
return Objects.requireNonNull(StreamUtil.copyStreamToTempFile(inStream)).getPath();
}
catch (RuntimeException e) {
catch (Exception e) {
return null;
}
}
Expand All @@ -1058,15 +1059,22 @@ public static String getResourceAsFile(String name) {
*/
public static InputStream getResourceAsStream(String name) {
InputStream inStream = FileUtil.class.getResourceAsStream(name.replace('\\', '/'));
if (inStream == null) {
Class<?> clazz = getCallerClass();
if (clazz == null)
return null;
inStream = clazz.getResourceAsStream(name.replace('\\', '/'));
if (inStream == null)
return null;
}

if (inStream != null)
return inStream;

inStream = FileUtil.class.getResourceAsStream("/" + name.replace('\\', '/'));
if (inStream != null)
return inStream;

Class<?> clazz = getCallerClass();
if (clazz == null)
return null;

inStream = clazz.getResourceAsStream(name.replace('\\', '/'));
if (inStream != null)
return inStream;

inStream = clazz.getResourceAsStream("/" + name.replace('\\', '/'));
return inStream;
}

Expand Down
218 changes: 218 additions & 0 deletions src/java/pt/lsts/neptus/util/ImageSvgUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* Copyright (c) 2004-2025 Universidade do Porto - Faculdade de Engenharia
* Laboratório de Sistemas e Tecnologia Subaquática (LSTS)
* All rights reserved.
* Rua Dr. Roberto Frias s/n, sala I203, 4200-465 Porto, Portugal
*
* This file is part of Neptus, Command and Control Framework.
*
* Commercial Licence Usage
* Licencees holding valid commercial Neptus licences may use this file
* in accordance with the commercial licence agreement provided with the
* Software or, alternatively, in accordance with the terms contained in a
* written agreement between you and Universidade do Porto. For licensing
* terms, conditions, and further information contact lsts@fe.up.pt.
*
* Modified European Union Public Licence - EUPL v.1.1 Usage
* Alternatively, this file may be used under the terms of the Modified EUPL,
* Version 1.1 only (the "Licence"), appearing in the file LICENSE.md
* included in the packaging of this file. You may not use this work
* except in compliance with the Licence. Unless required by applicable
* law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the Licence for the specific
* language governing permissions and limitations at
* https://github.com/LSTS/neptus/blob/develop/LICENSE.md
* and http://ec.europa.eu/idabc/eupl.html.
*
* For more information please see <http://lsts.fe.up.pt/neptus>.
*
* Author: pdias
* 18/Jan/2025
*/
package pt.lsts.neptus.util;

import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
import org.apache.batik.transcoder.SVGAbstractTranscoder;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.print.PrintTranscoder;
import org.apache.batik.util.XMLResourceDescriptor;
import org.apache.commons.lang3.tuple.Pair;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import pt.lsts.neptus.NeptusLog;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.function.BiFunction;

public class ImageSvgUtils {
private ImageSvgUtils() {
}

/**
* Gets the SVG image from the path.
* @param path the path to the SVG image
* @return the SVG image
*/
public static Document getSvgImage(String path) {
try {
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
String data = StreamUtil.copyStreamToString(FileUtil.getResourceAsStream(path));
Document wDoc;
try {
wDoc = f.createDocument(null, new StringReader((String) data));
}
catch (IOException e) {
// Trying if missing xmlns svg
data = data.replace("<svg", "<svg xmlns=\"http://www.w3.org/2000/svg\"");
wDoc = f.createDocument(null, new StringReader((String) data));
NeptusLog.pub().warn("SVG file was missing xmlns=\"http://www.w3.org/2000/svg\". Fixed it.");
}
wDoc = SvgUtil.cleanInkscapeSVG(wDoc);
return wDoc;
}
catch (NoClassDefFoundError e) {
System.out.print("Batik missing in the classpath. Proceeding without world map.");
return null;
}
catch (Exception | Error e) {
NeptusLog.pub().error(e);
return null;
}
}

/**
* Gets the SVG image from the file.
*
* @param file the file to get the SVG image
* @return the SVG image
*/
public static Document getSvgImage(File file) {
return getSvgImage(file.getAbsolutePath());
}

/**
* Gets the SVG image as a buffered image.
* @param path the path to the SVG image
* @param graphicModifier a function to modify the graphics2D before painting the SVG image
* @return the buffered image
*/
public static BufferedImage getSvgImageAsBufferedImage(String path, BiFunction<Graphics2D, Document, Void> graphicModifier) {
Document svgDoc = getSvgImage(path);
if (svgDoc == null)
return null;

double width = SvgUtil.getWidth(svgDoc);
double height = SvgUtil.getHeight(svgDoc);
BufferedImage bufferedImage = ImageUtils.createCompatibleImage((int) Math.floor(width), (int) Math.floor(height),
Transparency.TRANSLUCENT);
try {
Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (graphicModifier != null)
graphicModifier.apply(graphics2D, svgDoc);
paintSvgImageToBufferedImage(graphics2D, svgDoc);
graphics2D.dispose();
}
catch (Exception e) {
NeptusLog.pub().error(e);
}
return bufferedImage;
}

/**
* Paints the SVG image to the buffered image.
* @param graphics2D the graphics2D to paint the SVG image
* @param svgDoc the SVG document to paint
*/
public static void paintSvgImageToBufferedImage(Graphics2D graphics2D, Document svgDoc) {
if (svgDoc == null || graphics2D == null)
return;
try {
double width = SvgUtil.getWidth(svgDoc);
double height = SvgUtil.getHeight(svgDoc);
PrintTranscoder prm = new PrintTranscoder();
prm.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, Double.valueOf(width).floatValue());
prm.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, Double.valueOf(height).floatValue());
TranscoderInput ti = new TranscoderInput(svgDoc);
prm.transcode(ti, null);

Paper paper = new Paper();
paper.setSize(width, height);
paper.setImageableArea(0, 0, width, height);
PageFormat page = new PageFormat();
page.setPaper(paper);

prm.print(graphics2D, page, 0);
}
catch (Exception e) {
NeptusLog.pub().error(e);
}
}

/**
* Paints the SVG image to the buffered image with the scale of the buffered image.
*
* @param bufferedImage the buffered image to paint the SVG image
* @param scaleToBufferedImageSize if true the SVG image will be scaled to the buffered image size
* @param graphicModifier a function to modify the graphics2D before painting the SVG image (non-cumulative).
* The first parameter is the graphics2D and the second is the SVG element index, starts at 0.
* @param svgDoc the SVG document(s) to paint
*/
public static void paintSvgImageToBufferedImage(BufferedImage bufferedImage, boolean scaleToBufferedImageSize,
BiFunction<Graphics2D, Integer, Void> graphicModifier,
Document... svgDoc) {
if (svgDoc == null || svgDoc.length == 0 || bufferedImage == null)
return;

Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
for (int i = 0; i < svgDoc.length; i++) {
Document d = svgDoc[i];
double svgWidth = SvgUtil.getWidth(d);
double svgHeight = SvgUtil.getHeight(d);
Pair<Double, Double> newSized = ImageUtils.getScaledSize(svgWidth, svgHeight, width, height);
Graphics2D gScaled = (Graphics2D) graphics2D.create();
if (scaleToBufferedImageSize) {
gScaled.scale(newSized.getLeft() / svgWidth, newSized.getRight() / svgHeight);
}
if (graphicModifier != null)
graphicModifier.apply(gScaled, i);
paintSvgImageToBufferedImage(gScaled, d);
gScaled.dispose();
}
graphics2D.dispose();
}

/**
* Paints the SVG image to the buffered image with the scale of the buffered image.
* @param element the SVG element to paint
* @param attribute the attribute to change
* @param value the value to set
* @param inlineOrStyleType if true the attribute is inline, otherwise is style
*/
public static void fillSvgElementAttribute(Element element, String attribute, String value, boolean inlineOrStyleType) {
int attribType = inlineOrStyleType ? 0 : 1; // FIXME: 0 is inline, 1 is style
try {
element.setAttribute(attribute, value);
}
catch (Exception e) {
NeptusLog.pub().error(e);
}
}
}
Loading

0 comments on commit e7716b4

Please # to comment.