/* * Copyright (C) 2010-2016 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.exporters.shape; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.exporters.commonshape.FillStyle; import com.jpexs.decompiler.flash.exporters.commonshape.LineStyle; import com.jpexs.decompiler.flash.types.ColorTransform; import com.jpexs.decompiler.flash.types.FILLSTYLE; import com.jpexs.decompiler.flash.types.FOCALGRADIENT; import com.jpexs.decompiler.flash.types.LINESTYLE; import com.jpexs.decompiler.flash.types.LINESTYLE2; import com.jpexs.decompiler.flash.types.RGB; import com.jpexs.decompiler.flash.types.SHAPE; import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; import com.jpexs.helpers.Cache; import java.awt.Color; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * * @author JPEXS, Claus Wahlers */ public abstract class ShapeExporterBase implements IShapeExporter { protected final SHAPE shape; private final List<FillStyle> _fillStyles; private final List<LineStyle> _lineStyles; private final List<List<IEdge>> _fillPaths; private final List<List<IEdge>> _linePaths; private final ColorTransform colorTransform; public ShapeExporterBase(SWF swf, SHAPE shape, ColorTransform colorTransform) { this.shape = shape; this.colorTransform = colorTransform; Cache<SHAPE, ShapeExportData> cache = swf.getShapeExportDataCache(); ShapeExportData cachedData = cache.get(shape); if (cachedData == null) { List<FillStyle> fillStyles = new ArrayList<>(); List<LineStyle> lineStyles = new ArrayList<>(); if (shape instanceof SHAPEWITHSTYLE) { SHAPEWITHSTYLE shapeWithStyle = (SHAPEWITHSTYLE) shape; for (FILLSTYLE fillStyle : shapeWithStyle.fillStyles.fillStyles) { fillStyles.add(new FillStyle(fillStyle)); } for (LINESTYLE lineStyle : shapeWithStyle.lineStyles.lineStyles) { lineStyles.add(new LineStyle(lineStyle)); } } // Create edge maps List<Map<Integer, List<IEdge>>> fillEdgeMaps = new ArrayList<>(); List<Map<Integer, List<IEdge>>> lineEdgeMaps = new ArrayList<>(); createEdgeMaps(shape, fillStyles, lineStyles, fillEdgeMaps, lineEdgeMaps); int count = lineEdgeMaps.size(); List<List<IEdge>> fillPaths = new ArrayList<>(count); List<List<IEdge>> linePaths = new ArrayList<>(count); for (int i = 0; i < count; i++) { fillPaths.add(createPathFromEdgeMap(fillEdgeMaps.get(i))); linePaths.add(createPathFromEdgeMap(lineEdgeMaps.get(i))); } cachedData = new ShapeExportData(); cachedData.fillPaths = fillPaths; cachedData.linePaths = linePaths; cachedData.fillStyles = fillStyles; cachedData.lineStyles = lineStyles; cache.put(shape, cachedData); } _fillStyles = cachedData.fillStyles; _lineStyles = cachedData.lineStyles; _fillPaths = cachedData.fillPaths; _linePaths = cachedData.linePaths; } public void export() { // Let the doc handler know that a shape export starts beginShape(); // Export fills and strokes for each group separately for (int i = 0; i < _linePaths.size(); i++) { // Export fills first exportFillPath(_fillPaths.get(i)); // Export strokes last exportLinePath(_linePaths.get(i)); } // Let the doc handler know that we're done exporting a shape endShape(); } private void createEdgeMaps(SHAPE shape, List<FillStyle> fillStyles, List<LineStyle> lineStyles, List<Map<Integer, List<IEdge>>> fillEdgeMaps, List<Map<Integer, List<IEdge>>> lineEdgeMaps) { int xPos = 0; int yPos = 0; int fillStyleIdxOffset = 0; int lineStyleIdxOffset = 0; int currentFillStyleIdx0 = 0; int currentFillStyleIdx1 = 0; int currentLineStyleIdx = 0; List<IEdge> subPath = new ArrayList<>(); Map<Integer, List<IEdge>> currentFillEdgeMap = new HashMap<>(); Map<Integer, List<IEdge>> currentLineEdgeMap = new HashMap<>(); List<SHAPERECORD> records = shape.shapeRecords; for (int i = 0; i < records.size(); i++) { SHAPERECORD shapeRecord = records.get(i); if (shapeRecord instanceof StyleChangeRecord) { StyleChangeRecord styleChangeRecord = (StyleChangeRecord) shapeRecord; if (styleChangeRecord.stateLineStyle || styleChangeRecord.stateFillStyle0 || styleChangeRecord.stateFillStyle1) { processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap); subPath = new ArrayList<>(); } if (styleChangeRecord.stateNewStyles) { fillStyleIdxOffset = fillStyles.size(); lineStyleIdxOffset = lineStyles.size(); appendFillStyles(fillStyles, styleChangeRecord.fillStyles.fillStyles); appendLineStyles(lineStyles, styleChangeRecord.lineStyles.lineStyles); } // Check if all styles are reset to 0. // This (probably) means that a new group starts with the next record if (styleChangeRecord.stateLineStyle && styleChangeRecord.lineStyle == 0 && styleChangeRecord.stateFillStyle0 && styleChangeRecord.fillStyle0 == 0 && styleChangeRecord.stateFillStyle1 && styleChangeRecord.fillStyle1 == 0) { cleanEdgeMap(currentFillEdgeMap); cleanEdgeMap(currentLineEdgeMap); fillEdgeMaps.add(currentFillEdgeMap); lineEdgeMaps.add(currentLineEdgeMap); currentFillEdgeMap = new HashMap<>(); currentLineEdgeMap = new HashMap<>(); currentLineStyleIdx = 0; currentFillStyleIdx0 = 0; currentFillStyleIdx1 = 0; } else { if (styleChangeRecord.stateLineStyle) { currentLineStyleIdx = styleChangeRecord.lineStyle; if (currentLineStyleIdx > 0) { currentLineStyleIdx += lineStyleIdxOffset; } } if (styleChangeRecord.stateFillStyle0) { currentFillStyleIdx0 = styleChangeRecord.fillStyle0; if (currentFillStyleIdx0 > 0) { currentFillStyleIdx0 += fillStyleIdxOffset; } } if (styleChangeRecord.stateFillStyle1) { currentFillStyleIdx1 = styleChangeRecord.fillStyle1; if (currentFillStyleIdx1 > 0) { currentFillStyleIdx1 += fillStyleIdxOffset; } } } if (styleChangeRecord.stateMoveTo) { xPos = styleChangeRecord.moveDeltaX; yPos = styleChangeRecord.moveDeltaY; } } else if (shapeRecord instanceof StraightEdgeRecord) { StraightEdgeRecord straightEdgeRecord = (StraightEdgeRecord) shapeRecord; int xPosFrom = xPos; int yPosFrom = yPos; if (straightEdgeRecord.generalLineFlag) { xPos += straightEdgeRecord.deltaX; yPos += straightEdgeRecord.deltaY; } else if (straightEdgeRecord.vertLineFlag) { yPos += straightEdgeRecord.deltaY; } else { xPos += straightEdgeRecord.deltaX; } subPath.add(new StraightEdge(xPosFrom, yPosFrom, xPos, yPos, currentLineStyleIdx, currentFillStyleIdx1)); } else if (shapeRecord instanceof CurvedEdgeRecord) { CurvedEdgeRecord curvedEdgeRecord = (CurvedEdgeRecord) shapeRecord; int xPosFrom = xPos; int yPosFrom = yPos; int xPosControl = xPos + curvedEdgeRecord.controlDeltaX; int yPosControl = yPos + curvedEdgeRecord.controlDeltaY; xPos = xPosControl + curvedEdgeRecord.anchorDeltaX; yPos = yPosControl + curvedEdgeRecord.anchorDeltaY; subPath.add(new CurvedEdge(xPosFrom, yPosFrom, xPosControl, yPosControl, xPos, yPos, currentLineStyleIdx, currentFillStyleIdx1)); } else if (shapeRecord instanceof EndShapeRecord) { // We're done. Process the last subpath, if any processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap); cleanEdgeMap(currentFillEdgeMap); cleanEdgeMap(currentLineEdgeMap); fillEdgeMaps.add(currentFillEdgeMap); lineEdgeMaps.add(currentLineEdgeMap); } } } private void processSubPath(List<IEdge> subPath, int lineStyleIdx, int fillStyleIdx0, int fillStyleIdx1, Map<Integer, List<IEdge>> currentFillEdgeMap, Map<Integer, List<IEdge>> currentLineEdgeMap) { List<IEdge> path; if (fillStyleIdx0 != 0) { path = currentFillEdgeMap.get(fillStyleIdx0); if (path == null) { path = new ArrayList<>(); currentFillEdgeMap.put(fillStyleIdx0, path); } for (int j = subPath.size() - 1; j >= 0; j--) { path.add(subPath.get(j).reverseWithNewFillStyle(fillStyleIdx0)); } } if (fillStyleIdx1 != 0) { path = currentFillEdgeMap.get(fillStyleIdx1); if (path == null) { path = new ArrayList<>(); currentFillEdgeMap.put(fillStyleIdx1, path); } appendEdges(path, subPath); } if (lineStyleIdx != 0) { path = currentLineEdgeMap.get(lineStyleIdx); if (path == null) { path = new ArrayList<>(); currentLineEdgeMap.put(lineStyleIdx, path); } appendEdges(path, subPath); } } private void exportFillPath(List<IEdge> path) { int posX = Integer.MAX_VALUE; int posY = Integer.MAX_VALUE; int fillStyleIdx = Integer.MAX_VALUE; if (path.size() > 0) { beginFills(); for (int i = 0; i < path.size(); i++) { IEdge e = path.get(i); if (fillStyleIdx != e.getFillStyleIdx()) { if (fillStyleIdx != Integer.MAX_VALUE) { endFill(); } fillStyleIdx = e.getFillStyleIdx(); posX = Integer.MAX_VALUE; posY = Integer.MAX_VALUE; if (fillStyleIdx - 1 < _fillStyles.size()) { FillStyle fillStyle = _fillStyles.get(fillStyleIdx - 1); switch (fillStyle.fillStyleType) { case FILLSTYLE.SOLID: // Solid fill beginFill(colorTransform == null ? fillStyle.color : colorTransform.apply(fillStyle.color)); break; case FILLSTYLE.LINEAR_GRADIENT: case FILLSTYLE.RADIAL_GRADIENT: case FILLSTYLE.FOCAL_RADIAL_GRADIENT: // Gradient fill beginGradientFill( fillStyle.fillStyleType, colorTransform == null ? fillStyle.gradient.gradientRecords : colorTransform.apply(fillStyle.gradient.gradientRecords), fillStyle.gradientMatrix, fillStyle.gradient.spreadMode, fillStyle.gradient.interpolationMode, (fillStyle.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyle.gradient).focalPoint : 0 ); break; case FILLSTYLE.REPEATING_BITMAP: case FILLSTYLE.CLIPPED_BITMAP: case FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP: case FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP: // Bitmap fill beginBitmapFill( fillStyle.bitmapId, fillStyle.bitmapMatrix, (fillStyle.fillStyleType == FILLSTYLE.REPEATING_BITMAP || fillStyle.fillStyleType == FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP), (fillStyle.fillStyleType == FILLSTYLE.REPEATING_BITMAP || fillStyle.fillStyleType == FILLSTYLE.CLIPPED_BITMAP), colorTransform ); break; } } else { // Font shapes define no fillstyles per se, but do reference fillstyle index 1, // which represents the font color. We just report null in this case. beginFill(null); } } if (posX != e.getFromX() || posY != e.getFromY()) { moveTo(e.getFromX(), e.getFromY()); } if (e instanceof CurvedEdge) { CurvedEdge c = (CurvedEdge) e; curveTo(c.getControlX(), c.getControlY(), c.toX, c.toY); } else { lineTo(e.getToX(), e.getToY()); } posX = e.getToX(); posY = e.getToY(); } if (fillStyleIdx != Integer.MAX_VALUE) { endFill(); } endFills(); } } private void exportLinePath(List<IEdge> path) { int posX = Integer.MAX_VALUE; int posY = Integer.MAX_VALUE; int lineStyleIdx = Integer.MAX_VALUE; if (path.size() > 0) { boolean autoClose = true; beginLines(); for (int i = 0; i < path.size(); i++) { IEdge e = path.get(i); if (lineStyleIdx != e.getLineStyleIdx()) { lineStyleIdx = e.getLineStyleIdx(); posX = Integer.MAX_VALUE; posY = Integer.MAX_VALUE; LineStyle lineStyle = null; try { lineStyle = _lineStyles.get(lineStyleIdx - 1); } catch (Exception ex) { } if (lineStyle != null) { String scaleMode = "NORMAL"; boolean pixelHintingFlag = false; int startCapStyle = LINESTYLE2.ROUND_CAP; int endCapStyle = LINESTYLE2.ROUND_CAP; int joinStyle = LINESTYLE2.ROUND_JOIN; float miterLimitFactor = 3f; boolean hasFillFlag = false; autoClose = true; if (lineStyle.isLineStyle2) { if (lineStyle.noClose) { autoClose = false; } if (lineStyle.noHScaleFlag && lineStyle.noVScaleFlag) { scaleMode = "NONE"; } else if (lineStyle.noHScaleFlag) { scaleMode = "VERTICAL"; } else if (lineStyle.noVScaleFlag) { scaleMode = "HORIZONTAL"; } pixelHintingFlag = lineStyle.pixelHintingFlag; startCapStyle = lineStyle.startCapStyle; endCapStyle = lineStyle.endCapStyle; joinStyle = lineStyle.joinStyle; miterLimitFactor = lineStyle.miterLimitFactor; hasFillFlag = lineStyle.hasFillFlag; } lineStyle( lineStyle.width, colorTransform == null ? lineStyle.color : colorTransform.apply(lineStyle.color), pixelHintingFlag, scaleMode, startCapStyle, endCapStyle, joinStyle, miterLimitFactor); if (hasFillFlag) { FillStyle fillStyle = lineStyle.fillType; switch (fillStyle.fillStyleType) { case FILLSTYLE.LINEAR_GRADIENT: case FILLSTYLE.RADIAL_GRADIENT: case FILLSTYLE.FOCAL_RADIAL_GRADIENT: // Gradient fill lineGradientStyle( fillStyle.fillStyleType, fillStyle.gradient.gradientRecords, fillStyle.gradientMatrix, fillStyle.gradient.spreadMode, fillStyle.gradient.interpolationMode, (fillStyle.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyle.gradient).focalPoint : 0 ); break; } } } else { // We should never get here lineStyle(1, new RGB(Color.black), false, "NORMAL", 0, 0, 0, 3); } } if (posX != e.getFromX() || posY != e.getFromY()) { moveTo(e.getFromX(), e.getFromY()); } if (e instanceof CurvedEdge) { CurvedEdge c = (CurvedEdge) e; curveTo(c.getControlX(), c.getControlY(), c.toX, c.toY); } else { lineTo(e.getToX(), e.getToY()); } posX = e.getToX(); posY = e.getToY(); } IEdge firstEdge = path.get(0); endLines(autoClose && firstEdge.getFromX() == posX && firstEdge.getFromY() == posY); } } private List<IEdge> createPathFromEdgeMap(Map<Integer, List<IEdge>> edgeMap) { List<IEdge> newPath = new ArrayList<>(); List<Integer> styleIdxArray = new ArrayList<>(); for (Integer styleIdx : edgeMap.keySet()) { styleIdxArray.add(styleIdx); } Collections.sort(styleIdxArray); for (int i = 0; i < styleIdxArray.size(); i++) { appendEdges(newPath, edgeMap.get(styleIdxArray.get(i))); } return newPath; } private void cleanEdgeMap(Map<Integer, List<IEdge>> edgeMap) { for (Integer styleIdx : edgeMap.keySet()) { List<IEdge> subPath = edgeMap.get(styleIdx); if (subPath != null && subPath.size() > 0) { int idx; IEdge prevEdge = null; List<IEdge> tmpPath = new ArrayList<>(); Map<Long, List<IEdge>> coordMap = createCoordMap(subPath); while (subPath.size() > 0) { idx = 0; while (idx < subPath.size()) { if (prevEdge != null) { IEdge subPathEdge = subPath.get(idx); if (prevEdge.getToX() != subPathEdge.getFromX() || prevEdge.getToY() != subPathEdge.getFromY()) { IEdge edge = findNextEdgeInCoordMap(coordMap, prevEdge); if (edge != null) { idx = subPath.indexOf(edge); } else { idx = 0; prevEdge = null; } continue; } } IEdge edge = subPath.remove(idx); tmpPath.add(edge); removeEdgeFromCoordMap(coordMap, edge); prevEdge = edge; } } edgeMap.put(styleIdx, tmpPath); } } } private Map<Long, List<IEdge>> createCoordMap(List<IEdge> path) { Map<Long, List<IEdge>> coordMap = new HashMap<>(); for (int i = 0; i < path.size(); i++) { IEdge edge = path.get(i); long fromLong = (((long) edge.getFromX()) << 32) | (edge.getFromY() & 0xffffffffL); List<IEdge> coordMapArray = coordMap.get(fromLong); if (coordMapArray == null) { List<IEdge> list = new ArrayList<>(); list.add(path.get(i)); coordMap.put(fromLong, list); } else { coordMapArray.add(path.get(i)); } } return coordMap; } private void removeEdgeFromCoordMap(Map<Long, List<IEdge>> coordMap, IEdge edge) { long fromLong = (((long) edge.getFromX()) << 32) | (edge.getFromY() & 0xffffffffL); List<IEdge> coordMapArray = coordMap.get(fromLong); if (coordMapArray != null) { if (coordMapArray.size() == 1) { coordMap.remove(fromLong); } else { int i = coordMapArray.indexOf(edge); if (i > -1) { coordMapArray.remove(i); } } } } private IEdge findNextEdgeInCoordMap(Map<Long, List<IEdge>> coordMap, IEdge edge) { long toLong = (((long) edge.getToX()) << 32) | (edge.getToY() & 0xffffffffL); List<IEdge> coordMapArray = coordMap.get(toLong); if (coordMapArray != null && coordMapArray.size() > 0) { return coordMapArray.get(0); } return null; } private void appendFillStyles(List<FillStyle> v1, FILLSTYLE[] v2) { for (FILLSTYLE s : v2) { v1.add(new FillStyle(s)); } } private void appendLineStyles(List<LineStyle> v1, LINESTYLE[] v2) { for (LINESTYLE s : v2) { v1.add(new LineStyle(s)); } } private void appendEdges(List<IEdge> v1, List<IEdge> v2) { for (int i = 0; i < v2.size(); i++) { v1.add(v2.get(i)); } } }