/* 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 java.awt.Color; import java.awt.Graphics; import org.jwildfire.base.Property; 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 ErodeTransformer extends Mesh2DTransformer { public enum Mode { ERODE, DILATE, OILTRANSFER, NEON }; public enum Shape { SQUARE, DIAMOND, DISK, PLUS, X, RANDOM } @Property(editorClass = ModeEditor.class, description = "ERODE (shrink), DILATE (grow) or OILTRANSFER (most popular color) mode") private Mode mode; @Property(editorClass = ShapeEditor.class, description = "Shape of the area to compute the darkest/brightest color") private Shape shape; @Property(description = "Size of the shape to compute the darkest/brightest color") private int size; @Property(description = "Seed for the random number generator") @PropertyMin(0) private int seed; @Override protected void performPixelTransformation(WFImage pImg) { if (this.size <= 1) return; SimpleImage img = (SimpleImage) pImg; Tools.srand123(this.seed); int width = pImg.getImageWidth(); int height = pImg.getImageHeight(); short[][] shape; switch (this.shape) { case DIAMOND: shape = generateDiamond(this.size); break; case DISK: shape = generateDisk(this.size); break; case SQUARE: shape = generateSquare(this.size); break; case PLUS: shape = generatePlus(this.size); break; case X: shape = generateX(this.size); break; default: shape = generateRandom(this.size); break; } SimpleImage srcGreyImg = null; if ((this.mode == Mode.DILATE) || (this.mode == Mode.ERODE) || (this.mode == Mode.NEON)) { srcGreyImg = srcImg.clone(); ColorToGrayTransformer cT = new ColorToGrayTransformer(); cT.setWeights(ColorToGrayTransformer.Weights.LUMINANCE); cT.transformImage(srcGreyImg); } switch (mode) { case ERODE: for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int lumMin = srcGreyImg.getRValue(j, i); int xLumMin = j; int yLumMin = i; for (int s = 0; s < shape.length; s++) { int x = j + shape[s][0]; int y = i + shape[s][1]; if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) { int lum = srcGreyImg.getRValue(x, y); if (lum < lumMin) { lumMin = lum; xLumMin = x; yLumMin = y; } } } img.setARGB(j, i, srcImg.getARGBValue(xLumMin, yLumMin)); } } break; case DILATE: for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int lumMax = srcGreyImg.getRValue(j, i); int xLumMax = j; int yLumMax = i; for (int s = 0; s < shape.length; s++) { int x = j + shape[s][0]; int y = i + shape[s][1]; if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) { int lum = srcGreyImg.getRValue(x, y); if (lum > lumMax) { lumMax = lum; xLumMax = x; yLumMax = y; } } } img.setARGB(j, i, srcImg.getARGBValue(xLumMax, yLumMax)); } } break; case NEON: Pixel srcPixel = new Pixel(); Pixel currPixel = new Pixel(); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int lumMax = srcGreyImg.getRValue(j, i); int lum0 = lumMax; int xLumMax = j; int yLumMax = i; for (int s = 0; s < shape.length; s++) { int x = j + shape[s][0]; int y = i + shape[s][1]; if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) { int lum = srcGreyImg.getRValue(x, y); if (lum > lumMax) { lumMax = lum; xLumMax = x; yLumMax = y; } } } int m1 = 30; int m2 = 100 - m1; srcPixel.setARGBValue(srcImg.getARGBValue(xLumMax, yLumMax)); currPixel.setARGBValue(srcImg.getARGBValue(j, i)); currPixel.r = (srcPixel.r * m1 + currPixel.r * m2) / 100; currPixel.g = (srcPixel.g * m1 + currPixel.g * m2) / 100; currPixel.b = (srcPixel.b * m1 + currPixel.b * m2) / 100; if (Math.abs(lum0 - lumMax) > 50) img.setRGB(j, i, 0, 128, 0); //pImg.setARGB(j, i, srcImg.getARGBValue(xLumMax, yLumMax)); } } break; case OILTRANSFER: int[] colorValues = new int[shape.length]; short[] colorUsed = new short[shape.length]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int cCount = 0; for (int s = 0; s < colorUsed.length; s++) colorUsed[s] = 0; for (int s = 0; s < shape.length; s++) { int x = j + shape[s][0]; int y = i + shape[s][1]; if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) { int color = srcImg.getARGBValue(x, y); boolean found = false; for (short t = 0; t < cCount; t++) { if (colorValues[t] == color) { colorUsed[t]++; found = true; break; } } if (!found) { colorValues[cCount] = color; colorUsed[cCount++] = 1; } } } int color = srcImg.getARGBValue(j, i); int usedMax = 1; for (int t = 0; t < cCount; t++) { if (colorUsed[t] > usedMax) { usedMax = colorUsed[t]; color = colorValues[t]; } } img.setARGB(j, i, color); } } break; } } @Override public void initDefaultParams(WFImage pImg) { mode = Mode.ERODE; shape = Shape.DISK; size = 5; } public Mode getMode() { return mode; } public void setMode(Mode mode) { this.mode = mode; } public static class ModeEditor extends ComboBoxPropertyEditor { public ModeEditor() { super(); setAvailableValues(new Mode[] { Mode.ERODE, Mode.DILATE, Mode.OILTRANSFER /*, Mode.NEON */}); } } public static class ShapeEditor extends ComboBoxPropertyEditor { public ShapeEditor() { super(); setAvailableValues(new Shape[] { Shape.DISK, Shape.SQUARE, Shape.DIAMOND, Shape.PLUS, Shape.X, Shape.RANDOM }); } } public Shape getShape() { return shape; } public void setShape(Shape shape) { this.shape = shape; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public int getSeed() { return seed; } public void setSeed(int seed) { this.seed = seed; } // xxx {-1,-1} { 0,-1} { 1,-1} // xxx {-1, 0} { 0, 0} { 1, 0} // xxx {-1, 1} { 0, 1} { 1, 1} private short[][] generateSquare(int pSize) { short s = (short) pSize; if ((s < 3) || (s % 2 == 0)) throw new IllegalArgumentException(String.valueOf(pSize)); short s2 = (short) (s / 2); short[][] res = new short[s * s][2]; int idx = 0; for (short i = 0; i < s; i++) { for (short j = 0; j < s; j++) { res[idx][0] = (short) (j - s2); res[idx++][1] = (short) (i - s2); } } return res; } // x { 0,-2} // xxx {-1,-1} { 0,-1} { 1,-1} // xxxxx {-2, 0} {-1, 0} { 0, 0} { 1, 0} { 2, 0} // xxx {-1, 1} { 0, 1} { 1, 1} // x { 0, 2} private short[][] generateDiamond(int pSize) { short s = (short) pSize; if ((s < 3) || (s % 2 == 0)) throw new IllegalArgumentException(String.valueOf(pSize)); short s2 = (short) (s / 2); int cnt = s; for (short i = 0; i < s2; i++) cnt += 2 * (1 + 2 * i); short[][] res = new short[cnt][2]; int idx = 0; for (short i = (short) -s2; i <= s2; i++) { int k = s2 - (i > 0 ? i : -i); for (short j = (short) -k; j <= k; j++) { res[idx][0] = j; res[idx++][1] = i; } } return res; } public static void printShape(short[][] pShape) { int idx = 0; while (idx < pShape.length) { System.out.print("{"); if (pShape[idx][0] >= 0) System.out.print(" "); System.out.print(pShape[idx][0] + ","); if (pShape[idx][1] >= 0) System.out.print(" "); System.out.print(pShape[idx][1] + "} "); idx++; if (idx % 3 == 0) System.out.println(""); } } // xxx {-1,-2} { 0,-2} { 1,-2} // xxxxx {-2,-1} {-1,-1} { 0,-1} { 1,-1} { 2,-1} // xxxxx {-2, 0} {-1, 0} { 0, 0} { 1, 0} { 2, 0} // xxxxx {-2, 1} {-1, 1} { 0, 1} { 1, 1} { 2, 1} // xxx {-1, 2} { 0, 2} { 1, 2} private short[][] generateDisk(int pSize) { short s = (short) pSize; if ((s < 3) || (s % 2 == 0)) throw new IllegalArgumentException(String.valueOf(pSize)); short s2 = (short) (s / 2); SimpleImage img = new SimpleImage(s + 120, s + 120); Graphics g = img.getGraphics(); g.setColor(new Color(255, 0, 0)); // fillOval gave strange effects at low scales, but drawOval seems to work g.drawOval(0, 0, s - 1, s - 1); // fill it manually for (short i = 0; i < s; i++) { nextLine: for (short j = 0; j <= s2; j++) { if (img.getRValue(j, i) == 255) { g.drawLine(j, i, s - j - 1, i); continue nextLine; } } } int cnt = 0; for (short i = 0; i < s; i++) { for (short j = 0; j < s; j++) { if (img.getRValue(j, i) == 255) cnt++; } } int idx = 0; short[][] res = new short[cnt][2]; for (short i = 0; i < s; i++) { for (short j = 0; j < s; j++) { if (img.getRValue(j, i) == 255) { res[idx][0] = (short) (j - s2); res[idx++][1] = (short) (i - s2); } } } /* try { new ImageSaver().saveAsPNG(img, "D:\\TMP\\_z.png"); } catch (Exception e) { e.printStackTrace(); } */ return res; } private short[][] generateRandom(int pSize) { short[][] res = new short[pSize * pSize][2]; int s2 = pSize / 2; for (int i = 0; i < pSize; i++) { for (int j = 0; j < pSize; j++) { int idx = i * pSize + j; if (Tools.drand() > 0.33) { res[idx][0] = (short) ((double) (j - s2) * Tools.drand() + 0.5); res[idx][1] = (short) ((double) (i - s2) * Tools.drand() + 0.5); } else { res[idx][0] = 0; res[idx][1] = 0; } } } return res; } // x { 0,-1} // xxx {-1, 0} { 0, 0} { 1, 0} // x { 0, 1} private short[][] generatePlus(int pSize) { short s = (short) pSize; if ((s < 3) || (s % 2 == 0)) throw new IllegalArgumentException(String.valueOf(pSize)); short s2 = (short) (s / 2); short[][] res = new short[2 * s - 1][2]; int idx = 0; for (short i = 0; i < s; i++) { short jMin = (i == s2) ? 0 : s2; short jMax = (i == s2) ? s : (short) (s2 + 1); System.out.println(i + ": " + jMin + "..." + jMax); for (short j = jMin; j < jMax; j++) { res[idx][0] = (short) (j - s2); res[idx++][1] = (short) (i - s2); } } return res; } // x x {-1,-1} { 1,-1} // x { 0, 0} // x x {-1, 1} { 1, 1} private short[][] generateX(int pSize) { short s = (short) pSize; if ((s < 3) || (s % 2 == 0)) throw new IllegalArgumentException(String.valueOf(pSize)); short s2 = (short) (s / 2); short[][] res = new short[2 * s - 1][2]; int idx = 0; for (short i = 0; i < s; i++) { if (i == s2) { res[idx][0] = 0; res[idx++][1] = (short) (i - s2); } else { short x = (short) (s2 - i); if (i > s2) x = (short) -x; res[idx][0] = (short) -x; res[idx++][1] = (short) (i - s2); res[idx][0] = (short) x; res[idx++][1] = (short) (i - s2); } } return res; } /* public static void main(String[] args) { ErodeTransformer t = new ErodeTransformer(); // short[][] shape = t.generateSquare(5); // short[][] shape = t.generateDiamond(5); // short[][] shape = t.generateDisk(13); // short[][] shape = t.generatePlus(5); short[][] shape = t.generateX(5); t.printShape(shape); } */ }