/*
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.envelope;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Map;
import java.util.WeakHashMap;
import org.jwildfire.base.Tools;
public class Envelope implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
public enum Interpolation {
SPLINE, BEZIER, LINEAR
}
public enum EditMode {
DRAG_POINTS, DRAG_CURVE_HORIZ, DRAG_CURVE_VERT, SCALE_CURVE_HORIZ, SCALE_CURVE_VERT
}
private int viewXMin = -10;
private int viewXMax = 70;
private double viewYMin = -120.0;
private double viewYMax = 120.0;
private Interpolation interpolation = Interpolation.SPLINE;
private int selectedIdx = 0;
private int x[];
private int xmin, xmax;
private double y[];
private boolean locked;
private boolean useBisection = false;
public Envelope clone() {
Envelope res = new Envelope();
res.viewXMin = viewXMin;
res.viewXMax = viewXMax;
res.viewYMin = viewYMin;
res.viewYMax = viewYMax;
res.interpolation = interpolation;
res.selectedIdx = selectedIdx;
res.x = new int[x.length];
res.y = new double[x.length];
for (int i = 0; i < x.length; i++) {
res.x[i] = x[i];
res.y[i] = y[i];
}
res.xmin = xmin;
res.xmax = xmax;
return res;
}
public Envelope() {
x = new int[0];
y = new double[0];
updateMinMax();
}
public Envelope(double value) {
x = new int[1];
x[0] = 1;
y = new double[1];
y[0] = value;
updateMinMax();
}
public Envelope(int[] pX, double pY[]) {
if (pX == null || pY == null || pX.length != pY.length) {
throw new IllegalArgumentException();
}
x = pX;
y = pY;
updateMinMax();
}
public Envelope(double pValue, int pViewXMin, int pViewXMax, double pViewYMin, double pViewYMax) {
x = new int[1];
x[0] = 1;
y = new double[1];
y[0] = pValue;
viewXMin = pViewXMin;
viewXMax = pViewXMax;
viewYMin = pViewYMin;
viewYMax = pViewYMax;
updateMinMax();
}
public int getViewXMin() {
return viewXMin;
}
public void setViewXMin(int viewXMin) {
this.viewXMin = viewXMin;
}
public int getViewXMax() {
return viewXMax;
}
public void setViewXMax(int viewXMax) {
this.viewXMax = viewXMax;
}
public double getViewYMin() {
return viewYMin;
}
public void setViewYMin(double viewYMin) {
this.viewYMin = viewYMin;
}
public double getViewYMax() {
return viewYMax;
}
public void setViewYMax(double viewYMax) {
this.viewYMax = viewYMax;
}
public void setInterpolation(Interpolation interpolation) {
this.interpolation = interpolation;
}
public Interpolation getInterpolation() {
return interpolation;
}
public int size() {
return x.length;
}
public void setSelectedX(int pX) {
x[selectedIdx] = pX;
updateMinMax();
}
public void setSelectedY(double pY) {
y[selectedIdx] = pY;
}
public void select(int pIdx) {
if ((pIdx < 0) || (pIdx >= x.length))
throw new IllegalArgumentException(String.valueOf(pIdx));
selectedIdx = pIdx;
}
public int getSelectedX() {
return x[selectedIdx];
}
public double getSelectedY() {
return y[selectedIdx];
}
public void clear() {
viewXMin = -10;
viewXMax = 70;
viewYMin = -120.0;
viewYMax = 120.0;
interpolation = Interpolation.SPLINE;
selectedIdx = 0;
x = new int[1];
y = new double[1];
updateMinMax();
}
public int[] getX() {
return x;
}
public double[] getY() {
return y;
}
public int getSelectedIdx() {
return selectedIdx;
}
public void setValues(int[] pX, double[] pY) {
if (pX.length != pY.length)
throw new IllegalArgumentException();
x = pX;
y = pY;
if (selectedIdx >= x.length)
selectedIdx--;
if (selectedIdx < 0 && x.length > 0)
selectedIdx = 0;
updateMinMax();
}
private static final Map<InterpolatedPointsKey, InterpolatedPoints> interpolatedPointCache = new WeakHashMap<InterpolatedPointsKey, InterpolatedPoints>();
private static InterpolatedPoints getInterpolatedPoints(int pX[], double pY[], Interpolation pInterpolation) {
InterpolatedPointsKey key = new InterpolatedPointsKey(pX, pY, pInterpolation);
InterpolatedPoints res = interpolatedPointCache.get(key);
if (res == null) {
res = new InterpolatedPoints(pX, pY, pInterpolation);
interpolatedPointCache.put(key, res);
}
return res;
}
private static class InterpolatedPointsKey {
private final int x[];
private final double y[];
private final Interpolation interpolation;
private final int hashCode;
public InterpolatedPointsKey(int pX[], double pY[], Interpolation pInterpolation) {
x = pX;
y = pY;
interpolation = pInterpolation;
hashCode = calcHashCode();
}
@Override
public int hashCode() {
return hashCode;
}
private int calcHashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((interpolation == null) ? 0 : interpolation.hashCode());
result = prime * result + Arrays.hashCode(x);
result = prime * result + Arrays.hashCode(y);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
InterpolatedPointsKey other = (InterpolatedPointsKey) obj;
if (interpolation != other.interpolation)
return false;
if (!Arrays.equals(x, other.x))
return false;
if (!Arrays.equals(y, other.y))
return false;
return true;
}
}
private static class InterpolatedPoints {
private final double vSX[];
private final double vSY[];
private final int vSNum;
public InterpolatedPoints(int pX[], double pY[], Interpolation pInterpolation) {
int size = pX.length;
int subdiv = org.jwildfire.envelope.Interpolation.calcSubDivPRV(pX, size);
org.jwildfire.envelope.Interpolation interpolationX, interpolationY;
if (size > 2) {
switch (pInterpolation) {
case SPLINE:
interpolationX = new SplineInterpolation();
interpolationY = new SplineInterpolation();
break;
case BEZIER:
interpolationX = new BezierInterpolation();
interpolationY = new BezierInterpolation();
break;
default:
interpolationX = new LinearInterpolation();
interpolationY = new LinearInterpolation();
break;
}
}
else {
interpolationX = new LinearInterpolation();
interpolationY = new LinearInterpolation();
}
interpolationX.setSrc(pX);
interpolationX.setSnum(size);
interpolationX.setSubdiv(subdiv);
interpolationX.interpolate();
interpolationY.setSrc(pY);
interpolationY.setSnum(size);
interpolationY.setSubdiv(subdiv);
interpolationY.interpolate();
if (interpolationX.getDnum() != interpolationY.getDnum())
throw new IllegalStateException();
vSNum = interpolationX.getDnum();
vSX = interpolationX.getDest();
vSY = interpolationY.getDest();
}
public double[] getvSX() {
return vSX;
}
public double[] getvSY() {
return vSY;
}
public int getvSNum() {
return vSNum;
}
}
private void updateMinMax() {
if (x.length > 0) {
xmin = xmax = x[0];
for (int i = 1; i < size(); i++) {
if (x[i] < xmin)
xmin = x[i];
else if (x[i] > xmax)
xmax = x[i];
}
}
else {
xmin = xmax = 0;
}
}
public double evaluate(double pTime) {
if (size() == 0)
return 0.0;
else if (size() == 1)
return y[0];
else if (pTime <= xmin)
return y[0];
else if (pTime >= xmax)
return y[size() - 1];
int indl = -1, indr = -1;
InterpolatedPoints iPoints = getInterpolatedPoints(x, y, interpolation);
double vSX[] = iPoints.getvSX();
double vSY[] = iPoints.getvSY();
int vSNum = vSX.length;
if (useBisection) {
int low = 0;
int high = vSNum - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
double midVal = vSX[mid];
if (midVal < pTime) {
low = mid + 1;
indl = mid;
}
else if (midVal > pTime) {
indr = mid;
high = mid - 1;
}
else {
return vSY[mid];
}
}
}
else {
for (int i = 0; i < vSNum; i++) {
if (Tools.FTOI(vSX[i]) <= pTime) {
indl = i;
}
else {
indr = i;
break;
}
}
}
if ((indl >= 0) && (indr >= 0)) {
double xdist = vSX[indr] - vSX[indl];
if (xdist < 0.00000001)
return vSX[indl];
else
return vSY[indl] + (pTime - vSX[indl]) / xdist * (vSY[indr] - vSY[indl]);
}
else if (indl >= 0) {
return vSY[indl];
}
else if (indr >= 0) {
return vSY[indr];
}
else {
return 0.0;
}
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public void setSelectedIdx(int pSelectedIdx) {
selectedIdx = pSelectedIdx;
}
@Override
public String toString() {
return "Envelope [" + x.length + "]";
}
public void setUseBisection(boolean pUseBisection) {
useBisection = pUseBisection;
}
public void smooth(int pSize) {
if (pSize >= 3 && y != null && y.length > pSize) {
double newY[] = new double[y.length];
int s = pSize / 2;
for (int i = 0; i < y.length; i++) {
double v = 0.0;
int w = 0;
for (int j = i - s; j <= i + s; j++) {
if (j >= 0 && j < y.length) {
v += y[j];
w++;
}
}
newY[i] = v / (double) w;
}
y = newY;
}
}
}