/*
* 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.types.filters;
import com.jpexs.decompiler.flash.types.RGBA;
import com.jpexs.helpers.SerializableImage;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Point;
import java.awt.Rectangle;
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;
/**
*
* @author JPEXS
*/
public class Filtering {
public static final int INNER = 1;
public static final int OUTER = 2;
public static final int FULL = 3;
private static final Color ALPHA = new Color(0, 0, 0, 0);
private static final Point POINT_0_0 = new Point(0, 0);
private static final Point POINT_255_0 = new Point(255, 0);
private static final Point POINT_511_0 = new Point(511, 0);
private static final Rectangle RECTANGLE_256_1 = new Rectangle(256, 1);
private static final Rectangle RECTANGLE_512_1 = new Rectangle(512, 1);
private static void boxBlurHorizontal(int[] pixels, int[] mask, int[] newColors, int w, int h, int radius) {
int index = 0;
for (int y = 0; y < h; y++) {
int hits = 0;
long r = 0;
long g = 0;
long b = 0;
long a = 0;
for (int x = -radius; x < w; x++) {
int oldPixel = x - radius - 1;
if (oldPixel >= 0) {
int color = pixels[index + oldPixel];
if ((mask == null) || (((mask[index + oldPixel] >> 24) & 0xff) > 0)) {
if (color != 0) {
a -= (color >> 24) & 0xff;
r -= ((color >> 16) & 0xff);
g -= ((color >> 8) & 0xff);
b -= ((color) & 0xff);
}
hits--;
}
}
int newPixel = x + radius;
if (newPixel < w) {
int color = pixels[index + newPixel];
if ((mask == null) || (((mask[index + newPixel] >> 24) & 0xff) > 0)) {
if (color != 0) {
a += (color >> 24) & 0xff;
r += ((color >> 16) & 0xff);
g += ((color >> 8) & 0xff);
b += ((color) & 0xff);
}
hits++;
}
}
if (x >= 0) {
if ((mask == null) || (((mask[index + x] >> 24) & 0xff) > 0)) {
if (hits == 0) {
newColors[x] = 0;
} else {
newColors[x] = RGBA.toInt((int) (r / hits) & 0xff, (int) (g / hits) & 0xff, (int) (b / hits) & 0xff, (int) (a / hits));
}
} else {
newColors[x] = 0;
}
}
}
System.arraycopy(newColors, 0, pixels, index, w);
index += w;
}
}
private static void boxBlurVertical(int[] pixels, int[] mask, int[] newColors, int w, int h, int radius) {
int oldPixelOffset = -(radius + 1) * w;
int newPixelOffset = (radius) * w;
for (int x = 0; x < w; x++) {
int hits = 0;
long r = 0;
long g = 0;
long b = 0;
long a = 0;
int index = -radius * w + x;
for (int y = -radius; y < h; y++) {
int oldPixel = y - radius - 1;
if (oldPixel >= 0) {
int color = pixels[index + oldPixelOffset];
if ((mask == null) || (((mask[index + oldPixelOffset] >> 24) & 0xff) > 0)) {
if (color != 0) {
a -= (color >> 24) & 0xff;
r -= ((color >> 16) & 0xff);
g -= ((color >> 8) & 0xff);
b -= ((color) & 0xff);
}
hits--;
}
}
int newPixel = y + radius;
if (newPixel < h) {
if ((mask == null) || (((mask[index + newPixelOffset] >> 24) & 0xff) > 0)) {
int color = pixels[index + newPixelOffset];
if (color != 0) {
a += (color >> 24) & 0xff;
r += ((color >> 16) & 0xff);
g += ((color >> 8) & 0xff);
b += ((color) & 0xff);
}
hits++;
}
}
if (y >= 0) {
if ((mask == null) || (((mask[y * w + x] >> 24) & 0xff) > 0)) {
if (hits == 0) {
newColors[y] = 0;
} else {
newColors[y] = RGBA.toInt((int) (r / hits) & 0xff, (int) (g / hits) & 0xff, (int) (b / hits) & 0xff, (int) (a / hits) & 0xff);
}
} else {
newColors[y] = 0;
}
}
index += w;
}
for (int y = 0; y < h; y++) {
pixels[y * w + x] = newColors[y];
}
}
}
private static void premultiply(int[] p) {
int length = p.length;
int offset = 0;
length += offset;
for (int i = offset; i < length; i++) {
int rgb = p[i];
int a = rgb >> 24 & 0xff;
int r = rgb >> 16 & 0xff;
int g = rgb >> 8 & 0xff;
int b = rgb & 0xff;
float f = (float) a * 0.003921569F;
r = (int) ((float) r * f);
g = (int) ((float) g * f);
b = (int) ((float) b * f);
p[i] = a << 24 | r << 16 | g << 8 | b;
}
}
private static void unpremultiply(int[] p) {
int length = p.length;
int offset = 0;
length += offset;
for (int i = offset; i < length; i++) {
int rgb = p[i];
int a = rgb >> 24 & 0xff;
int r = rgb >> 16 & 0xff;
int g = rgb >> 8 & 0xff;
int b = rgb & 0xff;
if (a == 0 || a == 255) {
continue;
}
float f = 255F / (float) a;
r = (int) ((float) r * f);
g = (int) ((float) g * f);
b = (int) ((float) b * f);
if (r > 255) {
r = 255;
}
if (g > 255) {
g = 255;
}
if (b > 255) {
b = 255;
}
p[i] = a << 24 | r << 16 | g << 8 | b;
}
}
public static SerializableImage blur(SerializableImage src, int hRadius, int vRadius, int iterations) {
int[] pixels = (int[]) getRGB(src.getBufferedImage()).clone();
int width = src.getWidth();
int height = src.getHeight();
blur(pixels, width, height, hRadius, vRadius, iterations, null);
BufferedImage ret = new BufferedImage(width, height, src.getType());
setRGB(ret, width, height, pixels);
return new SerializableImage(ret);
}
private static void blur(int[] src, int width, int height, int hRadius, int vRadius, int iterations, int[] mask) {
int[] inPixels = src;
premultiply(inPixels);
int[] tempRow = new int[width];
int[] tempColumn = new int[height];
for (int i = 0; i < iterations; i++) {
boxBlurHorizontal(inPixels, mask, tempRow, width, height, hRadius / 2);
boxBlurVertical(inPixels, mask, tempColumn, width, height, vRadius / 2);
}
unpremultiply(inPixels);
}
public static SerializableImage bevel(SerializableImage src, int blurX, int blurY, float strength, int type, int highlightColor, int shadowColor, float angle, float distance, boolean knockout, int iterations) {
return new SerializableImage(gradientBevel(src.getBufferedImage(), new Color[]{
new Color(shadowColor, true),
new Color(shadowColor & 0x00ffffff, true),
new Color(highlightColor & 0x00ffffff, true),
new Color(highlightColor, true)
}, new float[]{0, 127f / 255f, 128f / 255f, 1}, blurX, blurY, strength, type, angle, distance, knockout, iterations));
}
public static SerializableImage gradientBevel(SerializableImage src, Color[] colors, float[] ratios, int blurX, int blurY, float strength, int type, float angle, float distance, boolean knockout, int iterations) {
return new SerializableImage(gradientBevel(src.getBufferedImage(), colors, ratios, blurX, blurY, strength, type, angle, distance, knockout, iterations));
}
private static BufferedImage gradientBevel(BufferedImage src, Color[] colors, float[] ratios, int blurX, int blurY, float strength, int type, float angle, float distance, boolean knockout, int iterations) {
int width = src.getWidth();
int height = src.getHeight();
BufferedImage retImg = new BufferedImage(width, height, src.getType());
int[] srcPixels = getRGB(src);
int[] revPixels = new int[srcPixels.length];
for (int i = 0; i < srcPixels.length; i++) {
revPixels[i] = (srcPixels[i] & 0xffffff) + ((255 - ((srcPixels[i] >> 24) & 0xff)) << 24);
}
BufferedImage gradient = new BufferedImage(512, 1, src.getType());
Graphics2D gg = gradient.createGraphics();
Point p1 = POINT_0_0;
Point p2 = POINT_511_0;
gg.setPaint(new LinearGradientPaint(p1, p2, ratios, colors));
gg.fill(RECTANGLE_512_1);
int[] gradientPixels = getRGB(gradient);
BufferedImage shadowInner = null;
BufferedImage hilightInner = null;
if (type != OUTER) {
BufferedImage hilightIm = dropShadow(src, 0, 0, angle, distance, Color.red, true, iterations, strength, true);//new DropShadowFilter(blurX, blurY, strength, inner ? highlightColor : shadowColor, angle, distance, inner, true, iterations).filter(src
BufferedImage shadowIm = dropShadow(src, 0, 0, angle + 180, distance, Color.blue, true, iterations, strength, true); //new DropShadowFilter(blurX, blurY, strength, inner ? shadowColor : highlightColor, angle + 180, distance, inner, true, iterations).filter(src);
BufferedImage h2 = new BufferedImage(width, height, src.getType());
BufferedImage s2 = new BufferedImage(width, height, src.getType());
Graphics2D hc = h2.createGraphics();
Graphics2D sc = s2.createGraphics();
hc.drawImage(hilightIm, 0, 0, null);
hc.setComposite(AlphaComposite.DstOut);
hc.drawImage(shadowIm, 0, 0, null);
sc.drawImage(shadowIm, 0, 0, null);
sc.setComposite(AlphaComposite.DstOut);
sc.drawImage(hilightIm, 0, 0, null);
shadowInner = s2;
hilightInner = h2;
}
BufferedImage shadowOuter = null;
BufferedImage hilightOuter = null;
if (type != INNER) {
BufferedImage hilightIm = dropShadow(src, 0, 0, angle + 180, distance, Color.red, false, iterations, strength, true);//new DropShadowFilter(blurX, blurY, strength, inner ? highlightColor : shadowColor, angle, distance, inner, true, iterations).filter(src
BufferedImage shadowIm = dropShadow(src, 0, 0, angle, distance, Color.blue, false, iterations, strength, true); //new DropShadowFilter(blurX, blurY, strength, inner ? shadowColor : highlightColor, angle + 180, distance, inner, true, iterations).filter(src);
BufferedImage h2 = new BufferedImage(width, height, src.getType());
BufferedImage s2 = new BufferedImage(width, height, src.getType());
Graphics2D hc = h2.createGraphics();
Graphics2D sc = s2.createGraphics();
hc.drawImage(hilightIm, 0, 0, null);
hc.setComposite(AlphaComposite.DstOut);
hc.drawImage(shadowIm, 0, 0, null);
sc.drawImage(shadowIm, 0, 0, null);
sc.setComposite(AlphaComposite.DstOut);
sc.drawImage(hilightIm, 0, 0, null);
shadowOuter = s2;
hilightOuter = h2;
}
BufferedImage hilightIm = null;
BufferedImage shadowIm = null;
switch (type) {
case OUTER:
hilightIm = hilightOuter;
shadowIm = shadowOuter;
break;
case INNER:
hilightIm = hilightInner;
shadowIm = shadowInner;
break;
case FULL:
hilightIm = hilightInner;
shadowIm = shadowInner;
Graphics2D hc = hilightIm.createGraphics();
hc.setComposite(AlphaComposite.SrcOver);
hc.drawImage(hilightOuter, 0, 0, null);
Graphics2D sc = shadowIm.createGraphics();
sc.setComposite(AlphaComposite.SrcOver);
sc.drawImage(shadowOuter, 0, 0, null);
break;
}
int[] mask = null;
if (type == INNER) {
mask = srcPixels;
}
if (type == OUTER) {
mask = revPixels;
}
Graphics2D retc = retImg.createGraphics();
retc.setColor(Color.black);
retc.fillRect(0, 0, width, height);
retc.setComposite(AlphaComposite.SrcOver);
retc.drawImage(shadowIm, 0, 0, null);
retc.drawImage(hilightIm, 0, 0, null);
int[] ret = getRGB(retImg);
blur(ret, width, height, blurX, blurY, iterations, mask);
for (int i = 0; i < srcPixels.length; i++) {
int ah = (int) (((ret[i] >> 16) & 0xFF) * strength);
int as = (int) ((ret[i] & 0xFF) * strength);
int ra = cut(ah - as, -255, 255);
ret[i] = gradientPixels[255 + ra];
}
setRGB(retImg, width, height, ret);
if (!knockout) {
Graphics2D g = retImg.createGraphics();
g.setComposite(AlphaComposite.DstOver);
g.drawImage(src, 0, 0, null);
}
return retImg;
}
public static SerializableImage glow(SerializableImage src, int blurX, int blurY, float strength, Color color, boolean inner, boolean knockout, int iterations) {
return new SerializableImage(dropShadow(src.getBufferedImage(), blurX, blurY, 45, 0, color, inner, iterations, strength, knockout));
}
public static SerializableImage dropShadow(SerializableImage src, int blurX, int blurY, float angle, double distance, Color color, boolean inner, int iterations, float strength, boolean knockout) {
return new SerializableImage(dropShadow(src.getBufferedImage(), blurX, blurY, angle, distance, color, inner, iterations, strength, knockout));
}
private static int cut(int val, int min, int max) {
if (val > max) {
val = max;
}
if (val < min) {
val = min;
}
return val;
}
private static BufferedImage dropShadow(BufferedImage src, int blurX, int blurY, float angle, double distance, Color color, boolean inner, int iterations, float strength, boolean knockout) {
int width = src.getWidth();
int height = src.getHeight();
int[] srcPixels = getRGB(src);
int[] shadow = new int[srcPixels.length];
for (int i = 0; i < srcPixels.length; i++) {
int alpha = (srcPixels[i] >> 24) & 0xff;
if (inner) {
alpha = 255 - alpha;
}
shadow[i] = RGBA.toInt(color.getRed(), color.getGreen(), color.getBlue(), cut(color.getAlpha() * alpha / 255 * strength));
}
Color colorFirst = Color.BLACK;
Color colorAlpha = ALPHA;
double angleRad = angle / 180 * Math.PI;
double moveX = (distance * Math.cos(angleRad));
double moveY = (distance * Math.sin(angleRad));
shadow = moveRGB(width, height, shadow, moveX, moveY, inner ? colorFirst : colorAlpha);
if (blurX > 0 || blurY > 0) {
blur(shadow, width, height, blurX, blurY, iterations, null);
}
for (int i = 0; i < shadow.length; i++) {
int mask = (srcPixels[i] >> 24) & 0xff;
if (!inner) {
mask = 255 - mask;
}
shadow[i] = shadow[i] & 0xffffff + ((mask * ((shadow[i] >> 24) & 0xff) / 255) << 24);
}
BufferedImage retCanvas = new BufferedImage(width, height, src.getType());
setRGB(retCanvas, width, height, shadow);
if (!knockout) {
Graphics2D g = retCanvas.createGraphics();
g.setComposite(AlphaComposite.DstOver);
g.drawImage(src, 0, 0, null);
}
return retCanvas;
}
public static SerializableImage gradientGlow(SerializableImage src, int blurX, int blurY, float angle, double distance, Color[] colors, float[] ratios, int type, int iterations, float strength, boolean knockout) {
return new SerializableImage(gradientGlow(src.getBufferedImage(), blurX, blurY, angle, distance, colors, ratios, type, iterations, strength, knockout));
}
private static BufferedImage gradientGlow(BufferedImage src, int blurX, int blurY, float angle, double distance, Color[] colors, float[] ratios, int type, int iterations, float strength, boolean knockout) {
int width = src.getWidth();
int height = src.getHeight();
BufferedImage gradCanvas = new BufferedImage(256, 1, src.getType());
Graphics2D gg = gradCanvas.createGraphics();
Point p1 = POINT_0_0;
Point p2 = POINT_255_0;
gg.setPaint(new LinearGradientPaint(p1, p2, ratios, colors));
gg.fill(RECTANGLE_256_1);
int[] gradientPixels = getRGB(gradCanvas);
double angleRad = angle / 180 * Math.PI;
double moveX = (distance * Math.cos(angleRad));
double moveY = (distance * Math.sin(angleRad));
int[] srcPixels = getRGB(src);
int[] revPixels = new int[srcPixels.length];
for (int i = 0; i < srcPixels.length; i++) {
revPixels[i] = (srcPixels[i] & 0xffffff) + ((255 - ((srcPixels[i] >> 24) & 0xff)) << 24);
}
int[] shadow = new int[srcPixels.length];
for (int i = 0; i < srcPixels.length; i++) {
shadow[i] = 0 + ((cut(strength * ((srcPixels[i] >> 24) & 0xff))) << 24);
}
Color colorAlpha = ALPHA;
shadow = moveRGB(width, height, shadow, moveX, moveY, colorAlpha);
int[] mask = null;
if (type == INNER) {
mask = srcPixels;
}
if (type == OUTER) {
mask = revPixels;
}
blur(shadow, width, height, blurX, blurY, iterations, mask);
if (mask != null) {
for (int i = 0; i < mask.length; i++) {
int m = (mask[i] >> 24);
if (m == 0) {
shadow[i] = 0;
}
}
}
for (int i = 0; i < shadow.length; i++) {
int a = (shadow[i] >> 24) & 0xff;
shadow[i] = gradientPixels[a];
}
BufferedImage retCanvas = new BufferedImage(width, height, src.getType());
setRGB(retCanvas, width, height, shadow);
if (!knockout) {
Graphics2D retImg = retCanvas.createGraphics();
retImg.setComposite(AlphaComposite.DstOver);
retImg.drawImage(src, 0, 0, null);
}
return retCanvas;
}
private static int[] getRGB(BufferedImage image) {
int type = image.getType();
if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) {
return ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
}
int width = image.getWidth();
return image.getRGB(0, 0, width, image.getHeight(), null, 0, width);
}
public static void setRGB(BufferedImage image, int width, int height, int[] pixels) {
int type = image.getType();
if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) {
image.getRaster().setDataElements(0, 0, width, height, pixels);
} else {
image.setRGB(0, 0, width, height, pixels, 0, width);
}
}
private static int[] moveRGB(int width, int height, int[] rgb, double deltaX, double deltaY, Color fill) {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
setRGB(img, width, height, rgb);
BufferedImage retImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) retImg.getGraphics();
g.setPaint(fill);
g.fillRect(0, 0, width, height);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setTransform(AffineTransform.getTranslateInstance(deltaX, deltaY));
g.setComposite(AlphaComposite.Src);
g.drawImage(img, 0, 0, null);
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));
op.filter(src.getBufferedImage(), dst);
return new SerializableImage(dst);
}
public static SerializableImage colorMatrix(SerializableImage src, float[][] matrix) {
/*BandCombineOp changeColors = new BandCombineOp(matrix, new RenderingHints(null));
Raster sourceRaster = src.getRaster();
WritableRaster displayRaster = sourceRaster.createCompatibleWritableRaster();
changeColors.filter(sourceRaster, displayRaster);
return new SerializableImage(src.getColorModel(), displayRaster, true, null);*/
BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
int[] pixels = getRGB(src.getBufferedImage()).clone();
for (int i = 0; i < pixels.length; i++) {
int rgb = pixels[i];
int a = (rgb >> 24) & 0xff;
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
float[] mr = matrix[0];
int r2 = cut(mr[0] * r + mr[1] * g + mr[2] * b + mr[3] * a + mr[4]);
float[] mg = matrix[1];
int g2 = cut(mg[0] * r + mg[1] * g + mg[2] * b + mg[3] * a + mg[4]);
float[] mb = matrix[2];
int b2 = cut(mb[0] * r + mb[1] * g + mb[2] * b + mb[3] * a + mb[4]);
float[] ma = matrix[3];
int a2 = cut(ma[0] * r + ma[1] * g + ma[2] * b + ma[3] * a + ma[4]);
pixels[i] = (a2 << 24) | (r2 << 16) | (g2 << 8) | b2;
}
setRGB(dst, src.getWidth(), src.getHeight(), pixels);
return new SerializableImage(dst);
}
private static int cut(double val) {
int i = (int) val;
if (i < 0) {
i = 0;
}
if (i > 255) {
i = 255;
}
return i;
}
public static int colorEffect(int rgb,
int redAddTerm, int greenAddTerm, int blueAddTerm, int alphaAddTerm,
int redMultTerm, int greenMultTerm, int blueMultTerm, int alphaMultTerm) {
int a = (rgb >> 24) & 0xff;
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
r = cut(((r * redMultTerm) / 256) + redAddTerm);
g = cut(((g * greenMultTerm) / 256) + greenAddTerm);
b = cut(((b * blueMultTerm) / 256) + blueAddTerm);
a = cut(((a * alphaMultTerm) / 256) + alphaAddTerm);
return (a << 24) | (r << 16) | (g << 8) | b;
}
public static SerializableImage colorEffect(SerializableImage src,
int redAddTerm, int greenAddTerm, int blueAddTerm, int alphaAddTerm,
int redMultTerm, int greenMultTerm, int blueMultTerm, int alphaMultTerm) {
BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
int[] pixels = getRGB(src.getBufferedImage()).clone();
for (int i = 0; i < pixels.length; i++) {
int rgb = pixels[i];
int a = (rgb >> 24) & 0xff;
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
r = cut(((r * redMultTerm) / 256) + redAddTerm);
g = cut(((g * greenMultTerm) / 256) + greenAddTerm);
b = cut(((b * blueMultTerm) / 256) + blueAddTerm);
a = cut(((a * alphaMultTerm) / 256) + alphaAddTerm);
pixels[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
setRGB(dst, src.getWidth(), src.getHeight(), pixels);
return new SerializableImage(dst);
}
}