Commit 2547363a authored by Nico Mack's avatar Nico Mack

Added VectorImage corona. Improvements on Point class.

parent 7d522510
......@@ -135,3 +135,4 @@ MARKER_NODE=marker
MARKERS_NODE=markers
MARKER_BUILDER_NAMESPACE=lu.list.itis.dkd.tui.marker.builder
POINTING_OFFSET_NODE=pointingOffset
......@@ -118,9 +118,9 @@
<version>4.1</version>
</dependency>
<dependency>
<groupId>com.kitfox.svg</groupId>
<groupId>com.metsci.ext.com.kitfox.svg</groupId>
<artifactId>svg-salamander</artifactId>
<version>1.0</version>
<version>0.1.19</version>
</dependency>
</dependencies>
......
......@@ -155,7 +155,7 @@ public class BootstrappingUtils {
String value;
String contentAsString = Strings.nullToEmpty(getContent(rootElement, childName, variables));
if (contentAsString.length() > 0) {
value = contentAsString;
value = contentAsString.trim();
} else {
if (optional)
value = defaultValue;
......@@ -213,7 +213,7 @@ public class BootstrappingUtils {
String value;
String attributeAsString = Strings.nullToEmpty(rootElement.getAttributeValue(attributeName));
if (attributeAsString.length() > 0) {
value = attributeAsString;
value = attributeAsString.trim();
} else {
if (optional)
value = defaultValue;
......
......@@ -24,9 +24,14 @@ import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.exception.BuildException;
import lu.list.itis.dkd.tui.utility.Calibration;
import lu.list.itis.dkd.tui.utility.CoordinateState;
import lu.list.itis.dkd.tui.utility.Externalization;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.ScreenCoordinates;
import lu.list.itis.dkd.tui.widget.corona.ShapeFactory;
import com.google.common.base.Strings;
import org.jdom2.Element;
import java.awt.Shape;
......@@ -87,28 +92,46 @@ public class ShapeBootstrapper {
int triangleSize = BootstrappingUtils.getContentAsInteger(shapeNode, Externalization.TRIANGLE_SIZE_NODE, BootstrappingUtils.OPTIONAL, Calibration.tableToScreenY(Calibration.getSquareSize()), context);
int cornerRadius = BootstrappingUtils.getContentAsInteger(shapeNode, Externalization.CORNER_RADIUS_NODE, BootstrappingUtils.OPTIONAL, Calibration.tableToScreenY(Calibration.getCornerRadius()), context);
Point dimensions = new Point(ScreenCoordinates.class);
CoordinateState coordinates;
String state = BootstrappingUtils.getContentAsString(shapeNode, Externalization.STATE_NODE, BootstrappingUtils.OPTIONAL, null, context);
if (!Strings.isNullOrEmpty(state)) {
coordinates = CoordinateStateBootstrapper.getCoordinateState(shapeNode, dimensions, context, callback);
} else {
coordinates = new ScreenCoordinates(dimensions, 0, 0);
}
switch (type.trim().toLowerCase()) {
case "circle": //$NON-NLS-1$
return ShapeFactory.buildCircle(circleSize);
dimensions.setLocation(circleSize, circleSize);
dimensions.toCoordinates(ScreenCoordinates.class);
return ShapeFactory.buildCircle((int) dimensions.getX());
case "square": //$NON-NLS-1$
return ShapeFactory.buildSquare(squareSize);
dimensions.setLocation(squareSize, squareSize);
dimensions.toCoordinates(ScreenCoordinates.class);
return ShapeFactory.buildSquare((int) dimensions.getX());
case "triangle": //$NON-NLS-1$
return ShapeFactory.buildTriangle(triangleSize);
dimensions.setLocation(triangleSize, triangleSize);
dimensions.toCoordinates(ScreenCoordinates.class);
return ShapeFactory.buildTriangle((int) dimensions.getX());
case "rectangle": //$NON-NLS-1$
return ShapeFactory.buildRectangle(width, height);
dimensions.setLocation(width, height);
dimensions.toCoordinates(ScreenCoordinates.class);
return ShapeFactory.buildRectangle((int) dimensions.getX(), (int) dimensions.getY());
case "roundedsquare": //$NON-NLS-1$
return ShapeFactory.buildRoundedSquare(squareSize, cornerRadius);
dimensions.setLocation(squareSize, squareSize);
dimensions.toCoordinates(ScreenCoordinates.class);
Point radius = new Point(cornerRadius, cornerRadius, 0f, coordinates.getClass());
radius.toCoordinates(ScreenCoordinates.class);
return ShapeFactory.buildRoundedSquare((int) dimensions.getX(), (int) radius.getX());
case "roundedrectangle": //$NON-NLS-1$
return ShapeFactory.buildRoundedRectangle(width, height, cornerRadius);
dimensions.setLocation(width, height);
dimensions.toCoordinates(ScreenCoordinates.class);
return ShapeFactory.buildRoundedRectangle((int) dimensions.getX(), (int) dimensions.getY(), cornerRadius);
default:
throw new BuildException("Don't know how to build a shape of type: " + type); //$NON-NLS-1$
}
}
}
\ No newline at end of file
......@@ -178,6 +178,7 @@ public class Externalization extends NLS {
public static String MARKERS_NODE;
public static String MARKER_BUILDER_NAMESPACE;
public static String POINTING_OFFSET_NODE;
static {
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved.
*
* This file is part of TULIP.
*
* TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation, version 3 of the
* License.
*
* TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.utility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
/**
* @author mack
* @since [major].[minor]
* @version [major].[minor].[micro]
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class ImageUtilities {
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final Logger LOGGER = LoggerFactory.getLogger(ImageUtilities.class.getSimpleName());
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* The trimImage method crops transparent pixels from the borders of the specified image and
* returns the smallest possible non-transparent image. Algorithm was taken from
* http://stackoverflow.com/questions/3224561/crop-image-to-smallest-size-by-removing-transparent-pixels-in-java
*
* @param image
* with transparent pixels on its border(s)
* @return the smallest image containing the non-transparent pixels of the image.
*/
public static BufferedImage trimImage(BufferedImage image) {
WritableRaster raster = image.getAlphaRaster();
int width = raster.getWidth();
int height = raster.getHeight();
int left = 0;
int top = 0;
int right = width - 1;
int bottom = height - 1;
int minRight = width - 1;
int minBottom = height - 1;
top : for (; top < bottom; top++) {
for (int x = 0; x < width; x++) {
if (raster.getSample(x, top, 0) != 0) {
minRight = x;
minBottom = top;
break top;
}
}
}
left : for (; left < minRight; left++) {
for (int y = height - 1; y > top; y--) {
if (raster.getSample(left, y, 0) != 0) {
minBottom = y;
break left;
}
}
}
bottom : for (; bottom > minBottom; bottom--) {
for (int x = width - 1; x >= left; x--) {
if (raster.getSample(x, bottom, 0) != 0) {
minRight = x;
break bottom;
}
}
}
right : for (; right > minRight; right--) {
for (int y = bottom; y >= top; y--) {
if (raster.getSample(right, y, 0) != 0) {
break right;
}
}
}
return image.getSubimage(left, top, right - left + 1, bottom - top + 1);
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of Class *
// ***************************************************************************
// ---------------------------------------------------------------------------
}
......@@ -433,6 +433,7 @@ public class Point extends Float implements KdComparator<Point> {
return clone();
}
// switch (state.getClass().getSimpleName()) {
// case "TableCoordinates": //$NON-NLS-1$
// point.toTableCoordinates();
......@@ -457,6 +458,20 @@ public class Point extends Float implements KdComparator<Point> {
return clone;
}
/**
* @param xscale
* @param yscale
* @return
*/
public Point scale(double xscale, double yscale) {
Point clone = clone();
float scaledX = (float) (this.x * xscale);
float scaledY = (float) (this.y * yscale);
clone.setLocation(scaledX, scaledY);
return clone;
}
/**
* Method for creating a new {@link Point} from this point by subtracting coordinates and angle
* of the provided point.
......
/**
* Copyright Luxembourg Institute of Science and Technology, 2017. All rights reserved.
*
* This file is part of TULIP.
*
* TULIP is free software: you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation, version 3 of the
* License.
*
* TULIP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with TULIP. If
* not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
*/
package lu.list.itis.dkd.tui.utility;
/**
* @author mack
* @since [major].[minor]
* @version [major].[minor].[micro]
*/
// ***************************************************************************
// * Class Definition and Members *
// ***************************************************************************
public class PolarCoordinateHelper {
// ***************************************************************************
// * Constants *
// ***************************************************************************
private static final double PI_HALF = Math.PI / 2;
private static final double THREE_PI_HALF = 3 * Math.PI / 2;
private static final double TWO_PI = 2 * Math.PI;
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Constructor(s) *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Primitives *
// ***************************************************************************
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ***************************************************************************
// * Class Body *
// ***************************************************************************
// ---------------------------------------------------------------------------
/**
* @param angle
* @return
*/
public static final int getQuadrant(double angle) {
angle = angle % TWO_PI;
// Nudge angle into next quadrant for angles π/2, π, 3π/2 and 2π
// angle += ((angle == PI_HALF) || (angle == Math.PI) || (angle == THREE_PI_HALF) || (angle
// == TWO_PI)) ? 1e-10 : 0;
int cosSign = (Math.cos(angle) < 0) ? 1 : 0;
int sinSign = (Math.sin(angle) < 0) ? 1 : 0;
int quadrant = (cosSign ^ sinSign) + sinSign + sinSign;
return quadrant;
}
// ---------------------------------------------------------------------------
/**
* @param pole
* @param coordinate
* @return
*/
public static final double getRadius(Point pole, Point coordinate) {
double lengthX = coordinate.x - pole.x;
double lengthY = coordinate.y - pole.y;
double radius = Math.sqrt(lengthX * lengthX + lengthY * lengthY);
return radius;
}
// ---------------------------------------------------------------------------
/**
* @param angle
* @param width
* @param height
* @return
*/
public static final double getEllipticRadius(double angle, double width, double height) {
double lengthX = width * Math.cos(angle);
double lengthY = height * Math.sin(angle);
double radius = Math.sqrt(lengthX * lengthX + lengthY * lengthY);
return radius;
}
// ---------------------------------------------------------------------------
/**
* @param origin
* @param offsetX
* @param offsetY
* @return
*/
public static final Point translate(Point origin, double offsetX, double offsetY) {
double translatedX = origin.x + (offsetX * Math.cos(origin.getAngle()));
double translatedY = origin.y + (offsetY * Math.sin(origin.getAngle()));
return new Point((float) translatedX, (float) translatedY, origin.getAngle(), origin.getState().getClass());
}
// ---------------------------------------------------------------------------
/**
* converts the specified points to a polar representation. The method computes the euclidean
* distance between both points and the comprised angle, and returns a new point representing
* the polar distance between both points.Calculations are done in origins coordinate system,
* i.e. target will be converted prior to calculation.
*
* @param origin
* carthesian coordinates of first point
* @param target
* carthesian coordinates of second point
* @return a new point, with x and y value expressing the euclidean distance.
*/
// ---------------------------------------------------------------------------
public static final Point carthesianToPolar(Point origin, Point target) {
Point clone = target.clone();
clone.toCoordinates(origin.getState().getClass());
double x = clone.x - origin.x;
double y = clone.y - origin.y;
float distance = (float) Math.sqrt((x * x) + (y * y));
float angle = (float) Math.atan2(y, x);
return new Point(distance, distance, angle, origin.getState().getClass());
}
// ---------------------------------------------------------------------------
/**
* given a point expressed in carthesian coordinates and one point expressed in polar
* coordinates, the method computes a new carthesian point corresponding to the sum of both
* input points. Calculations are done in origins coordinate system, i.e. target will be
* converted prior to calculation.
*
* @param origin
* carthesian coordinates of first point
* @param polar
* polar coordinates
* @return a new point, with x and y value expressing the euclidean distance.
*/
// ---------------------------------------------------------------------------
public static final Point polarToCarthesian(Point origin, Point polar) {
Point clone = polar.clone();
clone.toCoordinates(origin.getState().getClass());
double x = origin.x + clone.x * Math.cos(clone.getAngle());
double y = origin.y + clone.y * Math.sin(clone.getAngle());
return new Point((float) x, (float) y, 0f, origin.getState().getClass());
}
// ---------------------------------------------------------------------------
// ***************************************************************************
// * End of Class *
// ***************************************************************************
// ---------------------------------------------------------------------------
}
\ No newline at end of file
......@@ -23,6 +23,7 @@ package lu.list.itis.dkd.tui.widget.corona;
import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.PolarCoordinateHelper;
import lu.list.itis.dkd.tui.utility.ScreenCoordinates;
import lu.list.itis.dkd.tui.widget.corona.builder.CoronaBuilder;
......@@ -116,6 +117,76 @@ public abstract class Corona implements Comparable<Corona> {
*/
}
/**
* computes the euclidian distance of the initial translation with respect to the corona's
* centre
*
* @return a point, with x and y values set to the euclidian distance (expressed in
* ScreenCoordinates) from corona centre. The angle property will be set to the offset
* angle resulting from the initial translation.
*/
protected Point getDistanceFromCentre() {
centre.toCoordinates(ScreenCoordinates.class);
if (initialTranslation != null) {
initialTranslation.toCoordinates(ScreenCoordinates.class);
return PolarCoordinateHelper.carthesianToPolar(centre, centre.add(initialTranslation));
}
return new Point(ScreenCoordinates.class);
}
protected Point getDrawingPoint(Point assetCentre) {
Point drawingPoint;
centre.toCoordinates(ScreenCoordinates.class);
if (initialTranslation != null) {
initialTranslation.toCoordinates(ScreenCoordinates.class);
} else {
initialTranslation = new Point();
}
drawingPoint = centre.add(initialTranslation);
if (assetCentre != null) {
assetCentre.toCoordinates(ScreenCoordinates.class);
drawingPoint = drawingPoint.subtract(assetCentre, false);
}
return drawingPoint;
}
protected AffineTransform getTransform(Point assetCentre) {
Point drawingPoint = this.getDrawingPoint(assetCentre);
AffineTransform rotation = new AffineTransform();
rotation.rotate(rotateWithHandle ? drawingPoint.getAngle() : initialTranslation != null ? initialTranslation.getAngle() : 0, centre.getX(), centre.getY());
rotation.translate(drawingPoint.x, drawingPoint.y);
if (spinOnCoronaCentre) {
rotation.rotate(rotateWithHandle ? -drawingPoint.getAngle() : 0);
if (assetCentre != null) {
rotation.translate(-assetCentre.x, -assetCentre.y);
}
}
rotation.transform(new Point(), drawingPoint);
if (Double.compare(initialRotation, 0d) != 0) {
// AffineTransform transformation = new AffineTransform();
if (spinOnCoronaCentre && assetCentre != null) {
drawingPoint.add(assetCentre);
}
rotation.rotate(initialRotation, drawingPoint.getX(), drawingPoint.getY());
}
return rotation;
}
/**
* Abstract method specifying the signature of a paint method.
*
......
......@@ -23,11 +23,13 @@ package lu.list.itis.dkd.tui.widget.corona;
import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.utility.Point;
import lu.list.itis.dkd.tui.utility.ScreenCoordinates;
import lu.list.itis.dkd.tui.widget.corona.builder.ImageBuilder;
import lu.list.itis.dkd.tui.widget.corona.builder.VectorImageBuilder;
import com.kitfox.svg.app.beans.SVGIcon;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
......@@ -45,6 +47,7 @@ public class VectorImage extends Corona {
private SVGIcon image;
private int width;
private int height;
private Point imageCentre;
/**
* Constructor building all fields and calling the implicit super constructor using a
......@@ -58,6 +61,12 @@ public class VectorImage extends Corona {
image = builder.image;
width = builder.width;
height = builder.height;
if (image != null) {
Dimension imageSize = image.getPreferredSize();
imageCentre = new Point((float) imageSize.getWidth() / 2, (float) imageSize.getHeight() / 2, 0f, ScreenCoordinates.class);
}
}
/**
......@@ -73,33 +82,39 @@ public class VectorImage extends Corona {
return;
}
centre.toScreenCoordinates();
if (initialTranslation != null) {
initialTranslation.toScreenCoordinates();
} else {
initialTranslation = new Point();
}
Graphics2D localCanvas = (Graphics2D) canvas.create();
Point drawAt = centre.add(initialTranslation);
// centre.toCoordinates(ScreenCoordinates.class);
//
//
// Point drawAt = centre.add(initialTranslation).subtract(imageCentre, false);
//
// AffineTransform rotation = new AffineTransform();
// rotation.rotate(rotateWithHandle ? drawAt.getAngle() : initialTranslation != null ?
// initialTranslation.getAngle() : 0, centre.getX(), centre.getY());
// rotation.translate(drawAt.x, drawAt.y);
//
// if (spinOnCoronaCentre) {
// rotation.rotate(rotateWithHandle ? -drawAt.getAngle() : 0);
// rotation.translate(-imageCentre.getX(), -imageCentre.getY());
// }
AffineTransform rotation = new AffineTransform();
rotation.rotate(rotateWithHandle ? drawAt.getAngle() : initialTranslation != null ? initialTranslation.getAngle() : 0, centre.getX(), centre.getY());
rotation.translate(drawAt.x, drawAt.y);
if (spinOnCoronaCentre) {
rotation.rotate(rotateWithHandle ? -drawAt.getAngle() : 0);
rotation.translate(-image.getIconWidth() / 2, -image.getIconHeight() / 2);
}
// rotation.transform(new Point(), drawAt);
rotation.transform(new Point(), drawAt);
// Point drawingPoint = this.getDrawingPoint(imageCentre);
AffineTransform rotation = this.getTransform(imageCentre);
if (Double.compare(initialRotation, 0d) != 0) {
AffineTransform transformation = new AffineTransform();
transformation.rotate(initialRotation, spinOnCoronaCentre ? drawAt.getX() + image.getIconWidth() / 2 : drawAt.getX(), spinOnCoronaCentre ? drawAt.getY() + image.getIconHeight() / 2 : drawAt.getY());
canvas.setTransform(transformation);
}
image.paintIcon(null, canvas, (int) drawAt.x, (int) drawAt.y);
canvas.setTransform(new AffineTransform());
// if (Double.compare(initialRotation, 0d) != 0) {
// AffineTransform transformation = new AffineTransform();
// transformation.rotate(initialRotation, spinOnCoronaCentre ? drawAt.getX() +
// imageCentre.getX() : drawAt.getX(), spinOnCoronaCentre ? drawAt.getY() +
// imageCentre.getY() : drawAt.getY());
// }
localCanvas.setTransform(rotation);
image.paintIcon(null, localCanvas, 0, 0);
localCanvas.dispose();
}
}
\ No newline at end of file
......@@ -22,6 +22,7 @@ package lu.list.itis.dkd.tui.widget.corona.builder;
import lu.list.itis.dkd.dbc.annotation.NonNullByDefault;
import lu.list.itis.dkd.dbc.annotation.Nullable;
import lu.list.itis.dkd.tui.TangibleApplication;
import lu.list.itis.dkd.tui.bootstrapping.BootstrapCallback;
import lu.list.itis.dkd.tui.bootstrapping.BootstrapContext;
import lu.list.itis.dkd.tui.bootstrapping.BootstrappingUtils;
......@@ -32,6 +33,7 @@ import lu.list.itis.dkd.tui.widget.corona.Image;
import lu.list.itis.dkd.tui.widget.corona.VectorImage;
import com.kitfox.svg.SVGCache;
import com.kitfox.svg.SVGUniverse;
import com.kitfox.svg.app.beans.SVGIcon;
import org.jdom2.Element;
......@@ -114,20 +116,25 @@ public abstract class BaseVectorImageBuilder<B extends BaseVectorImageBuilder<B>
private void buildFromBootstrap(@Nullable Element rootElement, BootstrapContext context, BootstrapCallback callback) throws BuildException {
String imagePath = BootstrappingUtils.getContentAsString(rootElement, Externalization.IMAGE_ELEMENT, BootstrappingUtils.MANDATORY, null, context);
image = loadSVGResource(imagePath);
width = BootstrappingUtils.getContentAsInteger(rootElement, Externalization.WIDTH_NODE, BootstrappingUtils.OPTIONAL, -1, context);
height = BootstrappingUtils.getContentAsInteger(rootElement, Externalization.HEIGHT_NODE, BootstrappingUtils.OPTIONAL, -1, context);
if ((width >= 0) && (height >= 0)) {
image.setPreferredSize(new Dimension(width, height));
Element imageElement = rootElement.getChild(Externalization.IMAGE_ELEMENT);