/* 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.PropertyCategory; 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 WaveTransformer extends Mesh2DTransformer { public enum Axis { X, Y }; @Property(description = "X-coordinate of the wave origin") private double centreX = 0.0; @Property(description = "Y-coordinate of the wave origin") private double centreY = 0.0; @Property(description = "Propagation axis of the wave", editorClass = AxisEditor.class) private Axis axis = Axis.X; @Property(description = "Number of frames to describe a complete phase", category = PropertyCategory.SECONDARY) @PropertyMin(1) private int frames = 60; @Property(description = "Current frames", category = PropertyCategory.SECONDARY) @PropertyMin(1) private int frame = 33; @Property(category = PropertyCategory.SECONDARY, description = "Global zoom factor for the whole image") @PropertyMin(0.01) @PropertyMax(100.0) private double zoom = 1.0; @Property(description = "Damping of the wave", category = PropertyCategory.SECONDARY) private double damping = -0.5; @Property(description = "Amplitude of the wave") @PropertyMin(0.0) private double amplitude = 30.0; @Property(description = "Wavelength of the wave") @PropertyMin(0.0) private double wavelength = 150.0; @Property(description = "Phase shift of the Wave", category = PropertyCategory.SECONDARY) private double phase = 0.0; // TODO unklar: was genau macht dieser Parameter? // @Property() private double shift = 0.0; @Property(description = "Damping on/off", category = PropertyCategory.SECONDARY) private boolean damp = true; @Property(description = "Repeat the image at the borders") private boolean wrap = true; @Override protected void performPixelTransformation(WFImage pImg) { SimpleImage img = (SimpleImage) pImg; if (!damp) { if (axis == Axis.X) waveX(img); else waveY(img); } else { if (axis == Axis.X) waveX_damp(img); else waveY_damp(img); } } private void waveY_damp(SimpleImage pImg) { double damping = this.damping; double cx = this.centreX - 0.5; double cy = this.centreY - 0.5; double zoom = 1.0 / this.zoom; int width = pImg.getImageWidth(); int height = pImg.getImageHeight(); double PI2 = 2.0 * Math.PI; double shift = this.shift; double wavelength = this.wavelength * this.zoom; double amplitude = 0.0 - this.amplitude * this.zoom; double phase = this.phase; double amp = amplitude; double t = this.frames != 0 ? (double) this.frame / (double) this.frames : 0.0; shift = 2.0 * shift / (double) (width - 1); double w1 = (double) width - 1.0; double h1 = (double) height - 1.0; Pixel pPixel = new Pixel(); for (int pY = 0; pY < height; pY++) { for (int pX = 0; pX < width; pX++) { pPixel.setARGBValue(pImg.getARGBValue(pX, pY)); double x0 = (double) pX - cx; double y0 = (double) pY - cy; double sangle = shift * pX; double dl = (y0 - sangle) / wavelength; double dl2 = dl - sangle; if (dl2 < 0) dl2 = 0.0 - dl2; amp = amplitude * Math.exp(dl2 * damping); double zz = amp * Math.sin((PI2 * (t - dl)) + phase); x0 += zz; double x = x0 * zoom + cx; double y = y0 * zoom + cy; /* color-wrapping */ if (this.wrap) { while (x >= ((double) width - 0.5)) x -= (double) (width - 1); while ((int) x < 0.5) x += (double) (width - 1); while (y >= ((double) height - 0.5)) y -= (double) (height - 1); while ((int) y < 0.5) y += (double) (height - 1); } /* render it */ double xi = Tools.fmod33(x); double yi = Tools.fmod33(y); if ((x < 0.0) || (x > w1) || (y < 0.0) || (y > h1)) { pPixel.r = pPixel.g = pPixel.b = 0; } else { readSrcPixels(x, y); pPixel.r = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.r) + xi * (srcQ.r)) + yi * ((1.0 - xi) * (srcR.r) + xi * (srcS.r)))); pPixel.g = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.g) + xi * (srcQ.g)) + yi * ((1.0 - xi) * (srcR.g) + xi * (srcS.g)))); pPixel.b = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.b) + xi * (srcQ.b)) + yi * ((1.0 - xi) * (srcR.b) + xi * (srcS.b)))); } pImg.setRGB(pX, pY, pPixel.r, pPixel.g, pPixel.b); } } } private void waveX_damp(SimpleImage pImg) { double damping = this.damping; double cx = this.centreX - 0.5; double cy = this.centreY - 0.5; double zoom = 1.0 / this.zoom; int width = pImg.getImageWidth(); int height = pImg.getImageHeight(); double PI2 = 2.0 * Math.PI; double shift = this.shift; double wavelength = this.wavelength * this.zoom; double amplitude = 0.0 - this.amplitude * this.zoom; double phase = this.phase; double amp = amplitude; double t = this.frames != 0 ? (double) this.frame / (double) this.frames : 0.0; shift = 2.0 * shift / (double) (height - 1); double w1 = (double) width - 1.0; double h1 = (double) height - 1.0; Pixel pPixel = new Pixel(); for (int pY = 0; pY < height; pY++) { for (int pX = 0; pX < width; pX++) { pPixel.setARGBValue(pImg.getARGBValue(pX, pY)); /* transform the point */ double x0 = (double) pX - cx; double y0 = (double) pY - cy; double sangle = shift * pY; double dl = (x0 - sangle) / wavelength; double dl2 = dl - sangle; if (dl2 < 0) dl2 = 0.0 - dl2; amp = amplitude * Math.exp(dl2 * damping); double zz = amp * Math.sin((PI2 * (t - dl)) + phase); y0 += zz; double x = x0 * zoom + cx; double y = y0 * zoom + cy; /* color-wrapping */ if (this.wrap) { while (x >= ((double) width - 0.5)) x -= (double) (width - 1); while ((int) x < 0.5) x += (double) (width - 1); while (y >= ((double) height - 0.5)) y -= (double) (height - 1); while ((int) y < 0.5) y += (double) (height - 1); } /* render it */ double xi = Tools.fmod33(x); double yi = Tools.fmod33(y); if ((x < 0.0) || (x > w1) || (y < 0.0) || (y > h1)) { pPixel.r = pPixel.g = pPixel.b = 0; } else { readSrcPixels(x, y); pPixel.r = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.r) + xi * (srcQ.r)) + yi * ((1.0 - xi) * (srcR.r) + xi * (srcS.r)))); pPixel.g = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.g) + xi * (srcQ.g)) + yi * ((1.0 - xi) * (srcR.g) + xi * (srcS.g)))); pPixel.b = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.b) + xi * (srcQ.b)) + yi * ((1.0 - xi) * (srcR.b) + xi * (srcS.b)))); } pImg.setRGB(pX, pY, pPixel.r, pPixel.g, pPixel.b); } } } private void waveY(SimpleImage pImg) { double cx = this.centreX - 0.5; double cy = this.centreY - 0.5; double zoom = 1.0 / this.zoom; int width = pImg.getImageWidth(); int height = pImg.getImageHeight(); double PI2 = 2.0 * Math.PI; double shift = this.shift; double wavelength = this.wavelength * this.zoom; double amplitude = 0.0 - this.amplitude * this.zoom; double phase = this.phase; shift = 2.0 * shift / (double) (width - 1); double amp = amplitude; double t = this.frames != 0 ? (double) this.frame / (double) this.frames : 0.0; double w1 = (double) width - 1.0; double h1 = (double) height - 1.0; Pixel pPixel = new Pixel(); for (int pY = 0; pY < height; pY++) { for (int pX = 0; pX < width; pX++) { pPixel.setARGBValue(pImg.getARGBValue(pX, pY)); /* transform the point */ double x0 = (double) pX - cx; double y0 = (double) pY - cy; double dl = (y0 - shift * pX) / wavelength; double zz = amp * Math.sin((PI2 * (t - dl)) + phase); x0 += zz; double x = x0 * zoom + cx; double y = y0 * zoom + cy; /* color-wrapping */ if (this.wrap) { while (x >= ((double) width - 0.5)) x -= (double) (width - 1); while ((int) x < 0.5) x += (double) (width - 1); while (y >= ((double) height - 0.5)) y -= (double) (height - 1); while ((int) y < 0.5) y += (double) (height - 1); } /* render it */ double xi = Tools.fmod33(x); double yi = Tools.fmod33(y); if ((x < 0.0) || (x > w1) || (y < 0.0) || (y > h1)) { pPixel.r = pPixel.g = pPixel.b = 0; } else { readSrcPixels(x, y); pPixel.r = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.r) + xi * (srcQ.r)) + yi * ((1.0 - xi) * (srcR.r) + xi * (srcS.r)))); pPixel.g = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.g) + xi * (srcQ.g)) + yi * ((1.0 - xi) * (srcR.g) + xi * (srcS.g)))); pPixel.b = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.b) + xi * (srcQ.b)) + yi * ((1.0 - xi) * (srcR.b) + xi * (srcS.b)))); } pImg.setRGB(pX, pY, pPixel.r, pPixel.g, pPixel.b); } } } private void waveX(SimpleImage pImg) { double cx = this.centreX - 0.5; double cy = this.centreY - 0.5; double zoom = 1.0 / this.zoom; int width = pImg.getImageWidth(); int height = pImg.getImageHeight(); double PI2 = 2.0 * Math.PI; double shift = this.shift; double wavelength = this.wavelength * this.zoom; double amplitude = 0.0 - this.amplitude * this.zoom; double phase = this.phase; shift = 2.0 * shift / (double) (height - 1); double amp = amplitude; double t = this.frames != 0 ? (double) this.frame / (double) this.frames : 0.0; double w1 = (double) width - 1.0; double h1 = (double) height - 1.0; Pixel pPixel = new Pixel(); for (int pY = 0; pY < height; pY++) { for (int pX = 0; pX < width; pX++) { pPixel.setARGBValue(pImg.getARGBValue(pX, pY)); /* transform the point */ double x0 = (double) pX - cx; double y0 = (double) pY - cy; double dl = (x0 - shift * pY) / wavelength; double zz = amp * Math.sin((PI2 * (t - dl)) + phase); y0 += zz; double x = x0 * zoom + cx; double y = y0 * zoom + cy; /* color-wrapping */ if (this.wrap) { while (x >= ((double) width - 0.5)) x -= (double) (width - 1); while ((int) x < 0.5) x += (double) (width - 1); while (y >= ((double) height - 0.5)) y -= (double) (height - 1); while ((int) y < 0.5) y += (double) (height - 1); } /* render it */ double xi = Tools.fmod33(x); double yi = Tools.fmod33(y); if ((x < 0.0) || (x > w1) || (y < 0.0) || (y > h1)) { pPixel.r = pPixel.g = pPixel.b = 0; } else { readSrcPixels(x, y); pPixel.r = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.r) + xi * (srcQ.r)) + yi * ((1.0 - xi) * (srcR.r) + xi * (srcS.r)))); pPixel.g = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.g) + xi * (srcQ.g)) + yi * ((1.0 - xi) * (srcR.g) + xi * (srcS.g)))); pPixel.b = roundColor(((1.0 - yi) * ((1.0 - xi) * (srcP.b) + xi * (srcQ.b)) + yi * ((1.0 - xi) * (srcR.b) + xi * (srcS.b)))); } pImg.setRGB(pX, pY, pPixel.r, pPixel.g, pPixel.b); } } } @Override protected void cleanupTransformation(WFImage pImg) { super.cleanupTransformation(pImg); applySmoothing((SimpleImage) pImg, 1); } @Override public void initDefaultParams(WFImage pImg) { int width = pImg.getImageWidth(); int height = pImg.getImageHeight(); double rr = Math.sqrt(width * width + height * height); centreX = 0.10; centreY = -0.50; axis = Axis.X; frames = 60; frame = 33; zoom = 1.0; damping = -0.5; amplitude = Math.round(rr / 20.0); wavelength = Math.round(rr / 4.0); phase = 0.0; shift = 0.0; damp = true; wrap = true; } public double getCentreX() { return centreX; } public void setCentreX(double centreX) { this.centreX = centreX; } public double getCentreY() { return centreY; } public void setCentreY(double centreY) { this.centreY = centreY; } public Axis getAxis() { return axis; } public void setAxis(Axis axis) { this.axis = axis; } public int getFrames() { return frames; } public void setFrames(int frames) { this.frames = frames; } public int getFrame() { return frame; } public void setFrame(int frame) { this.frame = frame; } public double getZoom() { return zoom; } public void setZoom(double zoom) { this.zoom = zoom; } public double getDamping() { return damping; } public void setDamping(double damping) { this.damping = damping; } public double getAmplitude() { return amplitude; } public void setAmplitude(double amplitude) { this.amplitude = amplitude; } public double getWavelength() { return wavelength; } public void setWavelength(double wavelength) { this.wavelength = wavelength; } public double getPhase() { return phase; } public void setPhase(double phase) { this.phase = phase; } public double getShift() { return shift; } public void setShift(double shift) { this.shift = shift; } public boolean isDamp() { return damp; } public void setDamp(boolean damp) { this.damp = damp; } public boolean isWrap() { return wrap; } public void setWrap(boolean wrap) { this.wrap = wrap; } public static class AxisEditor extends ComboBoxPropertyEditor { public AxisEditor() { super(); setAvailableValues(new Axis[] { Axis.X, Axis.Y }); } } }