From 3e4df8b5c42d92f5086e161f981b342ad257408e Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Wed, 16 Dec 2015 08:24:27 +0100 Subject: [PATCH 1/6] svg import improved --- .../flash/exporters/commonshape/Matrix.java | 44 ++- .../exporters/commonshape/SVGExporter.java | 4 +- .../morphshape/SVGMorphShapeExporter.java | 2 +- .../exporters/shape/SVGShapeExporter.java | 4 +- .../flash/importers/ShapeImporter.java | 363 ++++++++++++++---- .../shaperecords/StraightEdgeRecord.java | 12 + 6 files changed, 344 insertions(+), 85 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java index d28cf2d87..82e6ea91e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java @@ -16,6 +16,7 @@ */ package com.jpexs.decompiler.flash.exporters.commonshape; +import com.jpexs.decompiler.flash.importers.SvgPathReader; import com.jpexs.decompiler.flash.types.MATRIX; import java.awt.geom.AffineTransform; @@ -229,7 +230,7 @@ public final class Matrix implements Cloneable { return transform; } - public String getTransformationString(double translateDivisor, double unitDivisor) { + public String getSvgTransformationString(double translateDivisor, double unitDivisor) { double translateX = roundPixels400(this.translateX / translateDivisor); double translateY = roundPixels400(this.translateY / translateDivisor); double rotateSkew0 = roundPixels400(this.rotateSkew0 / unitDivisor); @@ -240,6 +241,47 @@ public final class Matrix implements Cloneable { + rotateSkew1 + ", " + scaleY + ", " + translateX + ", " + translateY + ")"; } + public static Matrix parseSvgMatrix(String matrixStr, double translateDivisor, double unitDivisor) { + matrixStr = matrixStr.trim(); + if (matrixStr.startsWith("matrix")) { + matrixStr = matrixStr.substring(6).trim(); + if (matrixStr.startsWith("(") && matrixStr.endsWith(")")) { + matrixStr = matrixStr.substring(1, matrixStr.length() - 1); + SvgPathReader reader = new SvgPathReader(matrixStr); + double scaleX = reader.readDouble() * unitDivisor; + double rotateSkew0 = reader.readDouble() * unitDivisor; + double rotateSkew1 = reader.readDouble() * unitDivisor; + double scaleY = reader.readDouble() * unitDivisor; + double translateX = reader.readDouble() * translateDivisor; + double translateY = reader.readDouble() * translateDivisor; + Matrix result = new Matrix(); + result.translateX = translateX; + result.translateY = translateY; + result.rotateSkew0 = rotateSkew0; + result.rotateSkew1 = rotateSkew1; + result.scaleX = scaleX; + result.scaleY = scaleY; + return result; + } + } else if (matrixStr.startsWith("translate")) { + matrixStr = matrixStr.substring(9).trim(); + if (matrixStr.startsWith("(") && matrixStr.endsWith(")")) { + matrixStr = matrixStr.substring(1, matrixStr.length() - 1); + SvgPathReader reader = new SvgPathReader(matrixStr); + double translateX = reader.readDouble() * translateDivisor; + double translateY = reader.readDouble() * translateDivisor; + Matrix result = new Matrix(); + result.translateX = translateX; + result.translateY = translateY; + result.scaleX = unitDivisor; + result.scaleY = unitDivisor; + return result; + } + } + + return null; + } + private double roundPixels400(double pixels) { return Math.round(pixels * 10000) / 10000.0; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java index c4003dd20..963942f1e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java @@ -150,7 +150,7 @@ public class SVGExporter { public final Element createSubGroup(Matrix transform, String id) { Element group = createSubGroup(id, "g"); - group.setAttribute("transform", transform.getTransformationString(SWF.unitDivisor, 1)); + group.setAttribute("transform", transform.getSvgTransformationString(SWF.unitDivisor, 1)); return group; } @@ -210,7 +210,7 @@ public class SVGExporter { public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName) { Element image = _svg.createElement("use"); if (transform != null) { - image.setAttribute("transform", transform.getTransformationString(SWF.unitDivisor, 1)); + image.setAttribute("transform", transform.getSvgTransformationString(SWF.unitDivisor, 1)); image.setAttribute("width", Double.toString(boundRect.getWidth() / (double) SWF.unitDivisor)); image.setAttribute("height", Double.toString(boundRect.getHeight() / (double) SWF.unitDivisor)); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/morphshape/SVGMorphShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/morphshape/SVGMorphShapeExporter.java index 32b53d4fb..a28e7e993 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/morphshape/SVGMorphShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/morphshape/SVGMorphShapeExporter.java @@ -237,7 +237,7 @@ public class SVGMorphShapeExporter extends DefaultSVGMorphShapeExporter { final int animationLength = 2; // todo final String animationLengthStr = animationLength + "s"; - element.setAttribute(attribute, matrix.getTransformationString(SWF.unitDivisor / zoom, 1)); + element.setAttribute(attribute, matrix.getSvgTransformationString(SWF.unitDivisor / zoom, 1)); // QR decomposition double translateX = roundPixels400(matrix.translateX * zoom / SWF.unitDivisor); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/SVGShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/SVGShapeExporter.java index f837a27ef..cae1dc763 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/SVGShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/SVGShapeExporter.java @@ -122,7 +122,7 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter { pattern.setAttribute("height", "" + height); pattern.setAttribute("viewBox", "0 0 " + width + " " + height); if (matrix != null) { - pattern.setAttribute("patternTransform", matrix.getTransformationString(SWF.unitDivisor / zoom, SWF.unitDivisor / zoom)); + pattern.setAttribute("patternTransform", matrix.getSvgTransformationString(SWF.unitDivisor / zoom, SWF.unitDivisor / zoom)); } Element imageElement = exporter.createElement("image"); imageElement.setAttribute("width", "" + width); @@ -234,7 +234,7 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter { gradient.setAttribute("color-interpolation", "linearRGB"); } if (matrix != null) { - gradient.setAttribute("gradientTransform", matrix.getTransformationString(SWF.unitDivisor / zoom, 1)); + gradient.setAttribute("gradientTransform", matrix.getSvgTransformationString(SWF.unitDivisor / zoom, 1)); } for (int i = 0; i < gradientRecords.length; i++) { GRADRECORD record = gradientRecords[i]; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java index ae77053d1..1b972cec0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java @@ -17,6 +17,8 @@ package com.jpexs.decompiler.flash.importers; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.Point; import com.jpexs.decompiler.flash.helpers.ImageHelper; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG3Tag; @@ -68,6 +70,8 @@ import org.xml.sax.SAXException; */ public class ShapeImporter { + private boolean cubicWarning = false; + public Tag importImage(ShapeTag st, byte[] newData) throws IOException { return importImage(st, newData, 0, true); } @@ -143,6 +147,10 @@ public class ShapeImporter { } public Tag importSvg(ShapeTag st, String svgXml) { + return importSvg(st, svgXml, true); + } + + public Tag importSvg(ShapeTag st, String svgXml, boolean fill) { SWF swf = st.getSwf(); SHAPEWITHSTYLE shapes = new SHAPEWITHSTYLE(); @@ -164,57 +172,60 @@ public class ShapeImporter { throw new IOException("SVG root element should be 'svg'"); } - processSvgObject(shapeNum, shapes, rootElement); - + SvgStyle style = new SvgStyle(); + style = style.apply(rootElement); + Matrix transform = Matrix.getScaleInstance(SWF.unitDivisor); + processSvgObject(shapeNum, shapes, rootElement, transform, style); } catch (SAXException | IOException | ParserConfigurationException ex) { Logger.getLogger(ShapeImporter.class.getName()).log(Level.SEVERE, null, ex); } shapes.shapeRecords.add(new EndShapeRecord()); + RECT rect = st.getRect(); + int origXmin = rect.Xmin; + int origYmin = rect.Ymin; + rect.Xmin -= origXmin; + rect.Xmax -= origXmin; + rect.Ymin -= origYmin; + rect.Ymax -= origYmin; + + if (!fill) { + // todo: how to calulate the real SVG size? + RECT bounds = shapes.getBounds(); + rect.Xmax = rect.Xmin + bounds.getWidth(); + rect.Ymax = rect.Ymin + bounds.getHeight(); + } + st.shapes = shapes; st.setModified(true); return (Tag) st; } - private void processSvgObject(int shapeNum, SHAPEWITHSTYLE shapes, Element element) { + private void processSvgObject(int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style) { for (int i = 0; i < element.getChildNodes().getLength(); i++) { Node childNode = element.getChildNodes().item(i); if (childNode instanceof Element) { Element childElement = (Element) childNode; String tagName = childElement.getTagName(); + SvgStyle newStyle = style.apply(childElement); if ("g".equals(tagName)) { - processSvgObject(shapeNum, shapes, childElement); + Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), SWF.unitDivisor, 1); + Matrix m2 = m == null ? transform : m.concatenate(transform); + processSvgObject(shapeNum, shapes, childElement, m2, newStyle); } else if ("path".equals(tagName)) { - processPath(shapeNum, shapes, childElement); + processPath(shapeNum, shapes, childElement, transform, newStyle); } } } } - private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement) { + private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { String data = childElement.getAttribute("d"); - if (data == null) { - return; - } - - String attr = childElement.getAttribute("fill"); - Color fillColor = parseColor(attr); - - attr = childElement.getAttribute("fill-opacity"); - if (fillColor != null && attr != null && attr.length() > 0) { - double opacity = Double.parseDouble(attr); - fillColor = new Color(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue(), (int) Math.round(opacity * 255)); - } - - attr = childElement.getAttribute("stroke"); - Color lineColor = parseColor(attr); - - attr = childElement.getAttribute("stroke-width"); - int lineWidth = (int) Math.round((attr == null || attr.length() == 0 ? 1 : Double.parseDouble(attr)) * SWF.unitDivisor); char command = 0; + Point prevPoint = new Point(0, 0); double x0 = 0; double y0 = 0; @@ -229,65 +240,20 @@ public class ShapeImporter { boolean isRelative = Character.isLowerCase(command); - double x = pathReader.readDouble(); - double y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + double x = x0; + double y = y0; + Point p = null; switch (Character.toUpperCase(command)) { case 'M': - StyleChangeRecord scr = new StyleChangeRecord(); - scr.moveDeltaX = (int) Math.round((x - x0) * SWF.unitDivisor); - scr.moveDeltaY = (int) Math.round((y - y0) * SWF.unitDivisor); - scr.stateMoveTo = true; - + StyleChangeRecord scr; if (newShape) { newShape = false; - scr.stateNewStyles = true; - scr.fillStyles = new FILLSTYLEARRAY(); - scr.stateFillStyle1 = true; - scr.stateLineStyle = true; - if (fillColor != null) { - scr.fillStyles.fillStyles = new FILLSTYLE[1]; - scr.fillStyles.fillStyles[0] = new FILLSTYLE(); - scr.fillStyles.fillStyles[0].color = shapeNum >= 3 ? new RGBA(fillColor) : new RGB(fillColor); - scr.fillStyles.fillStyles[0].fillStyleType = FILLSTYLE.SOLID; - scr.fillStyle1 = 1; - } else { - scr.fillStyles.fillStyles = new FILLSTYLE[0]; - scr.fillStyle1 = 0; - } - - scr.lineStyles = new LINESTYLEARRAY(); - if (lineColor != null) { - scr.lineStyles.lineStyles = new LINESTYLE[1]; - scr.lineStyles.lineStyles[0] = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); - scr.lineStyles.lineStyles[0].color = shapeNum >= 3 ? new RGBA(lineColor) : new RGB(lineColor); - scr.lineStyles.lineStyles[0].width = lineWidth; - scr.lineStyle = 1; - } else { - scr.lineStyles.lineStyles = new LINESTYLE[0]; - scr.lineStyle = 0; - } + scr = getStyleChangeRecord(shapeNum, style); + } else { + scr = new StyleChangeRecord(); } - shapes.shapeRecords.add(scr); - break; - case 'L': - StraightEdgeRecord ser = new StraightEdgeRecord(); - ser.deltaX = (int) Math.round((x - x0) * SWF.unitDivisor); - ser.deltaY = (int) Math.round((y - y0) * SWF.unitDivisor); - ser.generalLineFlag = true; - shapes.shapeRecords.add(ser); - break; - case 'Q': - CurvedEdgeRecord cer = new CurvedEdgeRecord(); - cer.controlDeltaX = (int) Math.round((x - x0) * SWF.unitDivisor); - cer.controlDeltaY = (int) Math.round((y - y0) * SWF.unitDivisor); - x0 = x; - y0 = y; x = pathReader.readDouble(); y = pathReader.readDouble(); if (isRelative) { @@ -295,10 +261,139 @@ public class ShapeImporter { y += y0; } - cer.anchorDeltaX = (int) Math.round((x - x0) * SWF.unitDivisor); - cer.anchorDeltaY = (int) Math.round((y - y0) * SWF.unitDivisor); + p = transform.transform(x, y); + scr.moveDeltaX = (int) Math.round(p.x); + scr.moveDeltaY = (int) Math.round(p.y); + prevPoint = p; + System.out.println("M" + scr.moveDeltaX + "," + scr.moveDeltaY); + scr.stateMoveTo = true; + + shapes.shapeRecords.add(scr); + break; + case 'L': + StraightEdgeRecord ser = new StraightEdgeRecord(); + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + p = transform.transform(x, y); + ser.deltaX = (int) Math.round(p.x - prevPoint.x); + ser.deltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + System.out.println("L" + ser.deltaX + "," + ser.deltaY); + ser.generalLineFlag = true; + shapes.shapeRecords.add(ser); + break; + case 'H': + StraightEdgeRecord serh = new StraightEdgeRecord(); + x = pathReader.readDouble(); + if (isRelative) { + x += x0; + } + + p = transform.transform(x, y); + serh.deltaX = (int) Math.round(p.x - prevPoint.x); + prevPoint = p; + System.out.println("H" + serh.deltaX); + shapes.shapeRecords.add(serh); + break; + case 'V': + StraightEdgeRecord serv = new StraightEdgeRecord(); + y = pathReader.readDouble(); + if (isRelative) { + y += y0; + } + + p = transform.transform(x, y); + serv.deltaY = (int) Math.round(p.x - prevPoint.x); + prevPoint = p; + System.out.println("V" + serv.deltaX); + serv.vertLineFlag = true; + shapes.shapeRecords.add(serv); + break; + case 'Q': + CurvedEdgeRecord cer = new CurvedEdgeRecord(); + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + p = transform.transform(x, y); + cer.controlDeltaX = (int) Math.round(p.x - prevPoint.x); + cer.controlDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + p = transform.transform(x, y); + cer.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); + cer.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + System.out.println("Q" + cer.controlDeltaX + "," + cer.controlDeltaY + "," + cer.anchorDeltaX + "," + cer.controlDeltaY); shapes.shapeRecords.add(cer); break; + case 'C': + if (!cubicWarning) { + cubicWarning = true; + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Cubic curves are not supported by Flash."); + } + + // create at least something... + CurvedEdgeRecord cer2 = new CurvedEdgeRecord(); + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + p = transform.transform(x, y); + int controlDeltaX1 = (int) Math.round(p.x - prevPoint.x); + int controlDeltaY1 = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + p = transform.transform(x, y); + int controlDeltaX2 = (int) Math.round(p.x - prevPoint.x); + int controlDeltaY2 = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + cer2.controlDeltaX = (controlDeltaX1 + controlDeltaX2) / 2; + cer2.controlDeltaY = (controlDeltaY1 + controlDeltaY2) / 2; + + p = transform.transform(x, y); + cer2.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); + cer2.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + System.out.println("C" + cer2.controlDeltaX + "," + cer2.controlDeltaY + "," + cer2.anchorDeltaX + "," + cer2.controlDeltaY); + shapes.shapeRecords.add(cer2); + break; + default: + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); + break; } x0 = x; @@ -306,20 +401,130 @@ public class ShapeImporter { } } + private StyleChangeRecord getStyleChangeRecord(int shapeNum, SvgStyle style) { + StyleChangeRecord scr = new StyleChangeRecord(); + + scr.stateNewStyles = true; + scr.fillStyles = new FILLSTYLEARRAY(); + scr.stateFillStyle1 = true; + scr.stateLineStyle = true; + Color fillColor = style.getFillColorWithOpacity(); + if (fillColor != null) { + scr.fillStyles.fillStyles = new FILLSTYLE[1]; + scr.fillStyles.fillStyles[0] = new FILLSTYLE(); + scr.fillStyles.fillStyles[0].color = shapeNum >= 3 ? new RGBA(fillColor) : new RGB(fillColor); + scr.fillStyles.fillStyles[0].fillStyleType = FILLSTYLE.SOLID; + scr.fillStyle1 = 1; + } else { + scr.fillStyles.fillStyles = new FILLSTYLE[0]; + scr.fillStyle1 = 0; + } + + scr.lineStyles = new LINESTYLEARRAY(); + Color lineColor = style.strokeColor; + if (lineColor != null) { + scr.lineStyles.lineStyles = new LINESTYLE[1]; + scr.lineStyles.lineStyles[0] = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); + scr.lineStyles.lineStyles[0].color = shapeNum >= 3 ? new RGBA(lineColor) : new RGB(lineColor); + scr.lineStyles.lineStyles[0].width = (int) Math.round(style.strokeWidth * SWF.unitDivisor); + scr.lineStyle = 1; + } else { + scr.lineStyles.lineStyles = new LINESTYLE[0]; + scr.lineStyle = 0; + } + + return scr; + } + private Color parseColor(String rgbStr) { if (rgbStr == null) { return null; } + // todo: honfika: named colors: http://www.w3.org/TR/SVG/types.html#ColorKeywords + switch (rgbStr) { + case "none": + return new Color(0, true); + } + if (rgbStr.startsWith("#")) { String s = rgbStr.substring(1); + if (s.length() == 3) { + s = "" + s.charAt(0) + s.charAt(0) + s.charAt(1) + s.charAt(1) + s.charAt(2) + s.charAt(2); + } + int i = Integer.parseInt(s, 16); - return new Color(i, s.length() > 6); + return new Color(i, false); } return null; } + class SvgStyle { + + public Color fillColor; + + public double fillOpacity; + + public Color strokeColor; + + public double strokeWidth; + + public SvgStyle() { + fillColor = Color.BLACK; + fillOpacity = 1; + strokeColor = null; + strokeWidth = 1; + } + + public Color getFillColorWithOpacity() { + if (fillColor == null) { + return null; + } + + int opacity = (int) Math.round(fillOpacity * 255); + if (opacity == 255) { + return fillColor; + } + + return new Color(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue(), opacity); + } + + private SvgStyle apply(Element element) { + SvgStyle result = new SvgStyle(); + result.fillColor = fillColor; + result.fillOpacity = fillOpacity; + result.strokeColor = strokeColor; + result.strokeWidth = strokeWidth; + + String attr = element.getAttribute("fill"); + Color fillColor = parseColor(attr); + if (fillColor != null) { + result.fillColor = fillColor; + } + + attr = element.getAttribute("fill-opacity"); + if (attr.length() > 0) { + double opacity = Double.parseDouble(attr); + result.fillOpacity = opacity; + } + + attr = element.getAttribute("stroke"); + Color strokeColor = parseColor(attr); + if (strokeColor != null) { + result.strokeColor = strokeColor; + } + + attr = element.getAttribute("stroke-width"); + if (attr.length() > 0) { + double strokeWidth = Double.parseDouble(attr); + result.strokeWidth = strokeWidth; + } + + return result; + } + } + public static int getShapeTagType(String format) { int res = 0; switch (format) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StraightEdgeRecord.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StraightEdgeRecord.java index 5bbdeca52..c3b03703b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StraightEdgeRecord.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/StraightEdgeRecord.java @@ -105,4 +105,16 @@ public class StraightEdgeRecord extends SHAPERECORD { numBits = 0; } } + + public void simplify() { + if (generalLineFlag) { + if (deltaX == 0) { + generalLineFlag = false; + vertLineFlag = true; + } else if (deltaY == 0) { + generalLineFlag = false; + vertLineFlag = false; + } + } + } } From 57f9cbe2e7340b8b9a4d5e1b7b86b377d0a89dac Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Thu, 17 Dec 2015 09:04:46 +0100 Subject: [PATCH 2/6] approximate cubic curves with quadratic --- .../flash/importers/ShapeImporter.java | 130 +++++-- .../flash/importers/svg/CubicToQuad.java | 340 ++++++++++++++++++ 2 files changed, 435 insertions(+), 35 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/CubicToQuad.java diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java index 1b972cec0..01896c801 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java @@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; import com.jpexs.decompiler.flash.exporters.commonshape.Point; import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.importers.svg.CubicToQuad; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG3Tag; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG4Tag; @@ -53,6 +54,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; @@ -193,8 +195,8 @@ public class ShapeImporter { if (!fill) { // todo: how to calulate the real SVG size? RECT bounds = shapes.getBounds(); - rect.Xmax = rect.Xmin + bounds.getWidth(); - rect.Ymax = rect.Ymin + bounds.getHeight(); + rect.Xmax = rect.Xmin + bounds.Xmax - Math.min(0, bounds.Xmin); + rect.Ymax = rect.Ymin + bounds.Ymax - Math.min(0, bounds.Ymin); } st.shapes = shapes; @@ -226,6 +228,8 @@ public class ShapeImporter { char command = 0; Point prevPoint = new Point(0, 0); + Point startPoint = prevPoint; + Point prevCControlPoint = null; double x0 = 0; double y0 = 0; @@ -235,7 +239,6 @@ public class ShapeImporter { char newCommand; if ((newCommand = pathReader.readCommand()) != 0) { command = newCommand; - continue; } boolean isRelative = Character.isLowerCase(command); @@ -244,7 +247,8 @@ public class ShapeImporter { double y = y0; Point p = null; - switch (Character.toUpperCase(command)) { + char cmd = Character.toUpperCase(command); + switch (cmd) { case 'M': StyleChangeRecord scr; if (newShape) { @@ -269,9 +273,20 @@ public class ShapeImporter { scr.stateMoveTo = true; shapes.shapeRecords.add(scr); + startPoint = p; + break; + case 'Z': + StraightEdgeRecord serz = new StraightEdgeRecord(); + p = startPoint; + serz.deltaX = (int) Math.round(p.x - prevPoint.x); + serz.deltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + System.out.println("Z" + serz.deltaX + "," + serz.deltaY); + serz.generalLineFlag = true; + shapes.shapeRecords.add(serz); break; case 'L': - StraightEdgeRecord ser = new StraightEdgeRecord(); + StraightEdgeRecord serl = new StraightEdgeRecord(); x = pathReader.readDouble(); y = pathReader.readDouble(); if (isRelative) { @@ -280,12 +295,12 @@ public class ShapeImporter { } p = transform.transform(x, y); - ser.deltaX = (int) Math.round(p.x - prevPoint.x); - ser.deltaY = (int) Math.round(p.y - prevPoint.y); + serl.deltaX = (int) Math.round(p.x - prevPoint.x); + serl.deltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; - System.out.println("L" + ser.deltaX + "," + ser.deltaY); - ser.generalLineFlag = true; - shapes.shapeRecords.add(ser); + System.out.println("L" + serl.deltaX + "," + serl.deltaY); + serl.generalLineFlag = true; + shapes.shapeRecords.add(serl); break; case 'H': StraightEdgeRecord serh = new StraightEdgeRecord(); @@ -343,13 +358,33 @@ public class ShapeImporter { shapes.shapeRecords.add(cer); break; case 'C': + case 'S': if (!cubicWarning) { cubicWarning = true; Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Cubic curves are not supported by Flash."); } // create at least something... - CurvedEdgeRecord cer2 = new CurvedEdgeRecord(); + Point pStart = prevPoint; + Point pControl1; + + if (cmd == 'C') { + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + pControl1 = transform.transform(x, y); + } else { + if (prevCControlPoint != null) { + pControl1 = new Point(2 * pStart.x - prevCControlPoint.x, 2 * pStart.y - prevCControlPoint.y); + } else { + pControl1 = pStart; + } + } + x = pathReader.readDouble(); y = pathReader.readDouble(); if (isRelative) { @@ -357,10 +392,8 @@ public class ShapeImporter { y += y0; } - p = transform.transform(x, y); - int controlDeltaX1 = (int) Math.round(p.x - prevPoint.x); - int controlDeltaY1 = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; + Point pControl2 = transform.transform(x, y); + prevCControlPoint = pControl2; x = pathReader.readDouble(); y = pathReader.readDouble(); @@ -370,30 +403,35 @@ public class ShapeImporter { } p = transform.transform(x, y); - int controlDeltaX2 = (int) Math.round(p.x - prevPoint.x); - int controlDeltaY2 = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; + //StraightEdgeRecord serc = new StraightEdgeRecord(); + //serc.generalLineFlag = true; + //serc.deltaX = (int) Math.round(p.x - prevPoint.x); + //serc.deltaY = (int) Math.round(p.y - prevPoint.y); + //shapes.shapeRecords.add(serc); + List quadCoordinates = new CubicToQuad().cubicToQuad(pStart.x, pStart.y, pControl1.x, pControl1.y, pControl2.x, pControl2.y, p.x, p.y, 0.0006); + for (int i = 2; i < quadCoordinates.size();) { + CurvedEdgeRecord cerc = new CurvedEdgeRecord(); + p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); + cerc.controlDeltaX = (int) Math.round(p.x - prevPoint.x); + cerc.controlDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + + p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); + cerc.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); + cerc.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + shapes.shapeRecords.add(cerc); } - cer2.controlDeltaX = (controlDeltaX1 + controlDeltaX2) / 2; - cer2.controlDeltaY = (controlDeltaY1 + controlDeltaY2) / 2; - - p = transform.transform(x, y); - cer2.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); - cer2.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - System.out.println("C" + cer2.controlDeltaX + "," + cer2.controlDeltaY + "," + cer2.anchorDeltaX + "," + cer2.controlDeltaY); - shapes.shapeRecords.add(cer2); break; default: Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); - break; + return; + } + + if (cmd != 'C') { + prevCControlPoint = null; } x0 = x; @@ -421,7 +459,7 @@ public class ShapeImporter { } scr.lineStyles = new LINESTYLEARRAY(); - Color lineColor = style.strokeColor; + Color lineColor = style.getStrokeColorWithOpacity(); if (lineColor != null) { scr.lineStyles.lineStyles = new LINESTYLE[1]; scr.lineStyles.lineStyles[0] = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); @@ -464,6 +502,8 @@ public class ShapeImporter { public Color fillColor; + public double opacity; + public double fillOpacity; public Color strokeColor; @@ -475,6 +515,7 @@ public class ShapeImporter { fillOpacity = 1; strokeColor = null; strokeWidth = 1; + opacity = 1; } public Color getFillColorWithOpacity() { @@ -482,7 +523,7 @@ public class ShapeImporter { return null; } - int opacity = (int) Math.round(fillOpacity * 255); + int opacity = (int) Math.round(this.opacity * fillOpacity * 255); if (opacity == 255) { return fillColor; } @@ -490,6 +531,19 @@ public class ShapeImporter { return new Color(fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue(), opacity); } + public Color getStrokeColorWithOpacity() { + if (strokeColor == null) { + return null; + } + + int opacity = (int) Math.round(this.opacity * 255); + if (opacity == 255) { + return strokeColor; + } + + return new Color(strokeColor.getRed(), strokeColor.getGreen(), strokeColor.getBlue(), opacity); + } + private SvgStyle apply(Element element) { SvgStyle result = new SvgStyle(); result.fillColor = fillColor; @@ -521,6 +575,12 @@ public class ShapeImporter { result.strokeWidth = strokeWidth; } + attr = element.getAttribute("opacity"); + if (attr.length() > 0) { + double opacity = Double.parseDouble(attr); + result.opacity = opacity; + } + return result; } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/CubicToQuad.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/CubicToQuad.java new file mode 100644 index 000000000..681aa593f --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/CubicToQuad.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2010-2015 JPEXS, All rights reserved. + * + * This library 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; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library 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 this library. + */ +package com.jpexs.decompiler.flash.importers.svg; + +import java.util.ArrayList; +import java.util.List; + +/** + * Ported from https://github.com/fontello/cubic2quad + * + * @author JPEXS, Vitaly Puzrin + */ +public class CubicToQuad { + + class Point { + + public double x; + + public double y; + + public Point(double x, double y) { + this.x = x; + this.y = y; + } + + public Point add(Point point) { + return new Point(this.x + point.x, this.y + point.y); + } + + public Point sub(Point point) { + return new Point(this.x - point.x, this.y - point.y); + } + + public Point mul(double value) { + return new Point(this.x * value, this.y * value); + } + + public Point div(double value) { + return new Point(this.x / value, this.y / value); + } + + public double dist() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + + public double sqr() { + return this.x * this.x + this.y * this.y; + } + + public double dot(Point point) { + return this.x * point.x + this.y * point.y; + } + } + + private Point[] calcPowerCoefficients(Point p1, Point c1, Point c2, Point p2) { + // point(t) = p1*(1-t)^3 + c1*t*(1-t)^2 + c2*t^2*(1-t) + p2*t^3 = a*t^3 + b*t^2 + c*t + d + // for each t value, so + // a = (p2 - p1) + 3 * (c1 - c2) + // b = 3 * (p1 + c2) - 6 * c1 + // c = 3 * (c1 - p1) + // d = p1 + Point a = p2.sub(p1).add(c1.sub(c2).mul(3)); + Point b = p1.add(c2).mul(3).sub(c1.mul(6)); + Point c = c1.sub(p1).mul(3); + Point d = p1; + return new Point[]{a, b, c, d}; + } + + private Point calcPoint(Point a, Point b, Point c, Point d, double t) { + // a*t^3 + b*t^2 + c*t + d = ((a*t + b)*t + c)*t + d + return a.mul(t).add(b).mul(t).add(c).mul(t).add(d); + } + + private Point calcPointQuad(Point a, Point b, Point c, double t) { + // a*t^2 + b*t + c = (a*t + b)*t + c + return a.mul(t).add(b).mul(t).add(c); + } + + private Point calcPointDerivative(Point a, Point b, Point c, Point d, double t) { + // d/dt[a*t^3 + b*t^2 + c*t + d] = 3*a*t^2 + 2*b*t + c = (3*a*t + 2*b)*t + c + return a.mul(3 * t).add(b.mul(2)).mul(t).add(c); + } + + private double[] quadSolve(double a, double b, double c) { + // a*x^2 + b*x + c = 0 + if (a == 0) { + return (b == 0) ? new double[0] : new double[]{-c / b}; + } + double D = b * b - 4 * a * c; + if (D < 0) { + return new double[0]; + } else if (D == 0) { + return new double[]{-b / (2 * a)}; + } + double DSqrt = Math.sqrt(D); + return new double[]{(-b - DSqrt) / (2 * a), (-b + DSqrt) / (2 * a)}; + } + + private double cubicRoot(double x) { + return (x < 0) ? -Math.pow(-x, 1 / 3) : Math.pow(x, 1 / 3); + } + + private double[] cubicSolve(double a, double b, double c, double d) { + // a*x^3 + b*x^2 + c*x + d = 0 + if (a == 0) { + return quadSolve(b, c, d); + } + + // solve using Cardan's method, which is described in paper of R.W.D. Nickals + // http://www.nickalls.org/dick/papers/maths/cubic1993.pdf (doi:10.2307/3619777) + double xn = -b / (3 * a); // point of symmetry x coordinate + double yn = ((a * xn + b) * xn + c) * xn + d; // point of symmetry y coordinate + double deltaSq = (b * b - 3 * a * c) / (9 * a * a); // delta^2 + double hSq = 4 * a * a * Math.pow(deltaSq, 3); // h^2 + double D3 = yn * yn - hSq; + if (D3 > 0) { // 1 real root + double D3Sqrt = Math.sqrt(D3); + return new double[]{xn + cubicRoot((-yn + D3Sqrt) / (2 * a)) + cubicRoot((-yn - D3Sqrt) / (2 * a))}; + } else if (D3 == 0) { // 2 real roots + double delta1 = cubicRoot(yn / (2 * a)); + return new double[]{xn - 2 * delta1, xn + delta1}; + } + + // 3 real roots + double theta = Math.acos(-yn / Math.sqrt(hSq)) / 3; + double delta = Math.sqrt(deltaSq); + return new double[]{ + xn + 2 * delta * Math.cos(theta), + xn + 2 * delta * Math.cos(theta + Math.PI * 2 / 3), + xn + 2 * delta * Math.cos(theta + Math.PI * 4 / 3) + }; + } + + private double minDistanceToQuad(Point point, Point p1, Point c1, Point p2) { + // f(t) = (1-t)^2 * p1 + 2*t*(1 - t) * c1 + t^2 * p2 = a*t^2 + b*t + c, t in [0, 1], + // a = p1 + p2 - 2 * c1 + // b = 2 * (c1 - p1) + // c = p1; a, b, c are vectors because p1, c1, p2 are vectors too + // The distance between given point and quadratic curve is equal to + // sqrt((f(t) - point)^2), so these expression has zero derivative by t at points where + // (f'(t), (f(t) - point)) = 0. + // Substituting quadratic curve as f(t) one could obtain a cubic equation + // e3*t^3 + e2*t^2 + e1*t + e0 = 0 with following coefficients: + // e3 = 2 * a^2 + // e2 = 3 * a*b + // e1 = (b^2 + 2 * a*(c - point)) + // e0 = (c - point)*b + // One of the roots of the equation from [0, 1], or t = 0 or t = 1 is a value of t + // at which the distance between given point and quadratic Bezier curve has minimum. + // So to find the minimal distance one have to just pick the minimum value of + // the distance on set {t = 0 | t = 1 | t is root of the equation from [0, 1] }. + + Point a = p1.add(p2).sub(c1.mul(2)); + Point b = c1.sub(p1).mul(2); + Point c = p1; + double e3 = 2 * a.sqr(); + double e2 = 3 * a.dot(b); + double e1 = (b.sqr() + 2 * a.dot(c.sub(point))); + double e0 = c.sub(point).dot(b); + double[] solveResult = cubicSolve(e3, e2, e1, e0); + List candidates = new ArrayList<>(); + for (double t : solveResult) { + if (t > 0 && t < 1) { + candidates.add(t); + } + } + + candidates.add(0d); + candidates.add(1d); + + double minDistance = 1e9; + for (int i = 0; i < candidates.size(); i++) { + double distance = calcPointQuad(a, b, c, candidates.get(i)).sub(point).dist(); + if (distance < minDistance) { + minDistance = distance; + } + } + return minDistance; + } + + private Point[] processSegment(Point a, Point b, Point c, Point d, double t1, double t2) { + // Find a single control point for given segment of cubic Bezier curve + // These control point is an interception of tangent lines to the boundary points + // Let's denote that f(t) is a vector function of parameter t that defines the cubic Bezier curve, + // f(t1) + f'(t1)*z1 is a parametric equation of tangent line to f(t1) with parameter z1 + // f(t2) + f'(t2)*z2 is the same for point f(t2) and the vector equation + // f(t1) + f'(t1)*z1 = f(t2) + f'(t2)*z2 defines the values of parameters z1 and z2. + // Defining fx(t) and fy(t) as the x and y components of vector function f(t) respectively + // and solving the given system for z1 one could obtain that + // + // -(fx(t2) - fx(t1))*fy'(t2) + (fy(t2) - fy(t1))*fx'(t2) + // z1 = ------------------------------------------------------. + // -fx'(t1)*fy'(t2) + fx'(t2)*fy'(t1) + // + // Let's assign letter D to the denominator and note that if D = 0 it means that the curve actually + // is a line. Substituting z1 to the equation of tangent line to the point f(t1), one could obtain that + // cx = [fx'(t1)*(fy(t2)*fx'(t2) - fx(t2)*fy'(t2)) + fx'(t2)*(fx(t1)*fy'(t1) - fy(t1)*fx'(t1))]/D + // cy = [fy'(t1)*(fy(t2)*fx'(t2) - fx(t2)*fy'(t2)) + fy'(t2)*(fx(t1)*fy'(t1) - fy(t1)*fx'(t1))]/D + // where c = (cx, cy) is the control point of quadratic Bezier curve. + + Point f1 = calcPoint(a, b, c, d, t1); + Point f2 = calcPoint(a, b, c, d, t2); + Point f1_ = calcPointDerivative(a, b, c, d, t1); + Point f2_ = calcPointDerivative(a, b, c, d, t2); + + double D = -f1_.x * f2_.y + f2_.x * f1_.y; + if (Math.abs(D) < 1e-8) { + return new Point[]{f1, f1.add(f2).div(2), f2}; // straight line segment + } + double cx = (f1_.x * (f2.y * f2_.x - f2.x * f2_.y) + f2_.x * (f1.x * f1_.y - f1.y * f1_.x)) / D; + double cy = (f1_.y * (f2.y * f2_.x - f2.x * f2_.y) + f2_.y * (f1.x * f1_.y - f1.y * f1_.x)) / D; + return new Point[]{f1, new Point(cx, cy), f2}; + } + + private boolean isSegmentApproximationClose(Point a, Point b, Point c, Point d, double tmin, double tmax, Point p1, Point c1, Point p2, double errorBound) { + // a,b,c,d define cubic curve + // tmin, tmax are boundary points on cubic curve + // p1, c1, p2 define quadratic curve + // errorBound is maximum allowed distance + // Try to find maximum distance between one of N points segment of given cubic + // and corresponding quadratic curve that estimates the cubic one, assuming + // that the boundary points of cubic and quadratic points are equal. + // + // The distance calculation method comes from Hausdorff distance defenition + // (https://en.wikipedia.org/wiki/Hausdorff_distance), but with following simplifications + // * it looks for maximum distance only for finite number of points of cubic curve + // * it doesn't perform reverse check that means selecting set of fixed points on + // the quadratic curve and looking for the closest points on the cubic curve + // But this method allows easy estimation of approximation error, so it is enough + // for practical purposes. + + int n = 10; // number of points + 1 + double dt = (tmax - tmin) / n; + for (double t = tmin + dt; t < tmax - dt; t += dt) { // don't check distance on boundary points + // because they should be the same + Point point = calcPoint(a, b, c, d, t); + if (minDistanceToQuad(point, p1, c1, p2) > errorBound) { + return false; + } + } + return true; + } + + private boolean _isApproximationClose(Point a, Point b, Point c, Point d, List quadCurves, double errorBound) { + double dt = 1 / quadCurves.size(); + for (int i = 0; i < quadCurves.size(); i++) { + Point p1 = quadCurves.get(i)[0]; + Point c1 = quadCurves.get(i)[1]; + Point p2 = quadCurves.get(i)[2]; + if (!isSegmentApproximationClose(a, b, c, d, i * dt, (i + 1) * dt, p1, c1, p2, errorBound)) { + return false; + } + } + return true; + } + + private List fromFlatArray(double[] points) { + List result = new ArrayList<>(); + int segmentsNumber = (points.length - 2) / 4; + for (int i = 0; i < segmentsNumber; i++) { + result.add(new Point[]{ + new Point(points[4 * i], points[4 * i + 1]), + new Point(points[4 * i + 2], points[4 * i + 3]), + new Point(points[4 * i + 4], points[4 * i + 5]) + }); + } + return result; + } + + private List toFlatArray(List quadsList) { + List result = new ArrayList<>(); + result.add(quadsList.get(0)[0].x); + result.add(quadsList.get(0)[0].y); + for (int i = 0; i < quadsList.size(); i++) { + result.add(quadsList.get(i)[1].x); + result.add(quadsList.get(i)[1].y); + result.add(quadsList.get(i)[2].x); + result.add(quadsList.get(i)[2].y); + } + return result; + } + + private boolean isApproximationClose(double p1x, double p1y, double c1x, double c1y, double c2x, double c2y, double p2x, double p2y, double[] quads, double errorBound) { + // TODO: rewrite it in C-style and remove _isApproximationClose + Point[] pc = calcPowerCoefficients( + new Point(p1x, p1y), + new Point(c1x, c1y), + new Point(c2x, c2y), + new Point(p2x, p2y) + ); + return _isApproximationClose(pc[0], pc[1], pc[2], pc[3], fromFlatArray(quads), errorBound); + } + + /* + * Approximate cubic Bezier curve defined with base points p1, p2 and control points c1, c2 with + * with a few quadratic Bezier curves. + * The function uses tangent method to find quadratic approximation of cubic curve segment and + * simplified Hausdorff distance to determine number of segments that is enough to make error small. + * In general the method is the same as described here: https://fontforge.github.io/bezier.html. + */ + public List cubicToQuad(double p1x, double p1y, double c1x, double c1y, double c2x, double c2y, double p2x, double p2y, double errorBound) { + Point p1 = new Point(p1x, p1y); + Point c1 = new Point(c1x, c1y); + Point c2 = new Point(c2x, c2y); + Point p2 = new Point(p2x, p2y); + Point[] pc = calcPowerCoefficients(p1, c1, c2, p2); + Point a = pc[0], b = pc[1], c = pc[2], d = pc[3]; + + List approximation = new ArrayList<>(); + for (int segmentsCount = 1; segmentsCount <= 8; segmentsCount++) { + approximation.clear(); + for (double t = 0; t < 1; t += 1.0 / segmentsCount) { + approximation.add(processSegment(a, b, c, d, t, t + 1.0 / segmentsCount)); + } + if (segmentsCount == 1 && (approximation.get(0)[1].sub(p1).dot(c1.sub(p1)) < 0 + || approximation.get(0)[1].sub(p2).dot(c2.sub(p2)) < 0)) { + // approximation concave, while the curve is convex (or vice versa) + continue; + } + if (_isApproximationClose(a, b, c, d, approximation, errorBound)) { + break; + } + } + return toFlatArray(approximation); + } +} From 0f416b09442298b8bff0496d3f01ac5fc3d09c6b Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Fri, 18 Dec 2015 12:24:56 +0100 Subject: [PATCH 3/6] svg import improved --- .../flash/exporters/commonshape/Matrix.java | 135 +++-- .../flash/importers/ShapeImporter.java | 565 ++++++++++++++---- .../flash/importers/SvgPathReader.java | 13 + .../decompiler/flash/types/LINESTYLE2.java | 12 +- 4 files changed, 550 insertions(+), 175 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java index 82e6ea91e..30562d598 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java @@ -16,7 +16,6 @@ */ package com.jpexs.decompiler.flash.exporters.commonshape; -import com.jpexs.decompiler.flash.importers.SvgPathReader; import com.jpexs.decompiler.flash.types.MATRIX; import java.awt.geom.AffineTransform; @@ -241,45 +240,107 @@ public final class Matrix implements Cloneable { + rotateSkew1 + ", " + scaleY + ", " + translateX + ", " + translateY + ")"; } - public static Matrix parseSvgMatrix(String matrixStr, double translateDivisor, double unitDivisor) { - matrixStr = matrixStr.trim(); - if (matrixStr.startsWith("matrix")) { - matrixStr = matrixStr.substring(6).trim(); - if (matrixStr.startsWith("(") && matrixStr.endsWith(")")) { - matrixStr = matrixStr.substring(1, matrixStr.length() - 1); - SvgPathReader reader = new SvgPathReader(matrixStr); - double scaleX = reader.readDouble() * unitDivisor; - double rotateSkew0 = reader.readDouble() * unitDivisor; - double rotateSkew1 = reader.readDouble() * unitDivisor; - double scaleY = reader.readDouble() * unitDivisor; - double translateX = reader.readDouble() * translateDivisor; - double translateY = reader.readDouble() * translateDivisor; - Matrix result = new Matrix(); - result.translateX = translateX; - result.translateY = translateY; - result.rotateSkew0 = rotateSkew0; - result.rotateSkew1 = rotateSkew1; - result.scaleX = scaleX; - result.scaleY = scaleY; - return result; - } - } else if (matrixStr.startsWith("translate")) { - matrixStr = matrixStr.substring(9).trim(); - if (matrixStr.startsWith("(") && matrixStr.endsWith(")")) { - matrixStr = matrixStr.substring(1, matrixStr.length() - 1); - SvgPathReader reader = new SvgPathReader(matrixStr); - double translateX = reader.readDouble() * translateDivisor; - double translateY = reader.readDouble() * translateDivisor; - Matrix result = new Matrix(); - result.translateX = translateX; - result.translateY = translateY; - result.scaleX = unitDivisor; - result.scaleY = unitDivisor; - return result; + public static Matrix parseSvgMatrix(String transformStr, double translateDivisor, double unitDivisor) { + Matrix ret = new Matrix(); + while (transformStr.length() > 0) { + String funcName = transformStr.split("\\(")[0]; + transformStr = transformStr.substring(funcName.length() + 1); + String params = transformStr.split("\\)")[0]; + transformStr = transformStr.substring(params.length() + 1); + String[] args = params.split(","); + funcName = funcName.trim(); + switch (funcName) { + case "matrix": + if (args.length == 6) { + double scaleX = Double.parseDouble(args[0].trim()) * unitDivisor; + double rotateSkew0 = Double.parseDouble(args[1].trim()) * unitDivisor; + double rotateSkew1 = Double.parseDouble(args[2].trim()) * unitDivisor; + double scaleY = Double.parseDouble(args[3].trim()) * unitDivisor; + double translateX = Double.parseDouble(args[4].trim()) * translateDivisor; + double translateY = Double.parseDouble(args[5].trim()) * translateDivisor; + Matrix result = new Matrix(); + result.translateX = translateX; + result.translateY = translateY; + result.rotateSkew0 = rotateSkew0; + result.rotateSkew1 = rotateSkew1; + result.scaleX = scaleX; + result.scaleY = scaleY; + ret = ret.concatenate(result); + } + break; + case "translate": + if (args.length == 1 || args.length == 2) { + double translateX = Double.parseDouble(args[0].trim()) * translateDivisor; + double translateY = 0; + if (args.length == 2) { + translateY = Double.parseDouble(args[1].trim()) * translateDivisor; + } + + Matrix result = new Matrix(); + result.translateX = translateX; + result.translateY = translateY; + result.scaleX = unitDivisor; + result.scaleY = unitDivisor; + ret = ret.concatenate(result); + } + break; + case "scale": + if (args.length == 1 || args.length == 2) { + double scaleX = Double.parseDouble(args[0].trim()); + double scaleY = scaleX; + if (args.length == 2) { + scaleY = Double.parseDouble(args[1].trim()); + } + + Matrix result = new Matrix(); + result.scaleX = scaleX; + result.scaleY = scaleY; + ret = ret.concatenate(result); + } + break; + case "skewX": + if (args.length == 1) { + double angle = Double.parseDouble(args[0].trim()) * Math.PI / 180; + + Matrix result = new Matrix(); + result.rotateSkew1 = Math.tan(angle); + ret = ret.concatenate(result); + } + break; + case "skewY": + if (args.length == 1) { + double angle = Double.parseDouble(args[0].trim()) * Math.PI / 180; + + Matrix result = new Matrix(); + result.rotateSkew0 = Math.tan(angle); + ret = ret.concatenate(result); + } + break; + case "rotate": + if (args.length == 1 || args.length == 3) { + double rotateAngle = Double.parseDouble(args[0].trim()); + double tx = 0; + double ty = 0; + if (args.length > 1) { + tx = Double.parseDouble(args[1].trim()); + ty = Double.parseDouble(args[2].trim()); + } + + double angleRad = -rotateAngle * Math.PI / 180; + Matrix result = new Matrix(); + result.rotateSkew0 = -Math.sin(angleRad); + result.rotateSkew1 = Math.sin(angleRad); + result.scaleX = Math.cos(angleRad); + result.scaleY = Math.cos(angleRad); + result = result.preConcatenate(getTranslateInstance(tx * translateDivisor, ty * translateDivisor)) + .concatenate(getTranslateInstance(-tx * translateDivisor, -ty * translateDivisor)); + ret = ret.concatenate(result); + } + break; } } - return null; + return ret; } private double roundPixels400(double pixels) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java index 01896c801..2072766f4 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java @@ -54,7 +54,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Random; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; @@ -72,7 +75,11 @@ import org.xml.sax.SAXException; */ public class ShapeImporter { - private boolean cubicWarning = false; + private final Set shownWarnings = new HashSet<>(); + + private static final Color TRANSPARENT = new Color(0, true); + + private final Random random = new Random(); public Tag importImage(ShapeTag st, byte[] newData) throws IOException { return importImage(st, newData, 0, true); @@ -212,58 +219,56 @@ public class ShapeImporter { Element childElement = (Element) childNode; String tagName = childElement.getTagName(); SvgStyle newStyle = style.apply(childElement); + Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), SWF.unitDivisor, 1); + Matrix m2 = m == null ? transform : m.concatenate(transform); if ("g".equals(tagName)) { - Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), SWF.unitDivisor, 1); - Matrix m2 = m == null ? transform : m.concatenate(transform); processSvgObject(shapeNum, shapes, childElement, m2, newStyle); } else if ("path".equals(tagName)) { - processPath(shapeNum, shapes, childElement, transform, newStyle); + processPath(shapeNum, shapes, childElement, m2, newStyle); + } else { + showWarning(tagName + "tagNotSupported", "The SVG tag '" + tagName + "' is not supported."); } } } } - private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String data = childElement.getAttribute("d"); - - char command = 0; + private void processCommands(int shapeNum, SHAPEWITHSTYLE shapes, List commands, Matrix transform, SvgStyle style) { Point prevPoint = new Point(0, 0); Point startPoint = prevPoint; - Point prevCControlPoint = null; double x0 = 0; double y0 = 0; - boolean newShape = true; - SvgPathReader pathReader = new SvgPathReader(data); - while (pathReader.hasNext()) { - char newCommand; - if ((newCommand = pathReader.readCommand()) != 0) { - command = newCommand; - } + StyleChangeRecord scrStyle = getStyleChangeRecord(shapeNum, style); + int fillStyle = scrStyle.fillStyle1; + int lineStyle = scrStyle.lineStyle; + scrStyle.stateFillStyle0 = true; + scrStyle.stateFillStyle1 = true; + scrStyle.stateLineStyle = true; + scrStyle.fillStyle0 = 0; + scrStyle.fillStyle1 = 0; + scrStyle.lineStyle = 0; + shapes.shapeRecords.add(scrStyle); - boolean isRelative = Character.isLowerCase(command); + for (PathCommand command : commands) { double x = x0; double y = y0; - Point p = null; - char cmd = Character.toUpperCase(command); + char cmd = Character.toUpperCase(command.command); switch (cmd) { case 'M': - StyleChangeRecord scr; - if (newShape) { - newShape = false; - scr = getStyleChangeRecord(shapeNum, style); - } else { - scr = new StyleChangeRecord(); + StyleChangeRecord scr = new StyleChangeRecord(); + if (fillStyle != 0) { + scr.stateFillStyle1 = true; + scr.fillStyle1 = fillStyle; + } + if (lineStyle != 0) { + scr.lineStyle = lineStyle; + scr.stateLineStyle = true; } - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[0]; + y = command.params[1]; p = transform.transform(x, y); scr.moveDeltaX = (int) Math.round(p.x); @@ -287,12 +292,8 @@ public class ShapeImporter { break; case 'L': StraightEdgeRecord serl = new StraightEdgeRecord(); - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[0]; + y = command.params[1]; p = transform.transform(x, y); serl.deltaX = (int) Math.round(p.x - prevPoint.x); @@ -300,14 +301,12 @@ public class ShapeImporter { prevPoint = p; System.out.println("L" + serl.deltaX + "," + serl.deltaY); serl.generalLineFlag = true; + serl.simplify(); shapes.shapeRecords.add(serl); break; case 'H': StraightEdgeRecord serh = new StraightEdgeRecord(); - x = pathReader.readDouble(); - if (isRelative) { - x += x0; - } + x = command.params[0]; p = transform.transform(x, y); serh.deltaX = (int) Math.round(p.x - prevPoint.x); @@ -317,38 +316,27 @@ public class ShapeImporter { break; case 'V': StraightEdgeRecord serv = new StraightEdgeRecord(); - y = pathReader.readDouble(); - if (isRelative) { - y += y0; - } + y = command.params[0]; p = transform.transform(x, y); - serv.deltaY = (int) Math.round(p.x - prevPoint.x); + serv.deltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; - System.out.println("V" + serv.deltaX); + System.out.println("V" + serv.deltaY); serv.vertLineFlag = true; shapes.shapeRecords.add(serv); break; case 'Q': CurvedEdgeRecord cer = new CurvedEdgeRecord(); - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[0]; + y = command.params[1]; p = transform.transform(x, y); cer.controlDeltaX = (int) Math.round(p.x - prevPoint.x); cer.controlDeltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[2]; + y = command.params[3]; p = transform.transform(x, y); cer.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); @@ -358,49 +346,24 @@ public class ShapeImporter { shapes.shapeRecords.add(cer); break; case 'C': - case 'S': - if (!cubicWarning) { - cubicWarning = true; - Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Cubic curves are not supported by Flash."); - } + showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); // create at least something... Point pStart = prevPoint; Point pControl1; - if (cmd == 'C') { - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[0]; + y = command.params[1]; - pControl1 = transform.transform(x, y); - } else { - if (prevCControlPoint != null) { - pControl1 = new Point(2 * pStart.x - prevCControlPoint.x, 2 * pStart.y - prevCControlPoint.y); - } else { - pControl1 = pStart; - } - } + pControl1 = transform.transform(x, y); - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[2]; + y = command.params[3]; Point pControl2 = transform.transform(x, y); - prevCControlPoint = pControl2; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[4]; + y = command.params[5]; p = transform.transform(x, y); @@ -430,13 +393,216 @@ public class ShapeImporter { return; } - if (cmd != 'C') { + x0 = x; + y0 = y; + } + } + + private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String data = childElement.getAttribute("d"); + + char command = 0; + Point startPoint = new Point(0, 0); + Point prevCControlPoint = null; + Point prevQControlPoint = null; + double x0 = 0; + double y0 = 0; + + List pathCommands = new ArrayList<>(); + SvgPathReader pathReader = new SvgPathReader(data); + while (pathReader.hasNext()) { + char newCommand; + if ((newCommand = pathReader.readCommand()) != 0) { + command = newCommand; + } + + boolean isRelative = Character.isLowerCase(command); + + double x = x0; + double y = y0; + + char cmd = Character.toUpperCase(command); + switch (cmd) { + case 'M': + PathCommand scr = new PathCommand(); + scr.command = 'M'; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + scr.params = new double[]{x, y}; + + pathCommands.add(scr); + startPoint = new Point(x, y); + + command = isRelative ? 'l' : 'L'; + break; + case 'Z': + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + x = startPoint.x; + y = startPoint.y; + serz.params = new double[]{x, y}; + pathCommands.add(serz); + break; + case 'L': + PathCommand serl = new PathCommand(); + serl.command = 'L'; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + serl.params = new double[]{x, y}; + pathCommands.add(serl); + break; + case 'H': + PathCommand serh = new PathCommand(); + serh.command = 'H'; + x = pathReader.readDouble(); + if (isRelative) { + x += x0; + } + + serh.params = new double[]{x}; + pathCommands.add(serh); + break; + case 'V': + PathCommand serv = new PathCommand(); + serv.command = 'V'; + y = pathReader.readDouble(); + if (isRelative) { + y += y0; + } + + serv.params = new double[]{y}; + pathCommands.add(serv); + break; + case 'Q': + case 'T': + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + + Point pControl; + if (cmd == 'Q') { + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + pControl = new Point(x, y); + } else { + if (prevQControlPoint != null) { + pControl = new Point(2 * x0 - prevQControlPoint.x, 2 * y0 - prevQControlPoint.y); + } else { + pControl = new Point(x0, y0); + } + } + + prevQControlPoint = pControl; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + cer.params = new double[]{pControl.x, pControl.y, x, y}; + pathCommands.add(cer); + break; + case 'C': + case 'S': + showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); + + // create at least something... + Point pControl1; + if (cmd == 'C') { + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + pControl1 = new Point(x, y); + } else { + if (prevCControlPoint != null) { + pControl1 = new Point(2 * x0 - prevCControlPoint.x, 2 * y0 - prevCControlPoint.y); + } else { + pControl1 = new Point(x0, y0); + } + } + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + Point pControl2 = new Point(x, y); + prevCControlPoint = pControl2; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + PathCommand cerc = new PathCommand(); + cerc.command = 'C'; + cerc.params = new double[]{pControl1.x, pControl1.y, pControl2.x, pControl2.y, x, y}; + pathCommands.add(cerc); + + break; + case 'A': + double rx = pathReader.readDouble(); + double ry = pathReader.readDouble(); + double xRot = pathReader.readDouble(); + int largeFlag = (int) pathReader.readDouble(); + int sweepFlag = (int) pathReader.readDouble(); + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + // todo: draw arc, now draw only a line + PathCommand sera = new PathCommand(); + sera.command = 'L'; + sera.params = new double[]{x, y}; + pathCommands.add(sera); + + break; + default: + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); + return; + } + + if (cmd != 'C' && cmd != 'S') { prevCControlPoint = null; } + if (cmd != 'Q' && cmd != 'T') { + prevQControlPoint = null; + } + x0 = x; y0 = y; } + + processCommands(shapeNum, shapes, pathCommands, transform, style); } private StyleChangeRecord getStyleChangeRecord(int shapeNum, SvgStyle style) { @@ -462,9 +628,34 @@ public class ShapeImporter { Color lineColor = style.getStrokeColorWithOpacity(); if (lineColor != null) { scr.lineStyles.lineStyles = new LINESTYLE[1]; - scr.lineStyles.lineStyles[0] = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); - scr.lineStyles.lineStyles[0].color = shapeNum >= 3 ? new RGBA(lineColor) : new RGB(lineColor); - scr.lineStyles.lineStyles[0].width = (int) Math.round(style.strokeWidth * SWF.unitDivisor); + LINESTYLE lineStyle = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2();; + lineStyle.color = shapeNum >= 3 ? new RGBA(lineColor) : new RGB(lineColor); + lineStyle.width = (int) Math.round(style.strokeWidth * SWF.unitDivisor); + SvgLineCap lineCap = style.strokeLineCap; + SvgLineJoin lineJoin = style.strokeLineJoin; + if (lineStyle instanceof LINESTYLE2) { + LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle; + int swfCap = lineCap == SvgLineCap.BUTT ? LINESTYLE2.NO_CAP + : lineCap == SvgLineCap.ROUND ? LINESTYLE2.ROUND_CAP + : lineCap == SvgLineCap.SQUARE ? LINESTYLE2.SQUARE_CAP : 0; + lineStyle2.startCapStyle = swfCap; + lineStyle2.endCapStyle = swfCap; + + int swfJoin = lineJoin == SvgLineJoin.MITER ? LINESTYLE2.MITER_JOIN + : lineJoin == SvgLineJoin.ROUND ? LINESTYLE2.ROUND_JOIN + : lineJoin == SvgLineJoin.BEVEL ? LINESTYLE2.BEVEL_JOIN : 0; + lineStyle2.joinStyle = swfJoin; + lineStyle2.miterLimitFactor = (int) style.strokeMiterLimit; + } else { + if (lineCap != SvgLineCap.ROUND) { + showWarning("lineCapNotSupported", "LineCap style not supported in shape " + shapeNum); + } + if (lineJoin != SvgLineJoin.ROUND) { + showWarning("lineJoinNotSupported", "LineJoin style not supported in shape " + shapeNum); + } + } + + scr.lineStyles.lineStyles[0] = lineStyle; scr.lineStyle = 1; } else { scr.lineStyles.lineStyles = new LINESTYLE[0]; @@ -482,7 +673,7 @@ public class ShapeImporter { // todo: honfika: named colors: http://www.w3.org/TR/SVG/types.html#ColorKeywords switch (rgbStr) { case "none": - return new Color(0, true); + return TRANSPARENT; } if (rgbStr.startsWith("#")) { @@ -493,12 +684,30 @@ public class ShapeImporter { int i = Integer.parseInt(s, 16); return new Color(i, false); + } else { + showWarning("fillNotSupported", "Only solid fills are supported. Random color assigned."); + return new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)); } - - return null; } - class SvgStyle { + class PathCommand { + + public char command; + + public double[] params; + } + + enum SvgLineCap { + + BUTT, ROUND, SQUARE + } + + enum SvgLineJoin { + + MITER, ROUND, BEVEL + } + + class SvgStyle implements Cloneable { public Color fillColor; @@ -510,12 +719,24 @@ public class ShapeImporter { public double strokeWidth; + public double strokeOpacity; + + public SvgLineCap strokeLineCap; + + public SvgLineJoin strokeLineJoin; + + public double strokeMiterLimit; + public SvgStyle() { fillColor = Color.BLACK; fillOpacity = 1; strokeColor = null; strokeWidth = 1; + strokeOpacity = 1; opacity = 1; + strokeLineCap = SvgLineCap.BUTT; + strokeLineJoin = SvgLineJoin.MITER; + strokeMiterLimit = 4; } public Color getFillColorWithOpacity() { @@ -536,7 +757,7 @@ public class ShapeImporter { return null; } - int opacity = (int) Math.round(this.opacity * 255); + int opacity = (int) Math.round(this.opacity * strokeOpacity * 255); if (opacity == 255) { return strokeColor; } @@ -544,47 +765,127 @@ public class ShapeImporter { return new Color(strokeColor.getRed(), strokeColor.getGreen(), strokeColor.getBlue(), opacity); } + @Override + public SvgStyle clone() { + try { + SvgStyle ret = (SvgStyle) super.clone(); + return ret; + } catch (CloneNotSupportedException ex) { + throw new RuntimeException(); + } + } + + private void applyStyle(SvgStyle style, String name, String value) { + if (value == null || value.length() == 0) { + return; + } + + switch (name) { + case "fill": { + Color fillColor = parseColor(value); + if (fillColor != null) { + style.fillColor = fillColor == TRANSPARENT ? null : fillColor; + } + } + break; + case "fill-opacity": { + double opacity = Double.parseDouble(value); + style.fillOpacity = opacity; + } + break; + case "stroke": { + Color strokeColor = parseColor(value); + if (strokeColor != null) { + style.strokeColor = strokeColor == TRANSPARENT ? null : strokeColor; + } + } + break; + case "stroke-width": { + double strokeWidth = Double.parseDouble(value); + style.strokeWidth = strokeWidth; + } + break; + case "stroke-opacity": { + double opacity = Double.parseDouble(value); + style.strokeOpacity = opacity; + } + break; + case "stroke-linecap": { + switch (value) { + case "butt": + style.strokeLineCap = SvgLineCap.BUTT; + break; + case "round": + style.strokeLineCap = SvgLineCap.ROUND; + break; + case "square": + style.strokeLineCap = SvgLineCap.SQUARE; + break; + } + } + break; + case "stroke-linejoin": { + switch (value) { + case "miter": + style.strokeLineJoin = SvgLineJoin.MITER; + break; + case "round": + style.strokeLineJoin = SvgLineJoin.ROUND; + break; + case "bevel": + style.strokeLineJoin = SvgLineJoin.BEVEL; + break; + } + } + break; + case "stroke-miterlimit": { + double strokeMiterLimit = Double.parseDouble(value); + style.strokeMiterLimit = strokeMiterLimit; + } + case "opacity": { + double opacity = Double.parseDouble(value); + style.opacity = opacity; + } + break; + } + } + private SvgStyle apply(Element element) { - SvgStyle result = new SvgStyle(); - result.fillColor = fillColor; - result.fillOpacity = fillOpacity; - result.strokeColor = strokeColor; - result.strokeWidth = strokeWidth; + SvgStyle result = clone(); - String attr = element.getAttribute("fill"); - Color fillColor = parseColor(attr); - if (fillColor != null) { - result.fillColor = fillColor; + String[] styles = new String[]{ + "fill", "fill-opacity", + "stroke", "stroke-width", "stroke-opacity", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", + "opacity" + }; + + for (String style : styles) { + if (element.hasAttribute(style)) { + String attr = element.getAttribute(style); + applyStyle(result, style, attr); + + } } - attr = element.getAttribute("fill-opacity"); - if (attr.length() > 0) { - double opacity = Double.parseDouble(attr); - result.fillOpacity = opacity; - } - - attr = element.getAttribute("stroke"); - Color strokeColor = parseColor(attr); - if (strokeColor != null) { - result.strokeColor = strokeColor; - } - - attr = element.getAttribute("stroke-width"); - if (attr.length() > 0) { - double strokeWidth = Double.parseDouble(attr); - result.strokeWidth = strokeWidth; - } - - attr = element.getAttribute("opacity"); - if (attr.length() > 0) { - double opacity = Double.parseDouble(attr); - result.opacity = opacity; + if (element.hasAttribute("style")) { + String[] styleDefs = element.getAttribute("style").split(";"); + for (String styleDef : styleDefs) { + String[] parts = styleDef.split(":", 2); + applyStyle(result, parts[0], parts[1].trim()); + } } return result; } } + private void showWarning(String name, String text) { + if (!shownWarnings.contains(name)) { + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, text); + shownWarnings.add(name); + } + } + public static int getShapeTagType(String format) { int res = 0; switch (format) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SvgPathReader.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SvgPathReader.java index f3604e4f6..85611823b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SvgPathReader.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SvgPathReader.java @@ -49,6 +49,7 @@ public class SvgPathReader { return 0; } + readWhiteSpaces(); char ch = peek(); char command = 0; if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { @@ -63,6 +64,7 @@ public class SvgPathReader { public double readDouble() { int startPos = pos; + readWhiteSpaces(); if (peek() == '-') { pos++; } @@ -89,6 +91,17 @@ public class SvgPathReader { return result; } + private void readWhiteSpaces() { + while (hasNext()) { + char ch = peek(); + if (ch != ' ' && ch != '\r' && ch != '\n') { + return; + } + + readChar(); + } + } + private void readSeparators() { while (hasNext()) { char ch = peek(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java index 599622932..ac3d67571 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java @@ -34,6 +34,12 @@ public class LINESTYLE2 extends LINESTYLE implements Serializable { @SWFType(value = BasicType.UB, count = 2) public int joinStyle; + public static final int ROUND_CAP = 0; + + public static final int NO_CAP = 1; + + public static final int SQUARE_CAP = 2; + public static final int ROUND_JOIN = 0; public static final int BEVEL_JOIN = 1; @@ -57,12 +63,6 @@ public class LINESTYLE2 extends LINESTYLE implements Serializable { @SWFType(value = BasicType.UB, count = 2) public int endCapStyle; - public static final int ROUND_CAP = 0; - - public static final int NO_CAP = 1; - - public static final int SQUARE_CAP = 2; - @SWFType(BasicType.UI16) @Conditional(value = "joinStyle", options = MITER_JOIN) public int miterLimitFactor; From c23e22b950134cf06cea2102d832b7facc64616b Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Fri, 18 Dec 2015 13:32:50 +0100 Subject: [PATCH 4/6] #1113 It takes too long to switch between rendered sprite fixed --- .../src/com/jpexs/helpers/Helper.java | 25 ++++++++++++------- .../jpexs/decompiler/flash/gui/MainPanel.java | 22 +++++++++++----- .../flash/gui/player/FlashPlayerPanel.java | 2 +- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java index 7d724e121..37309c249 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java @@ -775,18 +775,25 @@ public class Helper { sb.append((char) ch); } } + + char lastChar = sb.charAt(sb.length() - 1); + if (lastChar == ' ') { + sb.setLength(sb.length() - 1); + sb.append("%20"); + } else if (lastChar == ' ') { + sb.setLength(sb.length() - 1); + sb.append("%2E"); + } + str = sb.toString(); - if (str.endsWith(" ")) { - str = str.substring(0, str.length() - 1) + "%20"; - } - if (str.endsWith(".")) { - str = str.substring(0, str.length() - 1) + "%2E"; - } - str = "." + str + "."; for (String inv : invalidFilenamesParts) { - str = Pattern.compile("\\." + Pattern.quote(inv) + "\\.", Pattern.CASE_INSENSITIVE).matcher(str).replaceAll("._" + inv + "."); + if (str.startsWith(inv)) { + if (str.equals(inv) || str.startsWith(".")) { + str = "_" + str; + } + } } - str = str.substring(1, str.length() - 1); //remove dots + if (str.isEmpty()) { str = "unnamed"; } diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index f7b42fcfb..84be2998b 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -318,8 +318,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private static final Logger logger = Logger.getLogger(MainPanel.class.getName()); - private Map asms = new HashMap<>(); - public void setPercent(int percent) { progressBar.setValue(percent); progressBar.setVisible(true); @@ -816,7 +814,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se mainFrame.setTitle(ApplicationInfo.applicationVerName + (Configuration.displayFileName.get() ? " - " + swf.getFileTitle() : "")); List abcList = swf.getAbcList(); - asms = swf.getASMs(true); boolean hasAbc = !abcList.isEmpty(); @@ -1608,6 +1605,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se if (rawScriptName.startsWith("#PCODE ")) { rawScriptName = rawScriptName.substring("#PCODE ".length()); } + Map asms = swf.getASMs(true); if (actionPanel != null && asms.containsKey(rawScriptName)) { actionPanel.setSource(asms.get(rawScriptName), true); } @@ -2670,12 +2668,24 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se if (item instanceof ShapeTag) { ShapeTag st = (ShapeTag) item; - File selectedFile = showImportFileChooser("filter.images|*.jpg;*.jpeg;*.gif;*.png;*.bmp"); + String filter = "filter.images|*.jpg;*.jpeg;*.gif;*.png;*.bmp"; + if (Configuration.experimentalSvgImportEnabled.get()) { + filter += ";*.svg"; + } + + File selectedFile = showImportFileChooser(filter); if (selectedFile != null) { File selfile = Helper.fixDialogFile(selectedFile); - byte[] data = Helper.readFile(selfile.getAbsolutePath()); + byte[] data = null; + String svgText = null; + if (".svg".equals(Path.getExtension(selfile))) { + svgText = Helper.readTextFile(selfile.getAbsolutePath()); + } else { + data = Helper.readFile(selfile.getAbsolutePath()); + } try { - Tag newTag = new ShapeImporter().importImage(st, data, 0, false); + ShapeImporter shapeImporter = new ShapeImporter(); + Tag newTag = svgText != null ? shapeImporter.importSvg(st, svgText, false) : shapeImporter.importImage(st, data, 0, false); SWF swf = st.getSwf(); if (newTag != null) { refreshTree(swf); diff --git a/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java b/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java index 630bc5a20..633af979b 100644 --- a/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/player/FlashPlayerPanel.java @@ -250,7 +250,7 @@ public final class FlashPlayerPanel extends Panel implements Closeable, MediaDis private Thread playQueue; - private Object queueLock = new Object(); + private final Object queueLock = new Object(); public synchronized void displaySWF(final String flashName, final Color bgColor, final float frameRate) { From 1b1923eee1f502fa7119bc3ee5fae84dc7958bd6 Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Fri, 18 Dec 2015 13:33:47 +0100 Subject: [PATCH 5/6] fix --- libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java index 37309c249..8bc44f3d3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/Helper.java @@ -780,7 +780,7 @@ public class Helper { if (lastChar == ' ') { sb.setLength(sb.length() - 1); sb.append("%20"); - } else if (lastChar == ' ') { + } else if (lastChar == '.') { sb.setLength(sb.length() - 1); sb.append("%2E"); } From 60a8fa8110e4281d75d5e8d05c29c12b4f08d74c Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Fri, 18 Dec 2015 15:34:02 +0100 Subject: [PATCH 6/6] svg import improvement --- .../flash/importers/ShapeImporter.java | 658 +++++++++++++++++- 1 file changed, 655 insertions(+), 3 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java index 2072766f4..79ef9c792 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java @@ -225,6 +225,18 @@ public class ShapeImporter { processSvgObject(shapeNum, shapes, childElement, m2, newStyle); } else if ("path".equals(tagName)) { processPath(shapeNum, shapes, childElement, m2, newStyle); + } else if ("circle".equals(tagName)) { + processCircle(shapeNum, shapes, childElement, m2, newStyle); + } else if ("ellipse".equals(tagName)) { + processEllipse(shapeNum, shapes, childElement, m2, newStyle); + } else if ("rect".equals(tagName)) { + processRect(shapeNum, shapes, childElement, m2, newStyle); + } else if ("line".equals(tagName)) { + processLine(shapeNum, shapes, childElement, m2, newStyle); + } else if ("polyline".equals(tagName)) { + processPolyline(shapeNum, shapes, childElement, m2, newStyle); + } else if ("polygon".equals(tagName)) { + processPolygon(shapeNum, shapes, childElement, m2, newStyle); } else { showWarning(tagName + "tagNotSupported", "The SVG tag '" + tagName + "' is not supported."); } @@ -567,9 +579,9 @@ public class ShapeImporter { case 'A': double rx = pathReader.readDouble(); double ry = pathReader.readDouble(); - double xRot = pathReader.readDouble(); - int largeFlag = (int) pathReader.readDouble(); - int sweepFlag = (int) pathReader.readDouble(); + double fi = pathReader.readDouble() * Math.PI / 180; + boolean largeFlag = (int) pathReader.readDouble() != 0; + boolean sweepFlag = (int) pathReader.readDouble() != 0; x = pathReader.readDouble(); y = pathReader.readDouble(); @@ -578,9 +590,36 @@ public class ShapeImporter { y += y0; } + double x1 = x0; + double y1 = y0; + double x2 = x; + double y2 = y; + + double d1 = (x1 - x2) / 2; + double d2 = (y1 - y2) / 2; + double x1Comma = Math.cos(fi) * d1 + Math.sin(fi) * d2; + double y1Comma = -Math.sin(fi) * d1 + Math.cos(fi) * d2; + + double c = Math.sqrt((rx * rx * ry * ry - rx * rx * y1Comma * y1Comma - ry * ry * x1Comma * x1Comma) / (rx * rx * y1Comma * y1Comma + ry * ry * x1Comma * x1Comma)); + double cxComma = c * rx * y1Comma / ry; + double cyComma = c * -ry * x1Comma / rx; + + if (largeFlag == sweepFlag) { + cxComma = -cxComma; + cyComma = -cyComma; + } + + double cx = Math.cos(fi) * cxComma - Math.sin(fi) * cyComma + (x1 + x2) / 2; + double cy = Math.sin(fi) * cxComma + Math.cos(fi) * cyComma + (y1 + y2) / 2; + // todo: draw arc, now draw only a line PathCommand sera = new PathCommand(); sera.command = 'L'; + sera.params = new double[]{cx, cy}; + pathCommands.add(sera); + + sera = new PathCommand(); + sera.command = 'L'; sera.params = new double[]{x, y}; pathCommands.add(sera); @@ -605,6 +644,301 @@ public class ShapeImporter { processCommands(shapeNum, shapes, pathCommands, transform, style); } + private void processCircle(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("cx"); + double cx = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("cy"); + double cy = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("r"); + double r = attr.length() > 0 ? Double.parseDouble(attr) : 0; + double sqrt2RHalf = Math.sqrt(2) * r / 2; + double sqrt2Minus1R = (Math.sqrt(2) - 1) * r; + + List pathCommands = new ArrayList<>(); + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{cx + r, cy}; + pathCommands.add(scr); + + double[] points = new double[]{ + cx + r, cy - sqrt2Minus1R, + cx + sqrt2RHalf, cy - sqrt2RHalf, + cx + sqrt2Minus1R, cy - r, + cx, cy - r, + cx - sqrt2Minus1R, cy - r, + cx - sqrt2RHalf, cy - sqrt2RHalf, + cx - r, cy - sqrt2Minus1R, + cx - r, cy, + cx - r, cy + sqrt2Minus1R, + cx - sqrt2RHalf, cy + sqrt2RHalf, + cx - sqrt2Minus1R, cy + r, + cx, cy + r, + cx + sqrt2Minus1R, cy + r, + cx + sqrt2RHalf, cy + sqrt2RHalf, + cx + r, cy + sqrt2Minus1R, + cx + r, cy}; + + for (int i = 0; i < points.length; i += 4) { + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + cer.params = new double[]{points[i], points[i + 1], points[i + 2], points[i + 3]}; + + pathCommands.add(cer); + } + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("cx"); + double cx = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("cy"); + double cy = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("rx"); + double rx = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("ry"); + double ry = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + double sqrt2RXHalf = Math.sqrt(2) * rx / 2; + double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; + double sqrt2RYHalf = Math.sqrt(2) * ry / 2; + double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; + + List pathCommands = new ArrayList<>(); + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{cx + rx, cy}; + pathCommands.add(scr); + + double[] points = new double[]{ + cx + rx, cy - sqrt2Minus1RY, + cx + sqrt2RXHalf, cy - sqrt2RYHalf, + cx + sqrt2Minus1RX, cy - ry, + cx, cy - ry, + cx - sqrt2Minus1RX, cy - ry, + cx - sqrt2RXHalf, cy - sqrt2RYHalf, + cx - rx, cy - sqrt2Minus1RY, + cx - rx, cy, + cx - rx, cy + sqrt2Minus1RY, + cx - sqrt2RXHalf, cy + sqrt2RYHalf, + cx - sqrt2Minus1RX, cy + ry, + cx, cy + ry, + cx + sqrt2Minus1RX, cy + ry, + cx + sqrt2RXHalf, cy + sqrt2RYHalf, + cx + rx, cy + sqrt2Minus1RY, + cx + rx, cy}; + + for (int i = 0; i < points.length; i += 4) { + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + cer.params = new double[]{points[i], points[i + 1], points[i + 2], points[i + 3]}; + + pathCommands.add(cer); + } + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processRect(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("x"); + double x = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("y"); + double y = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("width"); + double width = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("height"); + double height = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("rx"); + double rx = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("ry"); + double ry = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + if (rx == 0 && ry != 0) { + rx = ry; + } else if (rx != 0 && ry == 0) { + ry = rx; + } + + if (rx > width / 2) { + rx = width / 2; + } + + if (ry > height / 2) { + ry = height / 2; + } + + List pathCommands = new ArrayList<>(); + + if (rx > 0 || ry > 0) { + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{x + width, y + ry}; + pathCommands.add(scr); + + double sqrt2RXHalf = Math.sqrt(2) * rx / 2; + double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; + double sqrt2RYHalf = Math.sqrt(2) * ry / 2; + double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; + + double[] points = new double[]{ + x + width, y + ry - sqrt2Minus1RY, + x + width - rx + sqrt2RXHalf, y + ry - sqrt2RYHalf, + x + width - rx + sqrt2Minus1RX, y, + x + width - rx, y, + x + rx, y, + x + rx - sqrt2Minus1RX, y, + x + rx - sqrt2RXHalf, y + ry - sqrt2RYHalf, + x, y + ry - sqrt2Minus1RY, + x, y + ry, + x, y + height - ry, + x, y + height - ry + sqrt2Minus1RY, + x + rx - sqrt2RXHalf, y + height - ry + sqrt2RYHalf, + x + rx - sqrt2Minus1RX, y + height, + x + rx, y + height, + x + width - rx, y + height, + x + width - rx + sqrt2Minus1RX, y + height, + x + width - rx + sqrt2RXHalf, y + height - ry + sqrt2RYHalf, + x + width, y + height - ry + sqrt2Minus1RY, + x + width, y + height - ry, + x + width, y + ry}; + + for (int i = 0; i < points.length;) { + if (i % 10 == 8) { + PathCommand cer = new PathCommand(); + cer.command = 'L'; + cer.params = new double[]{points[i], points[i + 1]}; + pathCommands.add(cer); + i += 2; + } else { + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + cer.params = new double[]{points[i], points[i + 1], points[i + 2], points[i + 3]}; + pathCommands.add(cer); + i += 4; + } + } + } else { + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{x, y}; + pathCommands.add(scr); + + double[] points = new double[]{ + x + width, y, + x + width, y + height, + x, y + height, + x, y}; + + for (int i = 0; i < points.length; i += 2) { + PathCommand cer = new PathCommand(); + cer.command = 'L'; + cer.params = new double[]{points[i], points[i + 1]}; + + pathCommands.add(cer); + } + } + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processLine(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("x1"); + double x1 = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("y1"); + double y1 = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("x2"); + double x2 = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + attr = childElement.getAttribute("y2"); + double y2 = attr.length() > 0 ? Double.parseDouble(attr) : 0; + + List pathCommands = new ArrayList<>(); + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{x1, y1}; + pathCommands.add(scr); + + PathCommand cer = new PathCommand(); + cer.command = 'L'; + cer.params = new double[]{x2, y2}; + + pathCommands.add(cer); + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + processPolyline(shapeNum, shapes, childElement, transform, style, false); + } + + private void processPolygon(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + processPolyline(shapeNum, shapes, childElement, transform, style, true); + } + + private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean close) { + String data = childElement.getAttribute("points"); + + char command = 'M'; + Point startPoint = new Point(0, 0); + double x0 = 0; + double y0 = 0; + + List pathCommands = new ArrayList<>(); + SvgPathReader pathReader = new SvgPathReader(data); + while (pathReader.hasNext()) { + double x = x0; + double y = y0; + + Point p = null; + switch (command) { + case 'M': + PathCommand scr = new PathCommand(); + scr.command = 'M'; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + scr.params = new double[]{x, y}; + + pathCommands.add(scr); + startPoint = new Point(x, y); + break; + case 'L': + PathCommand serl = new PathCommand(); + serl.command = 'L'; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + serl.params = new double[]{x, y}; + pathCommands.add(serl); + break; + } + + x0 = x; + y0 = y; + command = 'L'; + } + + if (close) { + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + serz.params = new double[]{startPoint.x, startPoint.y}; + pathCommands.add(serz); + } + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + private StyleChangeRecord getStyleChangeRecord(int shapeNum, SvgStyle style) { StyleChangeRecord scr = new StyleChangeRecord(); @@ -674,6 +1008,300 @@ public class ShapeImporter { switch (rgbStr) { case "none": return TRANSPARENT; + case "aliceblue": + return new Color(240, 248, 255); + case "antiquewhite": + return new Color(250, 235, 215); + case "aqua": + return new Color(0, 255, 255); + case "aquamarine": + return new Color(127, 255, 212); + case "azure": + return new Color(240, 255, 255); + case "beige": + return new Color(245, 245, 220); + case "bisque": + return new Color(255, 228, 196); + case "black": + return new Color(0, 0, 0); + case "blanchedalmond": + return new Color(255, 235, 205); + case "blue": + return new Color(0, 0, 255); + case "blueviolet": + return new Color(138, 43, 226); + case "brown": + return new Color(165, 42, 42); + case "burlywood": + return new Color(222, 184, 135); + case "cadetblue": + return new Color(95, 158, 160); + case "chartreuse": + return new Color(127, 255, 0); + case "chocolate": + return new Color(210, 105, 30); + case "coral": + return new Color(255, 127, 80); + case "cornflowerblue": + return new Color(100, 149, 237); + case "cornsilk": + return new Color(255, 248, 220); + case "crimson": + return new Color(220, 20, 60); + case "cyan": + return new Color(0, 255, 255); + case "darkblue": + return new Color(0, 0, 139); + case "darkcyan": + return new Color(0, 139, 139); + case "darkgoldenrod": + return new Color(184, 134, 11); + case "darkgray": + return new Color(169, 169, 169); + case "darkgreen": + return new Color(0, 100, 0); + case "darkgrey": + return new Color(169, 169, 169); + case "darkkhaki": + return new Color(189, 183, 107); + case "darkmagenta": + return new Color(139, 0, 139); + case "darkolivegreen": + return new Color(85, 107, 47); + case "darkorange": + return new Color(255, 140, 0); + case "darkorchid": + return new Color(153, 50, 204); + case "darkred": + return new Color(139, 0, 0); + case "darksalmon": + return new Color(233, 150, 122); + case "darkseagreen": + return new Color(143, 188, 143); + case "darkslateblue": + return new Color(72, 61, 139); + case "darkslategray": + return new Color(47, 79, 79); + case "darkslategrey": + return new Color(47, 79, 79); + case "darkturquoise": + return new Color(0, 206, 209); + case "darkviolet": + return new Color(148, 0, 211); + case "deeppink": + return new Color(255, 20, 147); + case "deepskyblue": + return new Color(0, 191, 255); + case "dimgray": + return new Color(105, 105, 105); + case "dimgrey": + return new Color(105, 105, 105); + case "dodgerblue": + return new Color(30, 144, 255); + case "firebrick": + return new Color(178, 34, 34); + case "floralwhite": + return new Color(255, 250, 240); + case "forestgreen": + return new Color(34, 139, 34); + case "fuchsia": + return new Color(255, 0, 255); + case "gainsboro": + return new Color(220, 220, 220); + case "ghostwhite": + return new Color(248, 248, 255); + case "gold": + return new Color(255, 215, 0); + case "goldenrod": + return new Color(218, 165, 32); + case "gray": + return new Color(128, 128, 128); + case "grey": + return new Color(128, 128, 128); + case "green": + return new Color(0, 128, 0); + case "greenyellow": + return new Color(173, 255, 47); + case "honeydew": + return new Color(240, 255, 240); + case "hotpink": + return new Color(255, 105, 180); + case "indianred": + return new Color(205, 92, 92); + case "indigo": + return new Color(75, 0, 130); + case "ivory": + return new Color(255, 255, 240); + case "khaki": + return new Color(240, 230, 140); + case "lavender": + return new Color(230, 230, 250); + case "lavenderblush": + return new Color(255, 240, 245); + case "lawngreen": + return new Color(124, 252, 0); + case "lemonchiffon": + return new Color(255, 250, 205); + case "lightblue": + return new Color(173, 216, 230); + case "lightcoral": + return new Color(240, 128, 128); + case "lightcyan": + return new Color(224, 255, 255); + case "lightgoldenrodyellow": + return new Color(250, 250, 210); + case "lightgray": + return new Color(211, 211, 211); + case "lightgreen": + return new Color(144, 238, 144); + case "lightgrey": + return new Color(211, 211, 211); + case "lightpink": + return new Color(255, 182, 193); + case "lightsalmon": + return new Color(255, 160, 122); + case "lightseagreen": + return new Color(32, 178, 170); + case "lightskyblue": + return new Color(135, 206, 250); + case "lightslategray": + return new Color(119, 136, 153); + case "lightslategrey": + return new Color(119, 136, 153); + case "lightsteelblue": + return new Color(176, 196, 222); + case "lightyellow": + return new Color(255, 255, 224); + case "lime": + return new Color(0, 255, 0); + case "limegreen": + return new Color(50, 205, 50); + case "linen": + return new Color(250, 240, 230); + case "magenta": + return new Color(255, 0, 255); + case "maroon": + return new Color(128, 0, 0); + case "mediumaquamarine": + return new Color(102, 205, 170); + case "mediumblue": + return new Color(0, 0, 205); + case "mediumorchid": + return new Color(186, 85, 211); + case "mediumpurple": + return new Color(147, 112, 219); + case "mediumseagreen": + return new Color(60, 179, 113); + case "mediumslateblue": + return new Color(123, 104, 238); + case "mediumspringgreen": + return new Color(0, 250, 154); + case "mediumturquoise": + return new Color(72, 209, 204); + case "mediumvioletred": + return new Color(199, 21, 133); + case "midnightblue": + return new Color(25, 25, 112); + case "mintcream": + return new Color(245, 255, 250); + case "mistyrose": + return new Color(255, 228, 225); + case "moccasin": + return new Color(255, 228, 181); + case "navajowhite": + return new Color(255, 222, 173); + case "navy": + return new Color(0, 0, 128); + case "oldlace": + return new Color(253, 245, 230); + case "olive": + return new Color(128, 128, 0); + case "olivedrab": + return new Color(107, 142, 35); + case "orange": + return new Color(255, 165, 0); + case "orangered": + return new Color(255, 69, 0); + case "orchid": + return new Color(218, 112, 214); + case "palegoldenrod": + return new Color(238, 232, 170); + case "palegreen": + return new Color(152, 251, 152); + case "paleturquoise": + return new Color(175, 238, 238); + case "palevioletred": + return new Color(219, 112, 147); + case "papayawhip": + return new Color(255, 239, 213); + case "peachpuff": + return new Color(255, 218, 185); + case "peru": + return new Color(205, 133, 63); + case "pink": + return new Color(255, 192, 203); + case "plum": + return new Color(221, 160, 221); + case "powderblue": + return new Color(176, 224, 230); + case "purple": + return new Color(128, 0, 128); + case "red": + return new Color(255, 0, 0); + case "rosybrown": + return new Color(188, 143, 143); + case "royalblue": + return new Color(65, 105, 225); + case "saddlebrown": + return new Color(139, 69, 19); + case "salmon": + return new Color(250, 128, 114); + case "sandybrown": + return new Color(244, 164, 96); + case "seagreen": + return new Color(46, 139, 87); + case "seashell": + return new Color(255, 245, 238); + case "sienna": + return new Color(160, 82, 45); + case "silver": + return new Color(192, 192, 192); + case "skyblue": + return new Color(135, 206, 235); + case "slateblue": + return new Color(106, 90, 205); + case "slategray": + return new Color(112, 128, 144); + case "slategrey": + return new Color(112, 128, 144); + case "snow": + return new Color(255, 250, 250); + case "springgreen": + return new Color(0, 255, 127); + case "steelblue": + return new Color(70, 130, 180); + case "tan": + return new Color(210, 180, 140); + case "teal": + return new Color(0, 128, 128); + case "thistle": + return new Color(216, 191, 216); + case "tomato": + return new Color(255, 99, 71); + case "turquoise": + return new Color(64, 224, 208); + case "violet": + return new Color(238, 130, 238); + case "wheat": + return new Color(245, 222, 179); + case "white": + return new Color(255, 255, 255); + case "whitesmoke": + return new Color(245, 245, 245); + case "yellow": + return new Color(255, 255, 0); + case "yellowgreen": + return new Color(154, 205, 50); } if (rgbStr.startsWith("#")) { @@ -684,10 +1312,34 @@ public class ShapeImporter { int i = Integer.parseInt(s, 16); return new Color(i, false); + } else if (rgbStr.startsWith("rgb")) { + rgbStr = rgbStr.substring(3).trim(); + if (rgbStr.startsWith("(") && rgbStr.endsWith(")")) { + rgbStr = rgbStr.substring(1, rgbStr.length() - 1); + String[] args = rgbStr.split(","); + if (args.length == 3) { + String a0 = args[0].trim(); + String a1 = args[1].trim(); + String a2 = args[2].trim(); + if (a0.endsWith("%") && a1.endsWith("%") && a2.endsWith("%")) { + int r = (int) Math.round(Integer.parseInt(a0.substring(0, a0.length() - 1)) * 255.0 / 100); + int g = (int) Math.round(Integer.parseInt(a1.substring(0, a1.length() - 1)) * 255.0 / 100); + int b = (int) Math.round(Integer.parseInt(a2.substring(0, a2.length() - 1)) * 255.0 / 100); + return new Color(r, g, b); + } else { + int r = Integer.parseInt(a0); + int g = Integer.parseInt(a1); + int b = Integer.parseInt(a2); + return new Color(r, g, b); + } + } + } } else { showWarning("fillNotSupported", "Only solid fills are supported. Random color assigned."); return new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)); } + + return null; } class PathCommand {