Added Option to fix conflation artifacts in antialising (Default off - it is slow)

This commit is contained in:
Jindra Petřík
2022-12-08 23:04:52 +01:00
parent 2f95902341
commit 149dba53fa
12 changed files with 151 additions and 20 deletions

View File

@@ -827,6 +827,10 @@ public final class Configuration {
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> playFrameSounds = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> fixAntialiasConflation = null;
private enum OSId {
WINDOWS, OSX, UNIX
}

View File

@@ -45,6 +45,7 @@ import java.awt.Shape;
import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
@@ -74,6 +75,8 @@ public class BitmapExporter extends ShapeExporterBase {
private final SWF swf;
private final GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); //For correct intersections display;
private Shape aliasedShape;
private Paint fillPaint;
@@ -104,6 +107,13 @@ public class BitmapExporter extends ShapeExporterBase {
private static boolean linearGradientColorWarnignShown = false;
private boolean scaleStrokes;
private boolean aliasedFill = false;
@Override
public void beginAliasedFills() {
aliasedFill = true;
}
private class TransformedStroke implements Stroke {
@@ -211,6 +221,7 @@ public class BitmapExporter extends ShapeExporterBase {
@Override
public void beginFills() {
aliasedFill = false;
}
@Override
@@ -597,12 +608,21 @@ public class BitmapExporter extends ShapeExporterBase {
}
protected void finalizePath() {
if (fillPaint != null) {
if (fillPaint != null) {
Shape shp = path;
if (aliasedFill) {
aliasedShape = new BasicStroke((float)(SWF.unitDivisor / unzoom / 2), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND).createStrokedShape(shp);
return;
} else if (aliasedShape != null) {
Area a = new Area(shp);
a.add(new Area(aliasedShape));
shp = a;
}
graphics.setComposite(AlphaComposite.SrcOver);
if (fillPaint instanceof MultipleGradientPaint) {
AffineTransform oldAf = graphics.getTransform();
Shape prevClip = graphics.getClip();
graphics.clip(path);
graphics.clip(shp);
Matrix inverse = null;
try {
double scx = fillTransform.getScaleX();
@@ -627,7 +647,7 @@ public class BitmapExporter extends ShapeExporterBase {
graphics.setPaint(fillPaint);
if (inverse != null) {
ExportRectangle rect = inverse.transform(new ExportRectangle(path.getBounds2D()));
ExportRectangle rect = inverse.transform(new ExportRectangle(shp.getBounds2D()));
double minX = rect.xMin;
double minY = rect.yMin;
graphics.fill(new java.awt.Rectangle((int) minX, (int) minY, (int) (rect.xMax - minX), (int) (rect.yMax - minY)));
@@ -638,7 +658,7 @@ public class BitmapExporter extends ShapeExporterBase {
} else if (fillPaint instanceof TexturePaint) {
AffineTransform oldAf = graphics.getTransform();
Shape prevClip = graphics.getClip();
graphics.clip(path);
graphics.clip(shp);
Matrix inverse = null;
if (fillRepeat) {
try {
@@ -671,7 +691,7 @@ public class BitmapExporter extends ShapeExporterBase {
if (fillRepeat) {
if (inverse != null) {
ExportRectangle rect = inverse.transform(new ExportRectangle(path.getBounds2D()));
ExportRectangle rect = inverse.transform(new ExportRectangle(shp.getBounds2D()));
double minX = rect.xMin;
double minY = rect.yMin;
graphics.fill(new Rectangle((int) minX, (int) minY, (int) (rect.xMax - minX), (int) (rect.yMax - minY)));
@@ -685,10 +705,13 @@ public class BitmapExporter extends ShapeExporterBase {
graphics.setClip(prevClip);
} else {
graphics.setPaint(fillPaint);
graphics.fill(path);
graphics.fill(shp);
}
}
if (linePaint != null && lineStroke != null) {
if(true) {
//return;
}
Shape strokedShape = lineStroke.createStrokedShape(path);
graphics.setComposite(AlphaComposite.SrcOver);
if (linePaint instanceof MultipleGradientPaint) {

View File

@@ -81,6 +81,8 @@ public class CanvasShapeExporter extends ShapeExporterBase {
protected int fillWidth = 0;
protected int fillHeight = 0;
protected boolean aliasedFill = false;
public static String getJsPrefix() {
return "<script>var canvas=document.getElementById(\"myCanvas\");\r\n"
@@ -168,6 +170,7 @@ public class CanvasShapeExporter extends ShapeExporterBase {
@Override
public void beginFills() {
aliasedFill = false;
}
@Override
@@ -521,4 +524,9 @@ public class CanvasShapeExporter extends ShapeExporterBase {
fillWidth = 0;
fillHeight = 0;
}
@Override
public void beginAliasedFills() {
aliasedFill = true;
}
}

View File

@@ -38,6 +38,8 @@ public abstract class DefaultSVGShapeExporter extends ShapeExporterBase {
protected StringBuilder pathData;
protected double zoom;
protected boolean aliasedFill;
public DefaultSVGShapeExporter(int shapeNum, SWF swf, SHAPE shape, ColorTransform colorTransform, double zoom) {
super(shapeNum, swf, shape, colorTransform);
@@ -54,6 +56,7 @@ public abstract class DefaultSVGShapeExporter extends ShapeExporterBase {
@Override
public void beginFills() {
aliasedFill = false;
}
@Override
@@ -75,21 +78,33 @@ public abstract class DefaultSVGShapeExporter extends ShapeExporterBase {
@Override
public void beginFill(RGB color) {
if (aliasedFill) {
return;
}
finalizePath();
}
@Override
public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) {
if (aliasedFill) {
return;
}
finalizePath();
}
@Override
public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) {
if (aliasedFill) {
return;
}
finalizePath();
}
@Override
public void endFill() {
if (aliasedFill) {
return;
}
finalizePath();
}
@@ -142,4 +157,11 @@ public abstract class DefaultSVGShapeExporter extends ShapeExporterBase {
protected double roundPixels20(double pixels) {
return Math.round(pixels * 100) / 100.0;
}
@Override
public void beginAliasedFills() {
aliasedFill = true;
}
}

View File

@@ -30,7 +30,9 @@ public interface IShapeExporter {
public void beginShape();
public void endShape();
public void beginAliasedFills();
public void beginFills();
public void endFills();

View File

@@ -38,6 +38,8 @@ public class PathExporter extends ShapeExporterBase {
private final List<GeneralPath> strokes = new ArrayList<>();
private double thickness = 0;
private boolean aliasedFill = false;
private GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
@@ -73,7 +75,7 @@ public class PathExporter extends ShapeExporterBase {
@Override
public void beginFills() {
aliasedFill = false;
}
@Override
@@ -151,4 +153,9 @@ public class PathExporter extends ShapeExporterBase {
paths.add(path);
path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); //For correct intersections display
}
@Override
public void beginAliasedFills() {
aliasedFill = true;
}
}

View File

@@ -50,7 +50,7 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter {
private final SWF swf;
private final SVGExporter exporter;
private final SVGExporter exporter;
public SVGShapeExporter(int shapeNum, SWF swf, SHAPE shape, int id, SVGExporter exporter, Color defaultColor, ColorTransform colorTransform, double zoom) {
super(shapeNum, swf, shape, colorTransform, zoom);
@@ -58,10 +58,13 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter {
this.id = id;
this.defaultColor = defaultColor;
this.exporter = exporter;
}
}
@Override
public void beginFill(RGB color) {
if (aliasedFill) {
return;
}
if (color == null && defaultColor != null) {
color = new RGB(defaultColor);
}
@@ -81,6 +84,9 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter {
@Override
public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) {
if (aliasedFill) {
return;
}
finalizePath();
Element gradient = (type == FILLSTYLE.LINEAR_GRADIENT)
? exporter.createElement("linearGradient")
@@ -141,6 +147,9 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter {
@Override
public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) {
if (aliasedFill) {
return;
}
finalizePath();
String patternId = getPattern(bitmapId, matrix, colorTransform);
if (patternId != null) {

View File

@@ -32,5 +32,7 @@ public class ShapeExportData {
public List<List<IEdge>> fillPaths;
public List<List<IEdge>> aliasedFillPaths;
public List<List<IEdge>> linePaths;
}

View File

@@ -17,6 +17,7 @@
package com.jpexs.decompiler.flash.exporters.shape;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.commonshape.FillStyle;
import com.jpexs.decompiler.flash.exporters.commonshape.LineStyle;
import com.jpexs.decompiler.flash.types.ColorTransform;
@@ -39,6 +40,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import sun.jvm.hotspot.interpreter.Bytecodes;
/**
*
@@ -55,6 +57,8 @@ public abstract class ShapeExporterBase implements IShapeExporter {
private final List<LineStyle> _lineStyles;
private final List<List<IEdge>> _fillPaths;
private final List<List<IEdge>> _aliasedFillPaths;
private final List<List<IEdge>> _linePaths;
@@ -88,22 +92,26 @@ public abstract class ShapeExporterBase implements IShapeExporter {
// Create edge maps
List<Map<Integer, List<IEdge>>> fillEdgeMaps = new ArrayList<>();
List<Map<Integer, List<IEdge>>> aliasedFillEdgeMaps = new ArrayList<>();
List<Map<Integer, List<IEdge>>> lineEdgeMaps = new ArrayList<>();
try {
createEdgeMaps(shapeNum, shape, fillStyles, lineStyles, fillEdgeMaps, lineEdgeMaps);
createEdgeMaps(shapeNum, shape, fillStyles, lineStyles, fillEdgeMaps, aliasedFillEdgeMaps, lineEdgeMaps);
} catch (Throwable t) {
t.printStackTrace();
}
int count = lineEdgeMaps.size();
List<List<IEdge>> fillPaths = new ArrayList<>(count);
List<List<IEdge>> aliasedFillPaths = new ArrayList<>(count);
List<List<IEdge>> linePaths = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
fillPaths.add(createPathFromEdgeMap(fillEdgeMaps.get(i)));
aliasedFillPaths.add(createPathFromEdgeMap(aliasedFillEdgeMaps.get(i)));
linePaths.add(createPathFromEdgeMap(lineEdgeMaps.get(i)));
}
cachedData = new ShapeExportData();
cachedData.fillPaths = fillPaths;
cachedData.aliasedFillPaths = aliasedFillPaths;
cachedData.linePaths = linePaths;
cachedData.fillStyles = fillStyles;
cachedData.lineStyles = lineStyles;
@@ -113,6 +121,7 @@ public abstract class ShapeExporterBase implements IShapeExporter {
_fillStyles = cachedData.fillStyles;
_lineStyles = cachedData.lineStyles;
_fillPaths = cachedData.fillPaths;
_aliasedFillPaths = cachedData.aliasedFillPaths;
_linePaths = cachedData.linePaths;
}
@@ -121,8 +130,12 @@ public abstract class ShapeExporterBase implements IShapeExporter {
beginShape();
// Export fills and strokes for each group separately
for (int i = 0; i < _linePaths.size(); i++) {
if (Configuration.fixAntialiasConflation.get()) {
exportFillPath(_aliasedFillPaths.get(i), true);
}
// Export fills first
exportFillPath(_fillPaths.get(i));
exportFillPath(_fillPaths.get(i), false);
// Export strokes last
exportLinePath(_linePaths.get(i));
}
@@ -131,7 +144,7 @@ public abstract class ShapeExporterBase implements IShapeExporter {
}
private void createEdgeMaps(int shapeNum, SHAPE shape, List<FillStyle> fillStyles, List<LineStyle> lineStyles,
List<Map<Integer, List<IEdge>>> fillEdgeMaps, List<Map<Integer, List<IEdge>>> lineEdgeMaps) {
List<Map<Integer, List<IEdge>>> fillEdgeMaps, List<Map<Integer, List<IEdge>>> aliasedFillEdgeMaps, List<Map<Integer, List<IEdge>>> lineEdgeMaps) {
int xPos = 0;
int yPos = 0;
int fillStyleIdxOffset = 0;
@@ -142,13 +155,14 @@ public abstract class ShapeExporterBase implements IShapeExporter {
List<IEdge> subPath = new ArrayList<>();
Map<Integer, List<IEdge>> currentFillEdgeMap = new HashMap<>();
Map<Integer, List<IEdge>> currentLineEdgeMap = new HashMap<>();
Map<Integer, List<IEdge>> currentAliasedFillEdgeMap = 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);
processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap, currentAliasedFillEdgeMap);
subPath = new ArrayList<>();
}
if (styleChangeRecord.stateNewStyles) {
@@ -167,10 +181,13 @@ public abstract class ShapeExporterBase implements IShapeExporter {
&& styleChangeRecord.stateFillStyle0 && styleChangeRecord.fillStyle0 == 0
&& styleChangeRecord.stateFillStyle1 && styleChangeRecord.fillStyle1 == 0) {
cleanEdgeMap(currentFillEdgeMap);
cleanEdgeMap(currentAliasedFillEdgeMap);
cleanEdgeMap(currentLineEdgeMap);
fillEdgeMaps.add(currentFillEdgeMap);
aliasedFillEdgeMaps.add(currentAliasedFillEdgeMap);
lineEdgeMaps.add(currentLineEdgeMap);
currentFillEdgeMap = new HashMap<>();
currentAliasedFillEdgeMap = new HashMap<>();
currentLineEdgeMap = new HashMap<>();
currentLineStyleIdx = 0;
currentFillStyleIdx0 = 0;
@@ -223,37 +240,62 @@ public abstract class ShapeExporterBase implements IShapeExporter {
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);
processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap, currentAliasedFillEdgeMap);
cleanEdgeMap(currentFillEdgeMap);
cleanEdgeMap(currentLineEdgeMap);
cleanEdgeMap(currentAliasedFillEdgeMap);
cleanEdgeMap(currentLineEdgeMap);
fillEdgeMaps.add(currentFillEdgeMap);
aliasedFillEdgeMaps.add(currentAliasedFillEdgeMap);
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) {
Map<Integer, List<IEdge>> currentFillEdgeMap, Map<Integer, List<IEdge>> currentLineEdgeMap, Map<Integer, List<IEdge>> currentAliasedFillEdgeMap) {
List<IEdge> path;
List<IEdge> apath = null;
boolean bothFillStyles = fillStyleIdx0 !=0 && fillStyleIdx1 != 0;
if (fillStyleIdx0 != 0) {
path = currentFillEdgeMap.get(fillStyleIdx0);
if (bothFillStyles) {
apath = currentAliasedFillEdgeMap.get(fillStyleIdx0);
}
if (path == null) {
path = new ArrayList<>();
currentFillEdgeMap.put(fillStyleIdx0, path);
}
if (bothFillStyles && apath == null) {
apath = new ArrayList<>();
currentAliasedFillEdgeMap.put(fillStyleIdx0, apath);
}
for (int j = subPath.size() - 1; j >= 0; j--) {
IEdge rev = subPath.get(j).reverseWithNewFillStyle(fillStyleIdx0);
//System.err.println("appending reversed " + rev);
path.add(rev);
if (bothFillStyles) {
apath.add(rev);
}
}
}
if (fillStyleIdx1 != 0) {
/*if (bothFillStyles) {
apath = currentAliasedFillEdgeMap.get(fillStyleIdx1);
}*/
path = currentFillEdgeMap.get(fillStyleIdx1);
if (path == null) {
path = new ArrayList<>();
currentFillEdgeMap.put(fillStyleIdx1, path);
}
/*if (bothFillStyles && apath == null) {
apath = new ArrayList<>();
currentAliasedFillEdgeMap.put(fillStyleIdx1, apath);
}*/
appendEdges(path, subPath);
/*if (bothFillStyles) {
appendEdges(apath, subPath);
}*/
}
if (lineStyleIdx != 0) {
path = currentLineEdgeMap.get(lineStyleIdx);
@@ -265,12 +307,16 @@ public abstract class ShapeExporterBase implements IShapeExporter {
}
}
private void exportFillPath(List<IEdge> path) {
private void exportFillPath(List<IEdge> path, boolean aliased) {
int posX = Integer.MAX_VALUE;
int posY = Integer.MAX_VALUE;
int fillStyleIdx = Integer.MAX_VALUE;
if (path.size() > 0) {
beginFills();
if (aliased) {
beginAliasedFills();
} else {
beginFills();
}
for (int i = 0; i < path.size(); i++) {
IEdge e = path.get(i);
if (fillStyleIdx != e.getFillStyleIdx()) {