/* JWildfire - an image and animation processor written in Java Copyright (C) 1995-2011 Andreas Maschke This 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 2.1 of the License, or (at your option) any later version. This software 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 software; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jwildfire.transform; import org.jwildfire.base.Property; import org.jwildfire.base.PropertyMax; import org.jwildfire.base.PropertyMin; import org.jwildfire.base.Tools; import org.jwildfire.image.Pixel; import org.jwildfire.image.SimpleImage; import org.jwildfire.image.WFImage; import com.l2fprod.common.beans.editor.ComboBoxPropertyEditor; public class ConvolveTransformer extends Mesh2DTransformer { public enum EdgeMode { MIRROR, WRAP, BLACK } public enum ColorMode { COLOR, GREY } public enum KernelType { EDGE_DETECT_3x3, EMBOSS_3x3, LAPLACE_3x3, SHARPEN_3x3, SHARPEN_LESS_3x3, SOBEL_3X3, GAUSSIAN_BLUR_5x5, GAUSSIAN_BLUR_3x3 } public enum KernelDirection { NORTH, EAST, SOUTH, WEST } @Property(description = "Effect transparency") @PropertyMin(0) @PropertyMax(100) private int transparency; @Property(description = "Constant part to add to each pixel value") @PropertyMin(0) @PropertyMax(255) private int bias; @Property(description = "How to treat the outer space of the source image", editorClass = EdgeModeEditor.class) private EdgeMode edgeMode; @Property(description = "Color or Grey mode", editorClass = ColorModeEditor.class) private ColorMode colorMode; @Property(description = "The convolution kernel", editorClass = KernelTypeEditor.class) private KernelType kernelType; @Property(description = "The kernel alignment", editorClass = KernelDirectionEditor.class) private KernelDirection kernelDirection; @Override protected void performPixelTransformation(WFImage pImg) { SimpleImage img = (SimpleImage) pImg; int kernel[][] = getKernel(); int width = srcImg.getImageWidth(); int height = srcImg.getImageHeight(); int m1 = this.transparency; int m2 = (100 - this.transparency); int kernelSize = kernel.length; int halveKernelSize = kernelSize / 2; int kernelSum = 0; for (int i = 0; i < kernelSize; i++) { for (int j = 0; j < kernelSize; j++) { kernelSum += kernel[i][j]; } } if (kernelSum == 0) kernelSum = 1; Pixel pixel = new Pixel(); if (colorMode == ColorMode.GREY) { srcImg = srcImg.clone(); new ColorToGrayTransformer().transformImage(srcImg); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int intSumR = 0; for (int k = 0; k < kernelSize; k++) { int y = i - halveKernelSize + k; addPixels: for (int l = 0; l < kernelSize; l++) { int x = j - halveKernelSize + l; if (x < 0) { switch (this.edgeMode) { case MIRROR: x = -x; break; case WRAP: x += width; break; case BLACK: continue addPixels; } } else if (x >= width) { switch (this.edgeMode) { case MIRROR: x = 2 * width - x - 1; break; case WRAP: x -= width; break; case BLACK: continue addPixels; } } if (y < 0) { switch (this.edgeMode) { case MIRROR: y = -y; break; case WRAP: y += height; break; case BLACK: continue addPixels; } } else if (y >= height) { switch (this.edgeMode) { case MIRROR: y = 2 * height - y - 1; break; case WRAP: y -= height; break; case BLACK: continue addPixels; } } intSumR += srcImg.getRValue(x, y) * kernel[k][l]; } } intSumR = Tools.limitColor((intSumR / kernelSum) + this.bias); if (this.transparency == 0) { img.setRGB(j, i, intSumR, intSumR, intSumR); } else { pixel.setARGBValue(srcImg.getARGBValue(j, i)); int rval = Tools.limitColor(((pixel.r * m1 + intSumR * m2) / 100)); img.setRGB(j, i, rval, rval, rval); } } } } else { for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int intSumR = 0; int intSumG = 0; int intSumB = 0; for (int k = 0; k < kernelSize; k++) { int y = i - halveKernelSize + k; addPixels: for (int l = 0; l < kernelSize; l++) { int x = j - halveKernelSize + l; if (x < 0) { switch (this.edgeMode) { case MIRROR: x = -x; break; case WRAP: x += width; break; case BLACK: continue addPixels; } } else if (x >= width) { switch (this.edgeMode) { case MIRROR: x = 2 * width - x - 1; break; case WRAP: x -= width; break; case BLACK: continue addPixels; } } if (y < 0) { switch (this.edgeMode) { case MIRROR: y = -y; break; case WRAP: y += height; break; case BLACK: continue addPixels; } } else if (y >= height) { switch (this.edgeMode) { case MIRROR: y = 2 * height - y - 1; break; case WRAP: y -= height; break; case BLACK: continue addPixels; } } pixel.setARGBValue(srcImg.getARGBValue(x, y)); intSumR += pixel.r * kernel[k][l]; intSumG += pixel.g * kernel[k][l]; intSumB += pixel.b * kernel[k][l]; } } intSumR = Tools.limitColor((intSumR / kernelSum) + this.bias); intSumG = Tools.limitColor((intSumG / kernelSum) + this.bias); intSumB = Tools.limitColor((intSumB / kernelSum) + this.bias); if (this.transparency == 0) { img.setRGB(j, i, intSumR, intSumG, intSumB); } else { pixel.setARGBValue(srcImg.getARGBValue(j, i)); int rval = Tools.limitColor(((pixel.r * m1 + intSumR * m2) / 100)); int gval = Tools.limitColor(((pixel.g * m1 + intSumG * m2) / 100)); int bval = Tools.limitColor(((pixel.b * m1 + intSumB * m2) / 100)); img.setRGB(j, i, rval, gval, bval); } } } } } @Override public void initDefaultParams(WFImage pImg) { edgeMode = EdgeMode.MIRROR; colorMode = ColorMode.COLOR; transparency = 0; kernelDirection = KernelDirection.NORTH; bias = 0; kernelType = KernelType.EMBOSS_3x3; } // 1 private static final int[][] KERNEL_EMPTY = { { 1 } }; // -1 -1 -1 // -1 8 -1 // -1 -1 -1 private static final int[][] KERNEL_EDGE_DETECT_3x3 = { { -1, -1, -1 }, { -1, 8, -1 }, { -1, -1, -1 } }; // -2 0 0 // 0 1 0 // 0 0 2 private static final int[][] KERNEL_EMBOSS_3x3 = { { -2, 0, 0 }, { 0, 1, 0 }, { 0, 0, 2 } }; // 0 -1 0 // -1 4 -1 // 0 -1 0 private static final int[][] KERNEL_LAPLACE_3x3 = { { 0, -1, 0 }, { -1, 4, -1 }, { 0, -1, 0 } }; // -1 -1 -1 // -1 9 -1 // -1 -1 -1 private static final int[][] KERNEL_SHARPEN_3x3 = { { -1, -1, -1 }, { -1, 9, -1 }, { -1, -1, -1 } }; // -1 -1 -1 // -1 17 -1 // -1 -1 -1 private static final int[][] KERNEL_SHARPEN_LESS_3x3 = { { -1, -1, -1 }, { -1, 17, -1 }, { -1, -1, -1 } }; // -1 0 1 // -2 0 2 // -1 0 1 private static final int[][] KERNEL_SOBEL_3x3 = { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } }; // 2 4 5 4 2 // 4 9 12 9 4 // 5 12 15 12 5 // 4 9 12 9 4 // 2 4 5 4 2 private static final int[][] KERNEL_GAUSSIAN_BLUR_5x5 = { { 2, 4, 5, 4, 2 }, { 4, 9, 12, 9, 4 }, { 5, 12, 15, 12, 5 }, { 4, 9, 12, 9, 4 }, { 2, 4, 5, 4, 2 } }; // 1 2 1 // 2 4 2 // 1 2 1 private static final int[][] KERNEL_GAUSSIAN_BLUR_3x3 = { { 1, 2, 1 }, { 2, 4, 2 }, { 1, 2, 1 } }; private int[][] getKernel() { int res[][]; switch (kernelType) { case EDGE_DETECT_3x3: res = KERNEL_EDGE_DETECT_3x3; break; case EMBOSS_3x3: res = KERNEL_EMBOSS_3x3; break; case LAPLACE_3x3: res = KERNEL_LAPLACE_3x3; break; case SHARPEN_3x3: res = KERNEL_SHARPEN_3x3; break; case SHARPEN_LESS_3x3: res = KERNEL_SHARPEN_LESS_3x3; break; case SOBEL_3X3: res = KERNEL_SOBEL_3x3; break; case GAUSSIAN_BLUR_3x3: res = KERNEL_GAUSSIAN_BLUR_3x3; break; case GAUSSIAN_BLUR_5x5: res = KERNEL_GAUSSIAN_BLUR_5x5; break; default: res = KERNEL_EMPTY; break; } final int size = res.length; switch (kernelDirection) { case EAST: { int[][] rotKernel = new int[size][size]; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { rotKernel[j][i] = res[size - 1 - i][j]; } } return rotKernel; } case WEST: { int[][] rotKernel = new int[size][size]; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { rotKernel[j][i] = res[i][size - 1 - j]; } } return rotKernel; } case SOUTH: { int[][] rotKernel = new int[size][size]; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { rotKernel[j][i] = res[size - 1 - j][size - 1 - i]; } } return rotKernel; } default: return res; } } public static class EdgeModeEditor extends ComboBoxPropertyEditor { public EdgeModeEditor() { super(); setAvailableValues(new EdgeMode[] { EdgeMode.MIRROR, EdgeMode.WRAP, EdgeMode.BLACK }); } } public static class ColorModeEditor extends ComboBoxPropertyEditor { public ColorModeEditor() { super(); setAvailableValues(new ColorMode[] { ColorMode.COLOR, ColorMode.GREY }); } } public static class KernelTypeEditor extends ComboBoxPropertyEditor { public KernelTypeEditor() { super(); setAvailableValues(new KernelType[] { KernelType.EDGE_DETECT_3x3, KernelType.EMBOSS_3x3, KernelType.LAPLACE_3x3, KernelType.SHARPEN_3x3, KernelType.SHARPEN_LESS_3x3, KernelType.SOBEL_3X3, KernelType.GAUSSIAN_BLUR_3x3, KernelType.GAUSSIAN_BLUR_5x5 }); } } public static class KernelDirectionEditor extends ComboBoxPropertyEditor { public KernelDirectionEditor() { super(); setAvailableValues(new KernelDirection[] { KernelDirection.NORTH, KernelDirection.EAST, KernelDirection.SOUTH, KernelDirection.WEST }); } } public int getTransparency() { return transparency; } public void setTransparency(int transparency) { this.transparency = transparency; } public int getBias() { return bias; } public void setBias(int bias) { this.bias = bias; } public EdgeMode getEdgeMode() { return edgeMode; } public void setEdgeMode(EdgeMode edgeMode) { this.edgeMode = edgeMode; } public KernelType getKernelType() { return kernelType; } public void setKernelType(KernelType kernelType) { this.kernelType = kernelType; } public ColorMode getColorMode() { return colorMode; } public void setColorMode(ColorMode colorMode) { this.colorMode = colorMode; } public KernelDirection getKernelDirection() { return kernelDirection; } public void setKernelDirection(KernelDirection kernelDirection) { this.kernelDirection = kernelDirection; } }