/* 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.create.tina.variation; import static org.jwildfire.base.mathlib.MathLib.FALSE; import static org.jwildfire.base.mathlib.MathLib.M_2PI; import static org.jwildfire.base.mathlib.MathLib.M_PI_2; import static org.jwildfire.base.mathlib.MathLib.SMALL_EPSILON; import static org.jwildfire.base.mathlib.MathLib.TRUE; import static org.jwildfire.base.mathlib.MathLib.atan; import static org.jwildfire.base.mathlib.MathLib.cos; import static org.jwildfire.base.mathlib.MathLib.fabs; import static org.jwildfire.base.mathlib.MathLib.floor; import static org.jwildfire.base.mathlib.MathLib.pow; import static org.jwildfire.base.mathlib.MathLib.sin; import static org.jwildfire.base.mathlib.MathLib.sqrt; import static org.jwildfire.base.mathlib.MathLib.tan; import java.io.Serializable; import org.jwildfire.base.Tools; import org.jwildfire.create.tina.base.Layer; import org.jwildfire.create.tina.base.XForm; import org.jwildfire.create.tina.base.XYZPoint; public class NBlurFunc extends VariationFunc { private static final long serialVersionUID = 1L; public static final String PARAM_NUMEDGES = "numEdges"; public static final String PARAM_NUMSTRIPES = "numStripes"; public static final String PARAM_RATIOSTRIPES = "ratioStripes"; public static final String PARAM_RATIOHOLE = "ratioHole"; public static final String PARAM_CIRCUMCIRCLE = "circumCircle"; private static final String PARAM_ADJUSTTOLINEAR = "adjustToLinear"; private static final String PARAM_EQUALBLUR = "equalBlur"; private static final String PARAM_EXACTCALC = "exactCalc"; private static final String PARAM_HIGHLIGHTEDGES = "highlightEdges"; private static final String[] paramNames = { PARAM_NUMEDGES, PARAM_NUMSTRIPES, PARAM_RATIOSTRIPES, PARAM_RATIOHOLE, PARAM_CIRCUMCIRCLE, PARAM_ADJUSTTOLINEAR, PARAM_EQUALBLUR, PARAM_EXACTCALC, PARAM_HIGHLIGHTEDGES }; private int numEdges = 3; private int numStripes = 0; private double ratioStripes = 1.0; private double ratioHole = 0.0; private int circumCircle = 0; private int adjustToLinear = 1; private int equalBlur = 1; private int exactCalc = 0; private double highlightEdges = 1.0; private RandXYData _randXYData = new RandXYData(); @Override public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) { // nBlur by FractalDesire, http://fractaldesire.deviantart.com/art/nBlur-plugin-190401515 //*********Adjustment of width of shape********* if (this.adjustToLinear == TRUE) { if ((this.numEdges) % 4 == 0) { pAmount /= sqrt(2.0 - 2.0 * cos(this._midAngle * ((double) this.numEdges / 2.0 - 1.0))) / 2.0; } else { pAmount /= sqrt(2.0 - 2.0 * cos(this._midAngle * floor(((double) this.numEdges / 2.0)))) / 2.0; } } // randXY(pContext, _randXYData); //********Exact calculation slower - interpolated calculation faster******** if ((this.exactCalc == TRUE) && (this.circumCircle == FALSE)) { while ((_randXYData.lenXY < _randXYData.lenInnerEdges) || (_randXYData.lenXY > _randXYData.lenOuterEdges)) randXY(pContext, _randXYData); } if ((this.exactCalc == TRUE) && (this.circumCircle == TRUE)) { while (_randXYData.lenXY < _randXYData.lenInnerEdges) randXY(pContext, _randXYData); } double xTmp = _randXYData.x; double yTmp = _randXYData.y; //************************************************************************** //********Begin of horizontal adjustment (rotation)******** double x = this._cosa * xTmp - this._sina * yTmp; double y = this._sina * xTmp + this._cosa * yTmp; //*********End of horizontal adjustment (rotation)********* pVarTP.x += pAmount * x; pVarTP.y += pAmount * y; if (pContext.isPreserveZCoordinate()) { pVarTP.z += pAmount * pAffineTP.z; } } private static class RandXYData implements Serializable { private static final long serialVersionUID = 1L; public double x, y; public double lenXY; public double lenOuterEdges, lenInnerEdges; } private void randXY(FlameTransformationContext pContext, RandXYData data) { double x, y; double xTmp, yTmp, lenOuterEdges, lenInnerEdges; double angXY, lenXY; double ranTmp, angTmp, angMem; double ratioTmp, ratioTmpNum, ratioTmpDen; double speedCalcTmp; int count; if (this.exactCalc == TRUE) { angXY = pContext.random() * M_2PI; } else { angXY = (atan(this._arc_tan1 * (pContext.random() - 0.5)) / this._arc_tan2 + 0.5 + (double) (rand(pContext) % this.numEdges)) * this._midAngle; } x = sin(angXY); y = cos(angXY); angMem = angXY; while (angXY > this._midAngle) { angXY -= this._midAngle; } //********Begin of xy-calculation of radial stripes******** if (this._hasStripes == TRUE) { angTmp = this._angStart; count = 0; while (angXY > angTmp) { angTmp += this._angStripes; if (angTmp > this._midAngle) angTmp = this._midAngle; count++; } if (angTmp != this._midAngle) angTmp -= this._angStart; if (this._negStripes == FALSE) { if ((count % 2) != 0) { if (angXY > angTmp) { angXY = angXY + this._angStart; angMem = angMem + this._angStart; x = sin(angMem); y = cos(angMem); angTmp += this._angStripes; count++; } else { angXY = angXY - this._angStart; angMem = angMem - this._angStart; x = sin(angMem); y = cos(angMem); angTmp -= this._angStripes; count--; } } if (((count % 2) == 0) && (this.ratioStripes > 1.0)) { if ((angXY > angTmp) && (count != this._maxStripes)) { angMem = angMem - angXY + angTmp + (angXY - angTmp) / this._angStart * this.ratioStripes * this._angStart; angXY = angTmp + (angXY - angTmp) / this._angStart * this.ratioStripes * this._angStart; x = sin(angMem); y = cos(angMem); } else { angMem = angMem - angXY + angTmp - (angTmp - angXY) / this._angStart * this.ratioStripes * this._angStart; angXY = angTmp + (angXY - angTmp) / this._angStart * this.ratioStripes * this._angStart; x = sin(angMem); y = cos(angMem); } } if (((count % 2) == 0) && (this.ratioStripes < 1.0)) { if ((fabs(angXY - angTmp) > this._speedCalc2) && (count != (this._maxStripes))) { if ((angXY - angTmp) > this._speedCalc2) { ratioTmpNum = (angXY - (angTmp + this._speedCalc2)) * this._speedCalc2; ratioTmpDen = this._angStart - this._speedCalc2; ratioTmp = ratioTmpNum / ratioTmpDen; double a = (angMem - angXY + angTmp + ratioTmp); x = sin(a); y = cos(a); angXY = angTmp + ratioTmp; } if ((angTmp - angXY) > this._speedCalc2) { ratioTmpNum = ((angTmp - this._speedCalc2 - angXY)) * this._speedCalc2; ratioTmpDen = this._angStart - this._speedCalc2; ratioTmp = ratioTmpNum / ratioTmpDen; double a = (angMem - angXY + angTmp - ratioTmp); x = sin(a); y = cos(a); angXY = angTmp - ratioTmp; } } if (count == this._maxStripes) { if ((angTmp - angXY) > this._speedCalc2) { ratioTmpNum = ((angTmp - this._speedCalc2 - angXY)) * this._speedCalc2; ratioTmpDen = this._angStart - this._speedCalc2; ratioTmp = ratioTmpNum / ratioTmpDen; double a = (angMem - angXY + angTmp - ratioTmp); x = sin(a); y = cos(a); angXY = angTmp - ratioTmp; } } } } else { //********Change ratio and ratioComplement******** ratioTmp = this.ratioStripes; this.ratioStripes = this._nb_ratioComplement; this._nb_ratioComplement = ratioTmp; speedCalcTmp = this._speedCalc1; this._speedCalc1 = this._speedCalc2; this._speedCalc2 = speedCalcTmp; //************************************************ if ((count % 2) == 0) { if ((angXY > angTmp) && (count != this._maxStripes)) { angXY = angXY + this._angStart; angMem = angMem + this._angStart; x = sin(angMem); y = cos(angMem); angTmp += this._angStripes; count++; } else { angXY = angXY - this._angStart; angMem = angMem - this._angStart; x = sin(angMem); y = cos(angMem); angTmp -= this._angStripes; count--; } } if (((count % 2) != 0) && (this.ratioStripes > 1.0)) { if ((angXY > angTmp) && (count != this._maxStripes)) { angMem = angMem - angXY + angTmp + (angXY - angTmp) / this._angStart * this.ratioStripes * this._angStart; angXY = angTmp + (angXY - angTmp) / this._angStart * this.ratioStripes * this._angStart; x = sin(angMem); y = cos(angMem); } else { angMem = angMem - angXY + angTmp - (angTmp - angXY) / this._angStart * this.ratioStripes * this._angStart; angXY = angTmp + (angXY - angTmp) / this._angStart * this.ratioStripes * this._angStart; x = sin(angMem); y = cos(angMem); } } if (((count % 2) != 0) && (this.ratioStripes < 1.0)) { if ((fabs(angXY - angTmp) > this._speedCalc2) && (count != this._maxStripes)) { if ((angXY - angTmp) > this._speedCalc2) { ratioTmpNum = (angXY - (angTmp + this._speedCalc2)) * this._speedCalc2; ratioTmpDen = this._angStart - this._speedCalc2; ratioTmp = ratioTmpNum / ratioTmpDen; double a = (angMem - angXY + angTmp + ratioTmp); x = sin(a); y = cos(a); angXY = angTmp + ratioTmp; } if ((angTmp - angXY) > this._speedCalc2) { ratioTmpNum = ((angTmp - this._speedCalc2 - angXY)) * this._speedCalc2; ratioTmpDen = this._angStart - this._speedCalc2; ratioTmp = ratioTmpNum / ratioTmpDen; double a = (angMem - angXY + angTmp - ratioTmp); x = sin(a); y = cos(a); angXY = angTmp - ratioTmp; } } if (count == this._maxStripes) { angTmp = this._midAngle; if ((angTmp - angXY) > this._speedCalc2) { ratioTmpNum = ((angTmp - this._speedCalc2 - angXY)) * this._speedCalc2; ratioTmpDen = this._angStart - this._speedCalc2; ratioTmp = ratioTmpNum / ratioTmpDen; double a = (angMem - angXY + angTmp - ratioTmp); x = sin(a); y = cos(a); angXY = angTmp - ratioTmp; } } } //********Restore ratio and ratioComplement******* ratioTmp = this.ratioStripes; this.ratioStripes = this._nb_ratioComplement; this._nb_ratioComplement = ratioTmp; speedCalcTmp = this._speedCalc1; this._speedCalc1 = this._speedCalc2; this._speedCalc2 = speedCalcTmp; //************************************************ } } //********End of xy-calculation of radial stripes******** //********Begin of calculation of edge limits******** xTmp = this._tan90_m_2 / (this._tan90_m_2 - tan(angXY)); yTmp = xTmp * tan(angXY); lenOuterEdges = sqrt(xTmp * xTmp + yTmp * yTmp); //*********End of calculation of edge limits******** //********Begin of radius-calculation (optionally hole)******** if (this.exactCalc == TRUE) { if (this.equalBlur == TRUE) ranTmp = sqrt(pContext.random()); else ranTmp = pContext.random(); } else { if (this.circumCircle == TRUE) { if (this.equalBlur == TRUE) ranTmp = sqrt(pContext.random()); else ranTmp = pContext.random(); } else { if (this.equalBlur == TRUE) ranTmp = sqrt(pContext.random()) * lenOuterEdges; else ranTmp = pContext.random() * lenOuterEdges; } } lenInnerEdges = this.ratioHole * lenOuterEdges; if (this.exactCalc == FALSE) { if (ranTmp < lenInnerEdges) { if (this.circumCircle == TRUE) { if (this.equalBlur == TRUE) ranTmp = lenInnerEdges + sqrt(pContext.random()) * (1.0 - lenInnerEdges + SMALL_EPSILON); else ranTmp = lenInnerEdges + pContext.random() * (1.0 - lenInnerEdges + SMALL_EPSILON); } else { if (this.equalBlur == TRUE) ranTmp = lenInnerEdges + sqrt(pContext.random()) * (lenOuterEdges - lenInnerEdges); else ranTmp = lenInnerEdges + pContext.random() * (lenOuterEdges - lenInnerEdges); } } } //if(VAR(hasStripes)==TRUE) ranTmp = pow(ranTmp,0.75); x *= ranTmp; y *= ranTmp; lenXY = sqrt(x * x + y * y); //*********End of radius-calculation (optionally hole)********* data.x = x; data.y = y; data.lenXY = lenXY; data.lenOuterEdges = lenOuterEdges; data.lenInnerEdges = lenInnerEdges; } private static final int RAND_MAX = 32767; private int rand(FlameTransformationContext pContext) { return pContext.random(RAND_MAX); } @Override public String[] getParameterNames() { return paramNames; } @Override public String[] getParameterAlternativeNames() { return new String[] { "nb_numEdges", "nb_numStripes", "nb_ratioStripes", "nb_ratioHole", "nb_circumCircle", "nb_adjustToLinear", "nb_equalBlur", "nb_exactCalc", "nb_highlightEdges" }; } @Override public Object[] getParameterValues() { return new Object[] { numEdges, numStripes, ratioStripes, ratioHole, circumCircle, adjustToLinear, equalBlur, exactCalc, highlightEdges }; } @Override public void setParameter(String pName, double pValue) { if (PARAM_NUMEDGES.equalsIgnoreCase(pName)) numEdges = Tools.FTOI(pValue); else if (PARAM_NUMSTRIPES.equalsIgnoreCase(pName)) numStripes = Tools.FTOI(pValue); else if (PARAM_RATIOSTRIPES.equalsIgnoreCase(pName)) ratioStripes = limitVal(pValue, 0.0, 2.0); else if (PARAM_RATIOHOLE.equalsIgnoreCase(pName)) ratioHole = limitVal(pValue, 0.0, 1.0); else if (PARAM_CIRCUMCIRCLE.equalsIgnoreCase(pName)) circumCircle = limitIntVal(Tools.FTOI(pValue), 0, 1); else if (PARAM_ADJUSTTOLINEAR.equalsIgnoreCase(pName)) adjustToLinear = limitIntVal(Tools.FTOI(pValue), 0, 1); else if (PARAM_EQUALBLUR.equalsIgnoreCase(pName)) equalBlur = limitIntVal(Tools.FTOI(pValue), 0, 1); else if (PARAM_EXACTCALC.equalsIgnoreCase(pName)) exactCalc = limitIntVal(Tools.FTOI(pValue), 0, 1); else if (PARAM_HIGHLIGHTEDGES.equalsIgnoreCase(pName)) highlightEdges = pValue; else throw new IllegalArgumentException(pName); } @Override public String getName() { return "nBlur"; } private double _midAngle, _angStripes, _angStart; private double _tan90_m_2, _sina, _cosa; private int _hasStripes, _negStripes; //**********Variables for speed up*********** private double _speedCalc1, _speedCalc2; private double _maxStripes; private double _arc_tan1, _arc_tan2; private double _nb_ratioComplement; @Override public void init(FlameTransformationContext pContext, Layer pLayer, XForm pXForm, double pAmount) { if (this.numEdges < 3) this.numEdges = 3; //*********Prepare stripes related stuff********* if (this.numStripes != 0) { this._hasStripes = TRUE; if (this.numStripes < 0) { this._negStripes = TRUE; this.numStripes *= -1; } else { this._negStripes = FALSE; } } else { this._hasStripes = FALSE; this._negStripes = FALSE; } //**********Prepare angle related stuff********** this._midAngle = M_2PI / (double) this.numEdges; if (this._hasStripes == TRUE) { this._angStripes = this._midAngle / (double) (2 * this.numStripes); this._angStart = this._angStripes / 2.0; this._nb_ratioComplement = 2.0 - this.ratioStripes; } //**********Prepare hole related stuff*********** if ((this.ratioHole > 0.95) && (this.exactCalc == TRUE) && (this.circumCircle == FALSE)) this.ratioHole = 0.95; //*********Prepare edge calculation related stuff********* this._tan90_m_2 = tan(M_PI_2 + this._midAngle / 2.0); double angle = this._midAngle / 2.0; this._sina = sin(angle); this._cosa = cos(angle); //*********Prepare factor of adjustment of interpolated calculation********* if (this.highlightEdges <= 0.1) this.highlightEdges = 0.1; //*********Prepare circumCircle-calculation********* if (this.circumCircle == TRUE) { this.exactCalc = FALSE; this.highlightEdges = 0.1; } //*********Prepare speed up related stuff********* this._speedCalc1 = this._nb_ratioComplement * this._angStart; this._speedCalc2 = this.ratioStripes * this._angStart; this._maxStripes = 2 * this.numStripes; if (this._negStripes == FALSE) { this._arc_tan1 = (13.0 / pow(this.numEdges, 1.3)) * this.highlightEdges; this._arc_tan2 = (2.0 * atan(this._arc_tan1 / (-2.0))); } else { this._arc_tan1 = (7.5 / pow(this.numEdges, 1.3)) * this.highlightEdges; this._arc_tan2 = (2.0 * atan(this._arc_tan1 / (-2.0))); } } }