Has small stroke fix

This commit is contained in:
Jindra Petřík
2025-06-02 01:11:03 +02:00
parent 821fa68a6b
commit b5ba12ef8c
3 changed files with 78 additions and 24 deletions

View File

@@ -34,7 +34,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -84,8 +83,6 @@ public class SVGExporter {
public Map<ExportKey, String> exportedTags = new HashMap<>();
public Set<ExportKey> exportedSmallStrokesTags = new HashSet<>();
public Map<Tag, Map<Integer, String>> exportedChars = new HashMap<>();
private final Map<String, Integer> lastIds = new HashMap<>();
@@ -550,11 +547,11 @@ public class SVGExporter {
_svgGs.peek().appendChild(filtersElement);
}
public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName, RECT scalingRect, boolean hasSmallStrokes) {
return addUse(transform, boundRect, href, instanceName, scalingRect, null, null, BlendMode.NORMAL, new ArrayList<>(), hasSmallStrokes);
public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName, RECT scalingRect) {
return addUse(transform, boundRect, href, instanceName, scalingRect, null, null, BlendMode.NORMAL, new ArrayList<>());
}
public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName, RECT scalingRect, String characterId, String characterName, int blendMode, List<FILTER> filters, boolean hasSmallStrokes) {
public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName, RECT scalingRect, String characterId, String characterName, int blendMode, List<FILTER> filters) {
if (scalingRect != null && (transform == null || (Double.compare(transform.rotateSkew0, 0.0) == 0 && Double.compare(transform.rotateSkew1, 0.0) == 0))) {
addScalingGridUse(transform, boundRect, href, instanceName, scalingRect, characterId, characterName, blendMode, filters);
return null; //??
@@ -574,10 +571,7 @@ public class SVGExporter {
if (characterName != null && !characterName.isEmpty()) {
image.setAttribute("ffdec:characterName", characterName);
}
if (hasSmallStrokes) {
image.setAttribute("ffdec:has-small-stroke", "true");
}
setBlendMode(image, blendMode);
handleFilters(image, filters);

View File

@@ -962,7 +962,7 @@ public abstract class TextTag extends DrawableTag {
}
if (!"".equals(charId)) {
Element charImage = exporter.addUse(mat, bounds, charId, null, null, false);
Element charImage = exporter.addUse(mat, bounds, charId, null, null);
RGBA colorA = new RGBA(textColor);
charImage.setAttribute("fill", colorA.toHexRGB());
if (colorA.alpha != 255) {

View File

@@ -59,18 +59,22 @@ import com.jpexs.decompiler.flash.types.BlendMode;
import com.jpexs.decompiler.flash.types.CLIPACTIONS;
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.LINESTYLE;
import com.jpexs.decompiler.flash.types.LINESTYLE2;
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.MATRIX;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.RGBA;
import com.jpexs.decompiler.flash.types.SOUNDINFO;
import com.jpexs.decompiler.flash.types.filters.BlendComposite;
import com.jpexs.decompiler.flash.types.filters.FILTER;
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.SerializableImage;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
@@ -84,7 +88,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -1585,6 +1588,65 @@ public class Timeline {
g.setClip(prevClip);
}
/**
* Check whether (inner) shape tags have strokes smaller than 1px
*
* @param exporter SVG exporter
* @param drawableTag Drawable tag
* @param matrix Matrix
* @return True if has small strokes
*/
private boolean tagHasSmallStrokes(SVGExporter exporter, DrawableTag drawableTag, Matrix matrix) {
if (drawableTag instanceof Timelined) {
Timelined tim = (Timelined) drawableTag;
for (Frame fr : tim.getTimeline().frames) {
for (DepthState ds : fr.layers.values()) {
Matrix subMat = matrix.concatenate(new Matrix(ds.matrix));
CharacterTag cht = ds.getCharacter();
if ((cht instanceof DrawableTag) && tagHasSmallStrokes(exporter, (DrawableTag) cht, subMat)) {
return true;
}
}
}
}
if (drawableTag instanceof ShapeTag) {
com.jpexs.decompiler.flash.exporters.commonshape.Point p00 = matrix.transform(0, 0);
com.jpexs.decompiler.flash.exporters.commonshape.Point p11 = matrix.transform(1, 1);
double thicknessScale = p00.distanceTo(p11) / Math.sqrt(2);
ShapeTag st = (ShapeTag) drawableTag;
List<LINESTYLEARRAY> lineStyles = new ArrayList<>();
lineStyles.add(st.getShapes().lineStyles);
for (SHAPERECORD rec : st.getShapes().shapeRecords) {
if (rec instanceof StyleChangeRecord) {
StyleChangeRecord scr = (StyleChangeRecord) rec;
if (scr.stateNewStyles) {
lineStyles.add(scr.lineStyles);
}
}
}
for (LINESTYLEARRAY lsArr : lineStyles) {
if (lsArr.lineStyles != null) {
for (LINESTYLE ls : lsArr.lineStyles) {
if (ls.width * exporter.getZoom() * thicknessScale < 1 * SWF.unitDivisor) {
return true;
}
}
}
if (lsArr.lineStyles2 != null) {
for (LINESTYLE2 ls : lsArr.lineStyles2) {
if (ls.width * exporter.getZoom() * thicknessScale < 1 * SWF.unitDivisor) {
return true;
}
}
}
}
}
return false;
}
/**
* Converts specified frame to SVG.
*
@@ -1652,7 +1714,7 @@ public class Timeline {
Matrix layerMatrix = new Matrix(layer.getDrawingMatrix());
Matrix absMat = strokeTransformation.concatenate(layerMatrix);
if (character instanceof DrawableTag) {
DrawableTag drawable = (DrawableTag) character;
@@ -1678,26 +1740,24 @@ public class Timeline {
boolean createNew = false;
SVGExporter.ExportKey exportKey = new SVGExporter.ExportKey(drawableTag, clrTrans, layer.ratio, layer.clipDepth > -1);
if (exporter.exportedTags.containsKey(exportKey)
&& !exporter.exportedSmallStrokesTags.contains(exportKey)) {
boolean hasSmallStroke = tagHasSmallStrokes(exporter, drawable, absMat);
if (!hasSmallStroke && exporter.exportedTags.containsKey(exportKey)) {
assetName = exporter.exportedTags.get(exportKey);
} else {
assetName = getTagIdPrefix(drawableTag, exporter);
exporter.exportedTags.put(exportKey, assetName);
if (!hasSmallStroke) {
exporter.exportedTags.put(exportKey, assetName);
}
createNew = true;
}
boolean hasSmallStrokes = false;
if (createNew) {
exporter.createDefGroup(new ExportRectangle(boundRect), assetName);
drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1, transformation, absMat);
hasSmallStrokes = exporter.hasSmallStrokes();
exporter.endGroup();
exporter.endGroup();
}
Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix));
exporter.addUse(mat, boundRect, assetName, layer.instanceName, scalingGrid == null ? null : scalingGrid.splitter, String.valueOf(drawable.getCharacterId()), String.join("___", drawable.getClassNames()), layer.blendMode, layer.filters, hasSmallStrokes);
if (hasSmallStrokes) {
exporter.exportedSmallStrokesTags.add(exportKey);
}
exporter.addUse(mat, boundRect, assetName, layer.instanceName, scalingGrid == null ? null : scalingGrid.splitter, String.valueOf(drawable.getCharacterId()), String.join("___", drawable.getClassNames()), layer.blendMode, layer.filters);
}
}
}