mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-07-04 20:55:57 +00:00
Added: SVG export - Gradient bevel filter
Fixed SVG blur kernelUnitLength
This commit is contained in:
@@ -564,6 +564,11 @@ public class SVGExporter implements RequiresNormalizedFonts {
|
||||
return;
|
||||
}
|
||||
Element filtersElement = _svg.createElement("filter");
|
||||
filtersElement.setAttribute("x", "-100%");
|
||||
filtersElement.setAttribute("y", "-100%");
|
||||
filtersElement.setAttribute("width", "300%");
|
||||
filtersElement.setAttribute("height", "300%");
|
||||
filtersElement.setAttribute("color-interpolation-filters", "sRGB");
|
||||
String filterId = getUniqueId("filter");
|
||||
String in = "SourceGraphic";
|
||||
boolean empty = true;
|
||||
|
||||
@@ -129,130 +129,11 @@ public class BEVELFILTER extends FILTER {
|
||||
|
||||
@Override
|
||||
public String toSvg(Document document, Element filtersElement, SVGExporter exporter, String in) {
|
||||
int type = Filtering.INNER;
|
||||
if (onTop && !innerShadow) {
|
||||
type = Filtering.FULL;
|
||||
} else if (!innerShadow) {
|
||||
type = Filtering.OUTER;
|
||||
}
|
||||
|
||||
String shadowInner = null;
|
||||
String hilightInner = null;
|
||||
if (type != Filtering.OUTER) {
|
||||
String hilight = dropShadowSvg(distance, angle, highlightColor, true, true, true, 0, 0, strength, passes, document, filtersElement, exporter, in);
|
||||
String shadow = dropShadowSvg(distance, angle + Math.PI, shadowColor, true, true, true, 0, 0, strength, passes, document, filtersElement, exporter, in);
|
||||
|
||||
Element feComposite1 = document.createElement("feComposite");
|
||||
feComposite1.setAttribute("in", hilight);
|
||||
feComposite1.setAttribute("in2", shadow);
|
||||
feComposite1.setAttribute("operator", "out");
|
||||
hilightInner = exporter.getUniqueId("filterResult");
|
||||
feComposite1.setAttribute("result", hilightInner);
|
||||
filtersElement.appendChild(feComposite1);
|
||||
|
||||
Element feComposite2 = document.createElement("feComposite");
|
||||
feComposite2.setAttribute("in", shadow);
|
||||
feComposite2.setAttribute("in2", hilight);
|
||||
feComposite2.setAttribute("operator", "out");
|
||||
shadowInner = exporter.getUniqueId("filterResult");
|
||||
feComposite2.setAttribute("result", shadowInner);
|
||||
filtersElement.appendChild(feComposite2);
|
||||
}
|
||||
|
||||
String shadowOuter = null;
|
||||
String hilightOuter = null;
|
||||
|
||||
if (type != Filtering.INNER) {
|
||||
String hilight = dropShadowSvg(distance, angle + Math.PI, highlightColor, false, true, true, 0, 0, strength, passes, document, filtersElement, exporter, in);
|
||||
String shadow = dropShadowSvg(distance, angle, shadowColor, false, true, true, 0, 0, strength, passes, document, filtersElement, exporter, in);
|
||||
|
||||
Element feComposite1 = document.createElement("feComposite");
|
||||
feComposite1.setAttribute("in", hilight);
|
||||
feComposite1.setAttribute("in2", shadow);
|
||||
feComposite1.setAttribute("operator", "out");
|
||||
shadowOuter = exporter.getUniqueId("filterResult");
|
||||
feComposite1.setAttribute("result", shadowOuter);
|
||||
filtersElement.appendChild(feComposite1);
|
||||
|
||||
Element feComposite2 = document.createElement("feComposite");
|
||||
feComposite2.setAttribute("in", shadow);
|
||||
feComposite2.setAttribute("in2", hilight);
|
||||
feComposite2.setAttribute("operator", "out");
|
||||
hilightOuter = exporter.getUniqueId("filterResult");
|
||||
feComposite2.setAttribute("result", hilightOuter);
|
||||
filtersElement.appendChild(feComposite2);
|
||||
}
|
||||
|
||||
String hilight = null;
|
||||
String shadow = null;
|
||||
|
||||
switch (type) {
|
||||
case Filtering.OUTER:
|
||||
hilight = hilightOuter;
|
||||
shadow = shadowOuter;
|
||||
break;
|
||||
case Filtering.INNER:
|
||||
hilight = hilightInner;
|
||||
shadow = shadowInner;
|
||||
break;
|
||||
case Filtering.FULL:
|
||||
Element feComposite1 = document.createElement("feComposite");
|
||||
feComposite1.setAttribute("in", hilightInner);
|
||||
feComposite1.setAttribute("in2", hilightOuter);
|
||||
feComposite1.setAttribute("operator", "over");
|
||||
hilight = exporter.getUniqueId("filterResult");
|
||||
feComposite1.setAttribute("result", hilight);
|
||||
filtersElement.appendChild(feComposite1);
|
||||
|
||||
Element feComposite2 = document.createElement("feComposite");
|
||||
feComposite2.setAttribute("in", shadowInner);
|
||||
feComposite2.setAttribute("in2", shadowOuter);
|
||||
feComposite2.setAttribute("operator", "over");
|
||||
shadow = exporter.getUniqueId("filterResult");
|
||||
feComposite2.setAttribute("result", shadow);
|
||||
filtersElement.appendChild(feComposite2);
|
||||
break;
|
||||
}
|
||||
|
||||
Element feComposite3 = document.createElement("feComposite");
|
||||
feComposite3.setAttribute("in", shadow);
|
||||
feComposite3.setAttribute("in2", hilight);
|
||||
feComposite3.setAttribute("operator", "over");
|
||||
String result = exporter.getUniqueId("filterResult");
|
||||
feComposite3.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComposite3);
|
||||
|
||||
result = blurSvg(blurX, blurY, passes, document, filtersElement, exporter, result);
|
||||
|
||||
if (type == Filtering.INNER) {
|
||||
Element feComposite4 = document.createElement("feComposite");
|
||||
feComposite4.setAttribute("in", result);
|
||||
feComposite4.setAttribute("in2", in);
|
||||
feComposite4.setAttribute("operator", "in");
|
||||
result = exporter.getUniqueId("filterResult");
|
||||
feComposite4.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComposite4);
|
||||
}
|
||||
if (type == Filtering.OUTER) {
|
||||
Element feComposite4 = document.createElement("feComposite");
|
||||
feComposite4.setAttribute("in", result);
|
||||
feComposite4.setAttribute("in2", in);
|
||||
feComposite4.setAttribute("operator", "out");
|
||||
result = exporter.getUniqueId("filterResult");
|
||||
feComposite4.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComposite4);
|
||||
}
|
||||
|
||||
if (!knockout) {
|
||||
Element feComposite4 = document.createElement("feComposite");
|
||||
feComposite4.setAttribute("in", result);
|
||||
feComposite4.setAttribute("in2", in);
|
||||
feComposite4.setAttribute("operator", "over");
|
||||
result = exporter.getUniqueId("filterResult");
|
||||
feComposite4.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComposite4);
|
||||
}
|
||||
return result;
|
||||
RGBA shadowColorTransparent = new RGBA(shadowColor);
|
||||
shadowColorTransparent.alpha = 0;
|
||||
RGBA highlightColorTransparent = new RGBA(highlightColor);
|
||||
highlightColorTransparent.alpha = 0;
|
||||
return bevelSvg(distance, angle, new RGBA[]{shadowColor, shadowColorTransparent, highlightColorTransparent, highlightColor}, new int[]{0, 127, 128, 255}, knockout, onTop, innerShadow, blurX, blurY, strength, passes, document, filtersElement, exporter, in);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,9 +23,12 @@ import com.jpexs.decompiler.flash.types.RGBA;
|
||||
import com.jpexs.decompiler.flash.types.annotations.Internal;
|
||||
import com.jpexs.decompiler.flash.types.annotations.SWFType;
|
||||
import com.jpexs.helpers.ConcreteClasses;
|
||||
import com.jpexs.helpers.GradientUtil;
|
||||
import com.jpexs.helpers.SerializableImage;
|
||||
import java.awt.Color;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
@@ -132,11 +135,69 @@ public abstract class FILTER implements Serializable {
|
||||
Element filtersElement,
|
||||
SVGExporter exporter,
|
||||
String in
|
||||
) {
|
||||
return dropShadowSvg(
|
||||
distance,
|
||||
angle,
|
||||
new RGBA[]{dropShadowColor},
|
||||
new int[0],
|
||||
innerShadow,
|
||||
knockout,
|
||||
compositeSource,
|
||||
blurX,
|
||||
blurY,
|
||||
strength,
|
||||
iterations,
|
||||
document,
|
||||
filtersElement,
|
||||
exporter,
|
||||
in
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts drop shadow to SVG.
|
||||
*
|
||||
* @param distance Distance
|
||||
* @param angle Angle
|
||||
* @param dropShadowColor Drop shadow color
|
||||
* @param innerShadow Inner shadow
|
||||
* @param knockout Knockout
|
||||
* @param compositeSource Composite source
|
||||
* @param blurX Blur X
|
||||
* @param blurY Blur Y
|
||||
* @param strength Strength
|
||||
* @param iterations Iterations
|
||||
* @param document Document
|
||||
* @param filtersElement Filters element
|
||||
* @param exporter SVG exporter
|
||||
* @param in Input
|
||||
* @return SVG id of the drop shadow
|
||||
*/
|
||||
protected String dropShadowSvg(
|
||||
double distance,
|
||||
double angle,
|
||||
RGBA[] gradientColors,
|
||||
int[] gradientRatio,
|
||||
boolean innerShadow,
|
||||
boolean knockout,
|
||||
boolean compositeSource,
|
||||
double blurX,
|
||||
double blurY,
|
||||
double strength,
|
||||
int iterations,
|
||||
Document document,
|
||||
Element filtersElement,
|
||||
SVGExporter exporter,
|
||||
String in
|
||||
) {
|
||||
double dx = distance * Math.cos(angle);
|
||||
double dy = distance * Math.sin(angle);
|
||||
|
||||
RGBA dropShadowColor = gradientColors.length == 1 ? gradientColors[0] : new RGBA(0, 0, 0, 255);
|
||||
|
||||
if (innerShadow) {
|
||||
|
||||
Element feFlood = document.createElement("feFlood");
|
||||
feFlood.setAttribute("flood-color", dropShadowColor.toHexRGB());
|
||||
feFlood.setAttribute("flood-opacity", "" + dropShadowColor.getAlphaFloat());
|
||||
@@ -297,12 +358,12 @@ public abstract class FILTER implements Serializable {
|
||||
int orderX = (int) Math.ceil(blurX * exporter.getZoom());
|
||||
int orderY = (int) Math.ceil(blurY * exporter.getZoom());
|
||||
|
||||
if (orderX == 0) {
|
||||
orderX = 1;
|
||||
if (orderX % 2 == 0) {
|
||||
orderX++;
|
||||
}
|
||||
|
||||
if (orderY == 0) {
|
||||
orderY = 1;
|
||||
if (orderY % 2 == 0) {
|
||||
orderY++;
|
||||
}
|
||||
|
||||
double divisor = orderX * orderY;
|
||||
@@ -315,9 +376,10 @@ public abstract class FILTER implements Serializable {
|
||||
element.setAttribute("divisor", "" + divisor);
|
||||
|
||||
element.setAttribute("kernelMatrix", String.join(" ", parts));
|
||||
element.setAttribute("in", in);
|
||||
element.setAttribute("kernelUnitLength", "1");
|
||||
}
|
||||
|
||||
element.setAttribute("in", in);
|
||||
|
||||
String result = exporter.getUniqueId("filterResult");
|
||||
element.setAttribute("result", result);
|
||||
|
||||
@@ -328,6 +390,7 @@ public abstract class FILTER implements Serializable {
|
||||
|
||||
/**
|
||||
* Converts gradient ratios to Java format float ratios.
|
||||
*
|
||||
* @param input 0-255 values
|
||||
* @return 0f - 1f values, strictly increasing
|
||||
*/
|
||||
@@ -368,4 +431,226 @@ public abstract class FILTER implements Serializable {
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
protected String bevelSvg(double distance, double angle, RGBA[] gradientColors, int[] gradientRatio, boolean knockout, boolean onTop, boolean innerShadow, double blurX, double blurY, double strength, int passes, Document document, Element filtersElement, SVGExporter exporter, String in) {
|
||||
RGBA highlightColor = new RGBA(255, 0, 0, 255);
|
||||
RGBA shadowColor = new RGBA(0, 0, 255, 255);
|
||||
|
||||
int type = Filtering.INNER;
|
||||
if (onTop && !innerShadow) {
|
||||
type = Filtering.FULL;
|
||||
} else if (!innerShadow) {
|
||||
type = Filtering.OUTER;
|
||||
}
|
||||
|
||||
String shadowInner = null;
|
||||
String hilightInner = null;
|
||||
if (type != Filtering.OUTER) {
|
||||
String hilight = dropShadowSvg(distance, angle, highlightColor, true, true, true, 0, 0, strength, passes, document, filtersElement, exporter, in);
|
||||
String shadow = dropShadowSvg(distance, angle + Math.PI, shadowColor, true, true, true, 0, 0, strength, passes, document, filtersElement, exporter, in);
|
||||
|
||||
Element feComposite1 = document.createElement("feComposite");
|
||||
feComposite1.setAttribute("in", hilight);
|
||||
feComposite1.setAttribute("in2", shadow);
|
||||
feComposite1.setAttribute("operator", "out");
|
||||
hilightInner = exporter.getUniqueId("filterResult");
|
||||
feComposite1.setAttribute("result", hilightInner);
|
||||
filtersElement.appendChild(feComposite1);
|
||||
|
||||
Element feComposite2 = document.createElement("feComposite");
|
||||
feComposite2.setAttribute("in", shadow);
|
||||
feComposite2.setAttribute("in2", hilight);
|
||||
feComposite2.setAttribute("operator", "out");
|
||||
shadowInner = exporter.getUniqueId("filterResult");
|
||||
feComposite2.setAttribute("result", shadowInner);
|
||||
filtersElement.appendChild(feComposite2);
|
||||
}
|
||||
|
||||
String shadowOuter = null;
|
||||
String hilightOuter = null;
|
||||
|
||||
if (type != Filtering.INNER) {
|
||||
String hilight = dropShadowSvg(distance, angle + Math.PI, highlightColor, false, true, true, 0, 0, strength, passes, document, filtersElement, exporter, in);
|
||||
String shadow = dropShadowSvg(distance, angle, shadowColor, false, true, true, 0, 0, strength, passes, document, filtersElement, exporter, in);
|
||||
|
||||
Element feComposite1 = document.createElement("feComposite");
|
||||
feComposite1.setAttribute("in", hilight);
|
||||
feComposite1.setAttribute("in2", shadow);
|
||||
feComposite1.setAttribute("operator", "out");
|
||||
hilightOuter = exporter.getUniqueId("filterResult");
|
||||
feComposite1.setAttribute("result", hilightOuter);
|
||||
filtersElement.appendChild(feComposite1);
|
||||
|
||||
Element feComposite2 = document.createElement("feComposite");
|
||||
feComposite2.setAttribute("in", shadow);
|
||||
feComposite2.setAttribute("in2", hilight);
|
||||
feComposite2.setAttribute("operator", "out");
|
||||
shadowOuter = exporter.getUniqueId("filterResult");
|
||||
feComposite2.setAttribute("result", shadowOuter);
|
||||
filtersElement.appendChild(feComposite2);
|
||||
}
|
||||
|
||||
String hilight = null;
|
||||
String shadow = null;
|
||||
|
||||
switch (type) {
|
||||
case Filtering.OUTER:
|
||||
hilight = hilightOuter;
|
||||
shadow = shadowOuter;
|
||||
break;
|
||||
case Filtering.INNER:
|
||||
hilight = hilightInner;
|
||||
shadow = shadowInner;
|
||||
break;
|
||||
case Filtering.FULL:
|
||||
Element feComposite1 = document.createElement("feComposite");
|
||||
feComposite1.setAttribute("in", hilightInner);
|
||||
feComposite1.setAttribute("in2", hilightOuter);
|
||||
feComposite1.setAttribute("operator", "over");
|
||||
hilight = exporter.getUniqueId("filterResult");
|
||||
feComposite1.setAttribute("result", hilight);
|
||||
filtersElement.appendChild(feComposite1);
|
||||
|
||||
Element feComposite2 = document.createElement("feComposite");
|
||||
feComposite2.setAttribute("in", shadowInner);
|
||||
feComposite2.setAttribute("in2", shadowOuter);
|
||||
feComposite2.setAttribute("operator", "over");
|
||||
shadow = exporter.getUniqueId("filterResult");
|
||||
feComposite2.setAttribute("result", shadow);
|
||||
filtersElement.appendChild(feComposite2);
|
||||
break;
|
||||
}
|
||||
|
||||
Element feFlood = document.createElement("feFlood");
|
||||
feFlood.setAttribute("flood-color", "black");
|
||||
feFlood.setAttribute("flood-opacity", "1");
|
||||
String black = exporter.getUniqueId("filterResult");
|
||||
feFlood.setAttribute("result", black);
|
||||
filtersElement.appendChild(feFlood);
|
||||
|
||||
String result;
|
||||
|
||||
Element feComposite4 = document.createElement("feComposite");
|
||||
feComposite4.setAttribute("in", shadow);
|
||||
feComposite4.setAttribute("in2", black);
|
||||
feComposite4.setAttribute("operator", "over");
|
||||
result = exporter.getUniqueId("filterResult");
|
||||
feComposite4.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComposite4);
|
||||
|
||||
Element feComposite5 = document.createElement("feComposite");
|
||||
feComposite5.setAttribute("in", hilight);
|
||||
feComposite5.setAttribute("in2", result);
|
||||
feComposite5.setAttribute("operator", "over");
|
||||
result = exporter.getUniqueId("filterResult");
|
||||
feComposite5.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComposite5);
|
||||
|
||||
result = blurSvg(blurX, blurY, passes, document, filtersElement, exporter, result);
|
||||
|
||||
Element feColorMatrix = document.createElement("feColorMatrix");
|
||||
feColorMatrix.setAttribute("type", "matrix");
|
||||
feColorMatrix.setAttribute("in", result);
|
||||
double halfStrength = strength / 2;
|
||||
String matrixRow = "" + halfStrength + " 0 " + (-halfStrength) + " 0 0.5";
|
||||
feColorMatrix.setAttribute("values",
|
||||
matrixRow + " "
|
||||
+ matrixRow + " "
|
||||
+ matrixRow + " "
|
||||
+ matrixRow
|
||||
);
|
||||
result = exporter.getUniqueId("filterResult");
|
||||
feColorMatrix.setAttribute("result", result);
|
||||
filtersElement.appendChild(feColorMatrix);
|
||||
|
||||
result = prepareFeComponentTransfer(gradientColors, gradientRatio, document, filtersElement, exporter, result);
|
||||
|
||||
if (type == Filtering.INNER) {
|
||||
Element feComposite6 = document.createElement("feComposite");
|
||||
feComposite6.setAttribute("in", result);
|
||||
feComposite6.setAttribute("in2", in);
|
||||
feComposite6.setAttribute("operator", "in");
|
||||
result = exporter.getUniqueId("filterResult");
|
||||
feComposite6.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComposite6);
|
||||
}
|
||||
if (type == Filtering.OUTER) {
|
||||
Element feComposite6 = document.createElement("feComposite");
|
||||
feComposite6.setAttribute("in", result);
|
||||
feComposite6.setAttribute("in2", in);
|
||||
feComposite6.setAttribute("operator", "out");
|
||||
result = exporter.getUniqueId("filterResult");
|
||||
feComposite6.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComposite6);
|
||||
}
|
||||
|
||||
if (!knockout) {
|
||||
Element feComposite7 = document.createElement("feComposite");
|
||||
feComposite7.setAttribute("in", result);
|
||||
feComposite7.setAttribute("in2", in);
|
||||
feComposite7.setAttribute("operator", "over");
|
||||
result = exporter.getUniqueId("filterResult");
|
||||
feComposite7.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComposite7);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String prepareFeComponentTransfer(RGBA[] gradientColors, int[] gradientRatio, Document document, Element filtersElement, SVGExporter exporter, String in) {
|
||||
Element feComponentTransfer = document.createElement("feComponentTransfer");
|
||||
feComponentTransfer.setAttribute("in", in);
|
||||
|
||||
List<String> redValues = new ArrayList<>();
|
||||
List<String> greenValues = new ArrayList<>();
|
||||
List<String> blueValues = new ArrayList<>();
|
||||
List<String> alphaValues = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
RGBA color = GradientUtil.colorAt(gradientColors, gradientRatio, i, GradientUtil.ColorInterpolation.SRGB);
|
||||
redValues.add("" + (color.red / 255f));
|
||||
greenValues.add("" + (color.green / 255f));
|
||||
blueValues.add("" + (color.blue / 255f));
|
||||
alphaValues.add("" + color.getAlphaFloat());
|
||||
}
|
||||
|
||||
/*
|
||||
//In case we want to map 128 to center
|
||||
|
||||
for (int i = 0; i < 126; i++) { //126 colors
|
||||
RGBA color = GradientUtil.colorAt(gradientColors, gradientRatio, i * 127f / 125f, GradientUtil.ColorInterpolation.SRGB);
|
||||
redValues.add("" + (color.red / 255f));
|
||||
greenValues.add("" + (color.green / 255f));
|
||||
blueValues.add("" + (color.blue / 255f));
|
||||
alphaValues.add("" + color.getAlphaFloat());
|
||||
}
|
||||
for (int i = 128; i < 256; i++) { //1 center + 126 colors
|
||||
RGBA color = GradientUtil.colorAt(gradientColors, gradientRatio, i, GradientUtil.ColorInterpolation.SRGB);
|
||||
redValues.add("" + (color.red / 255f));
|
||||
greenValues.add("" + (color.green / 255f));
|
||||
blueValues.add("" + (color.blue / 255f));
|
||||
alphaValues.add("" + color.getAlphaFloat());
|
||||
}
|
||||
*/
|
||||
Element feFuncR = document.createElement("feFuncR");
|
||||
feFuncR.setAttribute("type", "table");
|
||||
feFuncR.setAttribute("tableValues", String.join(" ", redValues));
|
||||
Element feFuncG = document.createElement("feFuncG");
|
||||
feFuncG.setAttribute("type", "table");
|
||||
feFuncG.setAttribute("tableValues", String.join(" ", greenValues));
|
||||
Element feFuncB = document.createElement("feFuncB");
|
||||
feFuncB.setAttribute("type", "table");
|
||||
feFuncB.setAttribute("tableValues", String.join(" ", blueValues));
|
||||
Element feFuncA = document.createElement("feFuncA");
|
||||
feFuncA.setAttribute("type", "table");
|
||||
feFuncA.setAttribute("tableValues", String.join(" ", alphaValues));
|
||||
feComponentTransfer.appendChild(feFuncR);
|
||||
feComponentTransfer.appendChild(feFuncG);
|
||||
feComponentTransfer.appendChild(feFuncB);
|
||||
feComponentTransfer.appendChild(feFuncA);
|
||||
|
||||
String result = exporter.getUniqueId("filterResult");
|
||||
feComponentTransfer.setAttribute("result", result);
|
||||
filtersElement.appendChild(feComponentTransfer);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ public class GRADIENTBEVELFILTER extends FILTER {
|
||||
colorsArr[i] = gradientColors[i].toColor();
|
||||
}
|
||||
float[] ratiosArr = convertRatiosToJavaGradient(gradientRatio);
|
||||
|
||||
|
||||
int type = Filtering.INNER;
|
||||
if (onTop && !innerShadow) {
|
||||
type = Filtering.FULL;
|
||||
@@ -143,7 +143,7 @@ public class GRADIENTBEVELFILTER extends FILTER {
|
||||
|
||||
@Override
|
||||
public String toSvg(Document document, Element filtersElement, SVGExporter exporter, String in) {
|
||||
return null; //NOT SUPPORTED
|
||||
return bevelSvg(distance, angle, gradientColors, gradientRatio, knockout, onTop, innerShadow, blurX, blurY, strength, passes, document, filtersElement, exporter, in);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -211,6 +211,5 @@ public class GRADIENTBEVELFILTER extends FILTER {
|
||||
}
|
||||
return Arrays.equals(this.gradientRatio, other.gradientRatio);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
288
libsrc/ffdec_lib/src/com/jpexs/helpers/GradientUtil.java
Normal file
288
libsrc/ffdec_lib/src/com/jpexs/helpers/GradientUtil.java
Normal file
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2026 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.helpers;
|
||||
|
||||
|
||||
import com.jpexs.decompiler.flash.types.RGBA;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class GradientUtil {
|
||||
|
||||
private GradientUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches SVG's color-interpolation attribute.
|
||||
*/
|
||||
public enum ColorInterpolation {
|
||||
/**
|
||||
* Interpolate directly in sRGB-encoded component values (0..255).
|
||||
*/
|
||||
SRGB,
|
||||
/**
|
||||
* Convert sRGB -> linear RGB, interpolate in linear space, convert back
|
||||
* to sRGB.
|
||||
*/
|
||||
LINEAR_RGB
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the gradient color at position k (0..255), where "ratio" defines
|
||||
* stop positions (0..255) and "colors" defines stop colors. The stop lookup
|
||||
* is identical for both modes; only interpolation differs.
|
||||
*
|
||||
* Assumptions: - ratio[] is sorted ascending (0..255). - colors.length ==
|
||||
* ratio.length.
|
||||
*/
|
||||
public static RGBA colorAt(RGBA[] colors, int[] ratio, float k, ColorInterpolation mode) {
|
||||
if (colors == null || ratio == null || colors.length == 0 || colors.length != ratio.length) {
|
||||
throw new IllegalArgumentException("colors and ratio must be non-empty and have the same length.");
|
||||
}
|
||||
|
||||
// Clamp query position into [0, 255]
|
||||
k = clamp(k, 0f, 255f);
|
||||
|
||||
// Handle outside range quickly
|
||||
int n = ratio.length;
|
||||
if (k <= ratio[0]) {
|
||||
return colors[0];
|
||||
}
|
||||
if (k >= ratio[n - 1]) {
|
||||
return colors[n - 1];
|
||||
}
|
||||
|
||||
// Find the segment [i, i+1] such that ratio[i] <= k <= ratio[i+1]
|
||||
int i = 0;
|
||||
while (i < n - 1 && k > ratio[i + 1]) {
|
||||
i++;
|
||||
}
|
||||
|
||||
int p0 = ratio[i];
|
||||
int p1 = ratio[i + 1];
|
||||
RGBA c0 = colors[i];
|
||||
RGBA c1 = colors[i + 1];
|
||||
|
||||
// Degenerate case: two stops at the same position
|
||||
if (p1 == p0) {
|
||||
return c1;
|
||||
}
|
||||
|
||||
float t = (k - p0) / (float) (p1 - p0); // 0..1
|
||||
|
||||
// Interpolate alpha linearly in all modes
|
||||
int a = lerp8(c0.alpha, c1.alpha, t);
|
||||
|
||||
if (mode == ColorInterpolation.SRGB) {
|
||||
// SVG color-interpolation="sRGB": interpolate directly in sRGB component values
|
||||
int r = lerp8(c0.red, c1.red, t);
|
||||
int g = lerp8(c0.green, c1.green, t);
|
||||
int b = lerp8(c0.blue, c1.blue, t);
|
||||
return new RGBA(r, g, b, a);
|
||||
} else {
|
||||
// SVG color-interpolation="linearRGB": gamma-correct interpolation
|
||||
float r = lerpLinearRgbChannel(c0.red, c1.red, t);
|
||||
float g = lerpLinearRgbChannel(c0.green, c1.green, t);
|
||||
float b = lerpLinearRgbChannel(c0.blue, c1.blue, t);
|
||||
|
||||
return new RGBA(
|
||||
clamp255(Math.round(r * 255f)),
|
||||
clamp255(Math.round(g * 255f)),
|
||||
clamp255(Math.round(b * 255f)),
|
||||
a
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SplitResult {
|
||||
public final RGBA[] colorsA;
|
||||
public final int[] ratioA;
|
||||
public final RGBA[] colorsB;
|
||||
public final int[] ratioB;
|
||||
|
||||
public SplitResult(RGBA[] colorsA, int[] ratioA, RGBA[] colorsB, int[] ratioB) {
|
||||
this.colorsA = colorsA;
|
||||
this.ratioA = ratioA;
|
||||
this.colorsB = colorsB;
|
||||
this.ratioB = ratioB;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a gradient into two halves:
|
||||
* - A: original positions 0..127 mapped to 0..255
|
||||
* - B: original positions 128..255 mapped to 0..255
|
||||
*
|
||||
* Notes:
|
||||
* - Stop lookup is based on original ratio[].
|
||||
* - Boundary stops at 127 (A) and 128 (B) are ensured (interpolated if missing).
|
||||
* - ratio[] is assumed sorted ascending.
|
||||
*/
|
||||
public static SplitResult splitIntoHalves(
|
||||
RGBA[] colors, int[] ratio, ColorInterpolation mode
|
||||
) {
|
||||
// Build left half (0..127)
|
||||
Stops left = extractRange(colors, ratio, 0, 127, mode);
|
||||
|
||||
// Build right half (128..255)
|
||||
Stops right = extractRange(colors, ratio, 128, 255, mode);
|
||||
|
||||
// Remap ratios to 0..255 in each half
|
||||
int[] ratioA = remap(left.ratio, 0, 127);
|
||||
int[] ratioB = remap(right.ratio, 128, 255);
|
||||
|
||||
return new SplitResult(
|
||||
left.colors.toArray(new RGBA[0]), ratioA,
|
||||
right.colors.toArray(new RGBA[0]), ratioB
|
||||
);
|
||||
}
|
||||
|
||||
// ----- Internal representation of stops -----
|
||||
|
||||
private static final class Stops {
|
||||
final List<RGBA> colors = new ArrayList<>();
|
||||
final List<Integer> ratio = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all stops within [from..to] (inclusive), and ensures stops at both ends exist.
|
||||
* If an endpoint stop is missing, it is computed via SvgGradient.colorAt(...).
|
||||
*/
|
||||
private static Stops extractRange(RGBA[] colors, int[] ratio, int from, int to,
|
||||
ColorInterpolation mode) {
|
||||
Stops out = new Stops();
|
||||
|
||||
// Ensure start stop
|
||||
addStop(out, from, stopColorAtOrExisting(colors, ratio, from, mode));
|
||||
|
||||
// Add internal stops strictly inside (from, to)
|
||||
for (int i = 0; i < ratio.length; i++) {
|
||||
int p = ratio[i];
|
||||
if (p > from && p < to) {
|
||||
addStop(out, p, colors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure end stop
|
||||
addStop(out, to, stopColorAtOrExisting(colors, ratio, to, mode));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is an existing stop exactly at pos, returns its color;
|
||||
* otherwise computes the color by interpolation at that position.
|
||||
*/
|
||||
private static RGBA stopColorAtOrExisting(RGBA[] colors, int[] ratio, int pos,
|
||||
ColorInterpolation mode) {
|
||||
for (int i = 0; i < ratio.length; i++) {
|
||||
if (ratio[i] == pos) return colors[i];
|
||||
}
|
||||
return colorAt(colors, ratio, pos, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a stop keeping order; if the same position already exists, it overwrites the color.
|
||||
*/
|
||||
private static void addStop(Stops stops, int pos, RGBA color) {
|
||||
// Insert in ascending order (ratio is small, linear insert is fine)
|
||||
for (int i = 0; i < stops.ratio.size(); i++) {
|
||||
int existing = stops.ratio.get(i);
|
||||
if (existing == pos) {
|
||||
stops.colors.set(i, color);
|
||||
return;
|
||||
}
|
||||
if (existing > pos) {
|
||||
stops.ratio.add(i, pos);
|
||||
stops.colors.add(i, color);
|
||||
return;
|
||||
}
|
||||
}
|
||||
stops.ratio.add(pos);
|
||||
stops.colors.add(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remaps original integer positions in [from..to] to [0..255].
|
||||
* Uses rounding and clamps, guaranteeing endpoints map to 0 and 255.
|
||||
*/
|
||||
private static int[] remap(List<Integer> original, int from, int to) {
|
||||
int span = to - from; // for 0..127 and 128..255 span is 127
|
||||
int[] out = new int[original.size()];
|
||||
|
||||
for (int i = 0; i < original.size(); i++) {
|
||||
int p = original.get(i);
|
||||
float t = (p - from) / (float) span; // 0..1
|
||||
int mapped = Math.round(t * 255f); // 0..255
|
||||
out[i] = clamp255(mapped);
|
||||
}
|
||||
|
||||
// Force exact endpoints (avoid any rounding surprises)
|
||||
if (out.length > 0) {
|
||||
out[0] = 0;
|
||||
out[out.length - 1] = 255;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ----- Interpolation helpers -----
|
||||
private static int lerp8(int v0, int v1, float t) {
|
||||
return clamp255(Math.round(v0 + (v1 - v0) * t));
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates a single channel using linearRGB mode: sRGB (0..1) -> linear
|
||||
* (0..1) -> lerp -> sRGB (0..1)
|
||||
*/
|
||||
private static float lerpLinearRgbChannel(int c0_8bit, int c1_8bit, float t) {
|
||||
float s0 = c0_8bit / 255f;
|
||||
float s1 = c1_8bit / 255f;
|
||||
|
||||
float l0 = srgbToLinear(s0);
|
||||
float l1 = srgbToLinear(s1);
|
||||
|
||||
float l = l0 + (l1 - l0) * t;
|
||||
|
||||
return linearToSrgb(l);
|
||||
}
|
||||
|
||||
// ----- sRGB transfer functions -----
|
||||
private static float srgbToLinear(float c) {
|
||||
// IEC 61966-2-1 sRGB
|
||||
if (c <= 0.04045f) {
|
||||
return c / 12.92f;
|
||||
}
|
||||
return (float) Math.pow((c + 0.055f) / 1.055f, 2.4);
|
||||
}
|
||||
|
||||
private static float linearToSrgb(float c) {
|
||||
// IEC 61966-2-1 sRGB
|
||||
if (c <= 0.0031308f) {
|
||||
return 12.92f * c;
|
||||
}
|
||||
return 1.055f * (float) Math.pow(c, 1.0 / 2.4) - 0.055f;
|
||||
}
|
||||
|
||||
// ----- Clamp helpers -----
|
||||
private static float clamp(float v, float lo, float hi) {
|
||||
return Math.max(lo, Math.min(hi, v));
|
||||
}
|
||||
|
||||
private static int clamp255(int v) {
|
||||
return Math.max(0, Math.min(255, v));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user