Fixed Convolution matrix filter display and editing

This commit is contained in:
Jindra Petřík
2023-10-12 20:34:28 +02:00
parent 1d7fecac50
commit ae83087289
26 changed files with 401 additions and 62 deletions

View File

@@ -2219,11 +2219,9 @@ public class SWFInputStream implements AutoCloseable {
ret.matrixY = readUI8("matrixY");
ret.divisor = readFLOAT("divisor");
ret.bias = readFLOAT("bias");
ret.matrix = new float[ret.matrixX][ret.matrixY];
for (int x = 0; x < ret.matrixX; x++) {
for (int y = 0; y < ret.matrixY; y++) {
ret.matrix[x][y] = readFLOAT("cell");
}
ret.matrix = new float[ret.matrixX * ret.matrixY];
for (int i = 0; i < ret.matrixX * ret.matrixY; i++) {
ret.matrix[i] = readFLOAT("cell");
}
ret.defaultColor = readRGBA("defaultColor");
ret.reserved = (int) readUB(6, "reserved");

View File

@@ -896,9 +896,9 @@ public class SWFOutputStream extends OutputStream {
writeUI8(value.matrixY);
writeFLOAT(value.divisor);
writeFLOAT(value.bias);
for (int x = 0; x < value.matrixX; x++) {
for (int y = 0; y < value.matrixY; y++) {
writeFLOAT(value.matrix[x][y]);
for (int y = 0; y < value.matrixY; y++) {
for (int x = 0; x < value.matrixX; x++) {
writeFLOAT(value.matrix[y * value.matrixX + x]);
}
}
writeRGBA(value.defaultColor);

View File

@@ -816,13 +816,11 @@ public class FrameExporter {
}
if (filter instanceof CONVOLUTIONFILTER) {
CONVOLUTIONFILTER cf = (CONVOLUTIONFILTER) filter;
int height = cf.matrix.length;
int width = cf.matrix[0].length;
float[] matrix2 = new float[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
matrix2[y * width + x] = cf.matrix[x][y] * cf.divisor + cf.bias;
CONVOLUTIONFILTER cf = (CONVOLUTIONFILTER) filter;
float[] matrix2 = new float[cf.matrixX * cf.matrixY];
for (int y = 0; y < cf.matrixY; y++) {
for (int x = 0; x < cf.matrixX; x++) {
matrix2[y * cf.matrixX + x] = cf.matrix[y * cf.matrixX + x] / cf.divisor + cf.bias;
}
}
String mat = "[";

View File

@@ -766,10 +766,11 @@ public class Timeline {
int dtime = time - dframe;
ExportRectangle viewRect2 = new ExportRectangle(viewRect);
double deltaXMax = 0;
double deltaYMax = 0;
if (filters != null && filters.size() > 0) {
// calculate size after applying the filters
double deltaXMax = 0;
double deltaYMax = 0;
for (FILTER filter : filters) {
double x = filter.getDeltaX();
double y = filter.getDeltaY();
@@ -786,10 +787,8 @@ public class Timeline {
viewRect2.yMax += deltaYMax * SWF.unitDivisor;
}
rect.xMin -= SWF.unitDivisor;
rect.yMin -= SWF.unitDivisor;
/*rect.xMin = Math.max(0, rect.xMin);
rect.yMin = Math.max(0, rect.yMin);*/
//rect.xMin -= SWF.unitDivisor;
//rect.yMin -= SWF.unitDivisor;
drawMatrix.translate(rect.xMin, rect.yMin);
drawMatrix.translateX /= SWF.unitDivisor;
drawMatrix.translateY /= SWF.unitDivisor;
@@ -800,8 +799,8 @@ public class Timeline {
int newHeight = (int) (rect.getHeight() / SWF.unitDivisor);
int deltaX = (int) (rect.xMin / SWF.unitDivisor);
int deltaY = (int) (rect.yMin / SWF.unitDivisor);
newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1;
newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1;
newWidth = Math.min(image.getWidth() - deltaX, newWidth);
newHeight = Math.min(image.getHeight() - deltaY, newHeight);
if (newWidth <= 0 || newHeight <= 0) {
return;
@@ -888,14 +887,8 @@ public class Timeline {
}
if (filters != null) {
/*try {
ImageIO.write(img.getBufferedImage(), "PNG", new File("c:\\FlashRelated\\gwint\\out.png"));
} catch (IOException ex) {
Logger.getLogger(Timeline.class.getName()).log(Level.SEVERE, null, ex);
}
System.exit(0);*/
for (FILTER filter : filters) {
img = filter.apply(img, unzoom);
img = filter.apply(img, unzoom, (int)deltaXMax, (int)deltaYMax, (int)Math.round(newWidth - 2 * deltaXMax), (int)Math.round(newHeight - 2 * deltaYMax));
}
}
if (blendMode > 1) {

View File

@@ -102,7 +102,7 @@ public class BEVELFILTER extends FILTER {
}
@Override
public SerializableImage apply(SerializableImage src, double zoom) {
public SerializableImage apply(SerializableImage src, double zoom, int srcX, int srcY, int srcW, int srcH) {
int type = Filtering.INNER;
if (onTop && !innerShadow) {
type = Filtering.FULL;

View File

@@ -55,7 +55,7 @@ public class BLURFILTER extends FILTER {
}
@Override
public SerializableImage apply(SerializableImage src, double zoom) {
public SerializableImage apply(SerializableImage src, double zoom, int srcX, int srcY, int srcW, int srcH) {
return Filtering.blur(src, (int) Math.round(blurX * zoom), (int) Math.round(blurY * zoom), passes);
}

View File

@@ -41,7 +41,7 @@ public class COLORMATRIXFILTER extends FILTER {
}
@Override
public SerializableImage apply(SerializableImage src, double zoom) {
public SerializableImage apply(SerializableImage src, double zoom, int srcX, int srcY, int srcW, int srcH) {
float[][] matrix2 = new float[4][5];
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 5; x++) {

View File

@@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.types.RGBA;
import com.jpexs.decompiler.flash.types.annotations.Reserved;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
import com.jpexs.helpers.SerializableImage;
import java.awt.Color;
/**
* Two-dimensional discrete convolution filter.
@@ -33,36 +34,36 @@ public class CONVOLUTIONFILTER extends FILTER {
* Horizontal matrix size
*/
@SWFType(BasicType.UI8)
public int matrixX;
public int matrixX = 3;
/**
* Vertical matrix size
*/
@SWFType(BasicType.UI8)
public int matrixY;
public int matrixY = 3;
/**
* Divisor applied to the matrix values
*/
@SWFType(BasicType.FLOAT)
public float divisor;
public float divisor = 1f;
/**
* Bias applied to the matrix values
*/
@SWFType(BasicType.FLOAT)
public float bias;
public float bias = 0f;
/**
* Matrix values
*/
@SWFType(BasicType.FLOAT)
public float[][] matrix = new float[0][0];
public float[] matrix = new float[9];
/**
* Default color for pixels outside the image
*/
public RGBA defaultColor;
public RGBA defaultColor = new RGBA(Color.BLACK);
@Reserved
@SWFType(value = BasicType.UB, count = 6)
@@ -86,25 +87,17 @@ public class CONVOLUTIONFILTER extends FILTER {
}
@Override
public SerializableImage apply(SerializableImage src, double zoom) {
int height = matrix.length;
int width = matrix[0].length;
float[] matrix2 = new float[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
matrix2[y * width + x] = matrix[x][y] * divisor + bias;
}
}
return Filtering.convolution(src, matrix2, width, height);
public SerializableImage apply(SerializableImage src, double zoom, int srcX, int srcY, int srcW, int srcH) {
return Filtering.convolution(src, matrix, matrixX, matrixY, divisor, bias, defaultColor.toColor(), clamp, preserveAlpha, srcX, srcY, srcW, srcH);
}
@Override
public double getDeltaX() {
return 0;
return ((matrixX-1)>>1) + 1;
}
@Override
public double getDeltaY() {
return 0;
return ((matrixY-1)>>1) + 1;
}
}

View File

@@ -0,0 +1,269 @@
package com.jpexs.decompiler.flash.types.filters;
import java.awt.Color;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.ImagingOpException;
import java.awt.image.Kernel;
import java.awt.image.Raster;
import java.awt.image.RasterOp;
import java.awt.image.WritableRaster;
/**
* Convolution filter.
*/
public class ConvolveOp implements BufferedImageOp, RasterOp {
private final Kernel kernel;
private final RenderingHints hints;
private final boolean preserveAlpha;
private final float bias;
private final Color defaultColor;
private final boolean clamp;
private final float divisor;
private final int srcX;
private final int srcY;
private final int srcWidth;
private final int srcHeight;
public ConvolveOp(Kernel kernel,
RenderingHints hints,
float divisor,
float bias,
Color defaultColor,
boolean clamp,
boolean preserveAlpha,
int srcX,
int srcY,
int srcWidth,
int srcHeight
) {
this.kernel = kernel;
this.hints = hints;
this.bias = bias;
this.defaultColor = defaultColor;
this.clamp = clamp;
this.preserveAlpha = preserveAlpha;
this.divisor = divisor;
this.srcX = srcX;
this.srcY = srcY;
this.srcWidth = srcWidth;
this.srcHeight = srcHeight;
}
@Override
public final BufferedImage filter(BufferedImage src, BufferedImage dst) {
if (src == dst) {
throw new IllegalArgumentException("Source and destination images "
+ "cannot be the same.");
}
if (dst == null) {
dst = createCompatibleDestImage(src, src.getColorModel());
}
BufferedImage src1 = src;
BufferedImage dst1 = dst;
if (src1.getColorModel().getColorSpace().getType() != dst.getColorModel().getColorSpace().getType()) {
dst1 = createCompatibleDestImage(src, src.getColorModel());
}
filter(src1.getRaster(), dst1.getRaster());
if (dst1 != dst) {
new ColorConvertOp(hints).filter(dst1, dst);
}
return dst;
}
@Override
public BufferedImage createCompatibleDestImage(BufferedImage src,
ColorModel dstCM) {
if (dstCM != null) {
return new BufferedImage(dstCM,
src.getRaster().createCompatibleWritableRaster(),
src.isAlphaPremultiplied(), null);
}
return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
}
@Override
public final RenderingHints getRenderingHints() {
return hints;
}
public final Kernel getKernel() {
return (Kernel) kernel.clone();
}
@Override
public final WritableRaster filter(Raster src, WritableRaster dest) {
if (src == dest) {
throw new IllegalArgumentException("src == dest is not allowed.");
}
if (dest == null) {
dest = createCompatibleDestRaster(src);
} else if (src.getNumBands() != dest.getNumBands()) {
throw new ImagingOpException("src and dest have different band counts.");
}
int kWidth = kernel.getWidth();
int kHeight = kernel.getHeight();
int left = kernel.getXOrigin();
int top = kernel.getYOrigin();
int[] maxValue = src.getSampleModel().getSampleSize();
for (int i = 0; i < maxValue.length; i++) {
maxValue[i] = (int) Math.pow(2, maxValue[i]) - 1;
}
float[] kvals = kernel.getKernelData(null);
for (int x = srcX - left; x < srcX + srcWidth + left + 1; x++) {
for (int y = srcY - top; y < srcY + srcHeight + top + 1; y++) {
float a;
if (preserveAlpha && (x < srcX || y < srcY || x >= srcX + srcWidth + 1 || y >= srcY + srcHeight + 1)) {
dest.setSample(x, y, 3, 255);
a = 255;
} else {
if (preserveAlpha) {
a = src.getSample(x, y, 3);
dest.setSample(x, y, 3, a);
} else {
a = calculateBand(src, dest, maxValue, kvals, kWidth, kHeight, left, top, x, y, 3, 255f, false);
}
}
for (int b = 0; b < 3; b++) {
calculateBand(src, dest, maxValue, kvals, kWidth, kHeight, left, top, x, y, b, a, true);
}
}
}
return dest;
}
private float calculateBand(
Raster src,
WritableRaster dest,
int[] maxValue,
float[] kvals,
int kWidth,
int kHeight,
int left,
int top,
int x,
int y,
int b,
float alpha,
boolean multiply
) {
float nv = 0;
for (int i = 0; i < kHeight; i++) {
for (int j = 0; j < kWidth; j++) {
int nSrcX = x - left + j;
int nSrcY = y - top + i;
boolean outSide = false;
if (nSrcX < srcX) {
nSrcX = srcX;
outSide = true;
}
if (nSrcX >= srcX + srcWidth + 1) {
nSrcX = srcX + srcWidth - 1;
outSide = true;
}
if (nSrcY < srcY) {
nSrcY = srcY;
outSide = true;
}
if (nSrcY >= srcY + srcHeight + 1) {
nSrcY = srcY + srcHeight - 1;
outSide = true;
}
float v = 0;
if (outSide && !clamp) {
switch (b) {
case 0:
v = defaultColor.getRed() * maxValue[0] / 255f;
break;
case 1:
v = defaultColor.getGreen() * maxValue[1] / 255f;
break;
case 2:
v = defaultColor.getBlue() * maxValue[2] / 255f;
break;
case 3:
v = defaultColor.getAlpha() * maxValue[3] / 255f;
break;
}
} else {
int srcRealX = nSrcX;
int srcRealY = nSrcY;
v = src.getSample(srcRealX, srcRealY, b);
if (multiply) {
float sa = src.getSample(srcRealX, srcRealY, 3);
if (sa == 0f) {
v = 0;
} else {
v = v * 255f / sa;
}
}
}
nv += v * kvals[i * kWidth + j];
}
}
nv /= divisor;
nv += bias;
if (nv > maxValue[b]) {
nv = maxValue[b];
} else if (nv < 0) {
nv = 0;
}
if (multiply) {
nv = nv * alpha / 255f;
}
nv = Math.round(nv);
int destX = x;
int destY = y;
dest.setSample(destX, destY, b, nv);
return nv;
}
@Override
public WritableRaster createCompatibleDestRaster(Raster src) {
return src.createCompatibleWritableRaster();
}
@Override
public final Rectangle2D getBounds2D(BufferedImage src) {
return src.getRaster().getBounds();
}
@Override
public final Rectangle2D getBounds2D(Raster src) {
return src.getBounds();
}
@Override
public final Point2D getPoint2D(Point2D src, Point2D dst) {
if (dst == null) {
return (Point2D) src.clone();
}
dst.setLocation(src);
return dst;
}
}

View File

@@ -92,7 +92,7 @@ public class DROPSHADOWFILTER extends FILTER {
}
@Override
public SerializableImage apply(SerializableImage src, double zoom) {
public SerializableImage apply(SerializableImage src, double zoom, int srcX, int srcY, int srcW, int srcH) {
return Filtering.dropShadow(src, (int) Math.round(blurX * zoom), (int) Math.round(blurY * zoom), (int) (angle * 180 / Math.PI), distance * zoom, dropShadowColor.toColor(), innerShadow, passes, strength, knockout, compositeSource);
}

View File

@@ -45,7 +45,7 @@ public abstract class FILTER implements Serializable {
this.id = id;
}
public abstract SerializableImage apply(SerializableImage src, double zoom);
public abstract SerializableImage apply(SerializableImage src, double zoom, int srcX, int srcY, int srcW, int srcH);
public abstract double getDeltaX();

View File

@@ -28,7 +28,6 @@ import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.DataBufferInt;
import java.awt.image.Kernel;
@@ -559,9 +558,36 @@ public class Filtering {
return getRGB(retImg);
}
public static SerializableImage convolution(SerializableImage src, float[] matrix, int w, int h) {
BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
BufferedImageOp op = new ConvolveOp(new Kernel(w, h, matrix), ConvolveOp.EDGE_ZERO_FILL, new RenderingHints(null));
public static SerializableImage convolution(
SerializableImage src,
float[] matrix,
int w,
int h,
float divisor,
float bias,
Color defaultColor,
boolean clamp,
boolean preserveAlpha,
int srcX,
int srcY,
int srcWidth,
int srcHeight
) {
Kernel kernel = new Kernel(w, h, matrix);
BufferedImage dst = new BufferedImage(src.getWidth() + 1, src.getHeight() + 1, src.getType());
BufferedImageOp op = new ConvolveOp(
kernel,
new RenderingHints(null),
divisor,
bias,
defaultColor,
clamp,
preserveAlpha,
srcX,
srcY,
srcWidth,
srcHeight
);
op.filter(src.getBufferedImage(), dst);
return new SerializableImage(dst);
}

View File

@@ -80,7 +80,7 @@ public class GLOWFILTER extends FILTER {
}
@Override
public SerializableImage apply(SerializableImage src, double zoom) {
public SerializableImage apply(SerializableImage src, double zoom, int srcX, int srcY, int srcW, int srcH) {
return Filtering.glow(src, (int) Math.round(blurX * zoom), (int) Math.round(blurY * zoom), strength, glowColor.toColor(), innerGlow, knockout, passes);
}

View File

@@ -104,7 +104,7 @@ public class GRADIENTBEVELFILTER extends FILTER {
}
@Override
public SerializableImage apply(SerializableImage src, double zoom) {
public SerializableImage apply(SerializableImage src, double zoom, int srcX, int srcY, int srcW, int srcH) {
List<Color> colors = new ArrayList<>();
List<Float> ratios = new ArrayList<>();
for (int i = 0; i < gradientColors.length; i++) {

View File

@@ -107,7 +107,7 @@ public class GRADIENTGLOWFILTER extends FILTER {
}
@Override
public SerializableImage apply(SerializableImage src, double zoom) {
public SerializableImage apply(SerializableImage src, double zoom, int srcX, int srcY, int srcW, int srcH) {
List<Color> colors = new ArrayList<>();
List<Float> ratios = new ArrayList<>();
for (int i = 0; i < gradientColors.length; i++) {