/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
/*
* The org.opensourcephysics.media.core package defines the Open Source Physics
* media framework for working with video and other media.
*
* Copyright (c) 2014 Douglas Brown and Wolfgang Christian.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* For additional information and documentation on Open Source Physics,
* please see <http://www.opensourcephysics.org/>.
*/
package org.opensourcephysics.media.core;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.TreeSet;
import javax.swing.event.SwingPropertyChangeSupport;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
/**
* This manages point and vector transformations between imagespace and
* worldspace coordinates. Imagespace coordinates of a point refer to its
* pixel position (to sub-pixel precision) relative to the top left corner
* of an image. Worldspace coordinates of the point are its <i>scaled</i> position
* relative to a world reference frame <i>origin</i> and <i>axes</i>.
* Transformations between coordinate spaces depend on the scale
* (image units per world unit), the origin (image position of the
* origin of the world reference frame) and the x-axis direction
* (angle of the world x-axis measured ccw from the image x-axis).
* Any or all of these may vary with frame number.
*
* @author Douglas Brown
* @version 1.0
*/
public class ImageCoordSystem {
// static fields and initializer
protected static NumberFormat decimal = NumberFormat.getNumberInstance(Locale.US);
protected static NumberFormat sci = NumberFormat.getNumberInstance(Locale.US);
static {
((DecimalFormat) decimal).applyPattern("0.00"); //$NON-NLS-1$
((DecimalFormat) sci).applyPattern("0.###E0"); //$NON-NLS-1$
}
// instance fields
private int length;
protected PropertyChangeSupport support;
private Point2D point = new Point2D.Double();
private TransformArray toImage, toWorld;
private DoubleArray scaleX, scaleY;
private DoubleArray originX, originY;
private DoubleArray cosine, sine;
TreeSet<Integer> keyFrames = new TreeSet<Integer>();
protected boolean firePropChange = true;
private boolean isAdjusting = false;
private boolean fixedOrigin = true;
private boolean fixedAngle = true;
private boolean fixedScale = true;
private boolean locked = false;
private boolean updateToKeyFrames = true;
/**
* Constructs an ImageCoordSystem with a default initial array length.
*/
public ImageCoordSystem() {
this(10);
}
/**
* Constructs an ImageCoordSystem with a specified initial array length.
*
* @param length the initial length
*/
public ImageCoordSystem(int length) {
this.length = length;
toImage = new TransformArray(length);
toWorld = new TransformArray(length);
scaleX = new DoubleArray(length, 1);
scaleY = new DoubleArray(length, 1);
originX = new DoubleArray(length, 0);
originY = new DoubleArray(length, 0);
cosine = new DoubleArray(length, 1);
sine = new DoubleArray(length, 0);
support = new SwingPropertyChangeSupport(this);
updateAllTransforms();
}
/**
* Adds a PropertyChangeListener to this coordinate system.
*
* @param listener the object requesting property change notification
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
/**
* Adds a PropertyChangeListener to this coordinate system.
*
* @param property the name of the property of interest to the listener
* @param listener the object requesting property change notification
*/
public void addPropertyChangeListener(String property, PropertyChangeListener listener) {
support.addPropertyChangeListener(property, listener);
}
/**
* Removes a PropertyChangeListener from this coordinate system.
*
* @param listener the listener requesting removal
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
/**
* Removes a PropertyChangeListener for a specified property.
*
* @param property the name of the property
* @param listener the listener to remove
*/
public void removePropertyChangeListener(String property, PropertyChangeListener listener) {
support.removePropertyChangeListener(property, listener);
}
/**
* Sets the locked property. When locked, no changes are allowed.
*
* @param locked <code>true</code> to lock the coordinate system
*/
public void setLocked(boolean locked) {
this.locked = locked;
support.firePropertyChange("locked", null, new Boolean(locked)); //$NON-NLS-1$
}
/**
* Gets the locked property. When locked, no changes are allowed.
*
* @return <code>true</code> if this is locked
*/
public boolean isLocked() {
return locked;
}
/**
* Sets all origins, angles and scales to those at the specified frame.
*
* @param n the frame number
*/
public void setAllValuesToFrame(int n) {
setAllOriginsXY(getOriginX(n), getOriginY(n));
setAllCosineSines(getCosine(n), getSine(n));
setAllScalesXY(getScaleX(n), getScaleY(n));
keyFrames.clear();
}
/**
* Sets the fixed origin property. When it is fixed, it is in the same
* position at all times. When setting fixed origin to true, this sets
* all origins to the position of the origin at frame 0.
*
* @param fixed <code>true</code> to fix the origin
*/
public void setFixedOrigin(boolean fixed) {
setFixedOrigin(fixed, 0);
}
/**
* Sets the fixed origin property. When it is fixed, it is in the same
* position at all times. When setting fixed origin to true, this sets
* all origins to the position of the origin at frame n.
*
* @param fixed <code>true</code> to fix the origin
* @param n the frame number
*/
public void setFixedOrigin(boolean fixed, int n) {
if (fixed && fixedAngle && fixedScale) {
keyFrames.clear();
}
if(fixedOrigin==fixed) {
return;
}
fixedOrigin = fixed;
if(fixed) {
setAllOriginsXY(getOriginX(n), getOriginY(n));
}
support.firePropertyChange("fixed_origin", !fixed, fixed); //$NON-NLS-1$
}
/**
* Gets the fixed origin property.
*
* @return <code>true</code> if origin is fixed
*/
public boolean isFixedOrigin() {
return fixedOrigin;
}
/**
* Sets the fixed angle property. When it is fixed, it is the same
* at all times. When setting fixed angle to true, this sets
* all angles to the angle at frame 0.
*
* @param fixed <code>true</code> to fix the angle
*/
public void setFixedAngle(boolean fixed) {
setFixedAngle(fixed, 0);
}
/**
* Sets the fixed angle property. When it is fixed, it is the same
* at all times. When setting fixed angle to true, this sets
* all angles to the angle at frame n.
*
* @param fixed <code>true</code> to fix the angle
* @param n the frame number
*/
public void setFixedAngle(boolean fixed, int n) {
if (fixed && fixedOrigin && fixedScale) {
keyFrames.clear();
}
if(fixedAngle==fixed) {
return;
}
fixedAngle = fixed;
if(fixed) {
setAllCosineSines(getCosine(n), getSine(n));
}
support.firePropertyChange("fixed_angle", !fixed, fixed); //$NON-NLS-1$
}
/**
* Gets the fixed angle property.
*
* @return <code>true</code> if angle is fixed
*/
public boolean isFixedAngle() {
return fixedAngle;
}
/**
* Sets the fixed scale property. When it is fixed, it is the same
* at all times. When setting fixed scale to true, this sets
* all scales to the scale at frame 0.
*
* @param fixed <code>true</code> to fix the scale
*/
public void setFixedScale(boolean fixed) {
fixedScale = fixed;
}
/**
* Sets the fixed scale property. When it is fixed, it is the same
* at all times. When setting fixed scale to true, this sets
* all scales to the scale at frame n.
*
* @param fixed <code>true</code> to fix the scale
* @param n the frame number
*/
public void setFixedScale(boolean fixed, int n) {
if (fixed && fixedOrigin && fixedAngle) {
keyFrames.clear();
}
if(fixedScale==fixed) {
return;
}
fixedScale = fixed;
if(fixed) {
setAllScalesXY(getScaleX(n), getScaleY(n));
}
support.firePropertyChange("fixed_scale", !fixed, fixed); //$NON-NLS-1$
}
/**
* Gets the fixed scale property.
*
* @return <code>true</code> if scale is fixed
*/
public boolean isFixedScale() {
return fixedScale;
}
/**
* Gets the scale factor (image units per world unit) along the image
* x-axis (width direction) for the specified frame number.
*
* @param n the frame number
* @return the x-axis scale factor
*/
public double getScaleX(int n) {
return scaleX.get(n);
}
/**
* Gets the scale factor (image units per world unit) along the image
* y-axis (height direction) for the specified frame number.
*
* @param n the frame number
* @return the y-axis scale factor
*/
public double getScaleY(int n) {
return scaleY.get(n);
}
/**
* Sets the scale factor (image units per world unit) along the image
* x-axis (width direction) for the specified frame number.
*
* @param n the frame number
* @param value the x scale factor
*/
public void setScaleX(int n, double value) {
if(isLocked()) {
return;
}
if(isFixedScale()) {
setAllScalesX(value);
return;
}
if(n>=length) {
setLength(n+1);
}
if (updateToKeyFrames) {
// end frame is just before first key frame following n
int end = length-1;
for (int next: keyFrames) {
if (next>n) {
end = next-1;
break;
}
}
setScalesX(n, end, value);
keyFrames.add(n);
return;
}
if(scaleX.set(n, value)) {
try {
updateTransforms(n);
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
}
/**
* Sets the scale factor (image units per world unit) along the image
* x-axis (width direction) for all frames.
*
* @param value the x scale factor
*/
public void setAllScalesX(double value) {
if(isLocked()) {
return;
}
if(scaleX.fill(value)) {
updateAllTransforms();
}
}
/**
* Sets the horizontal scale factor for all frames in a specified range.
*
* @param start the start frame
* @param end the end frame
* @param value the x scale factor
*/
public void setScalesX(int start, int end, double value) {
if(isLocked()) {
return;
}
if(scaleX.fill(value, start, end)) {
updateTransforms(start, end);
}
}
/**
* Sets the scale factor (image units per world unit) along the image
* y-axis (height direction) for the specified frame number.
*
* @param n the frame number
* @param value the y scale factor
*/
public void setScaleY(int n, double value) {
if(isLocked()) {
return;
}
if(isFixedScale()) {
setAllScalesY(value);
return;
}
if(n>=length) {
setLength(n+1);
}
if (updateToKeyFrames) {
// end frame is just before first key frame following n
int end = length-1;
for (int next: keyFrames) {
if (next>n) {
end = next-1;
break;
}
}
setScalesY(n, end, value);
keyFrames.add(n);
return;
}
if(scaleY.set(n, value)) {
try {
updateTransforms(n);
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
}
/**
* Sets the scale factor (image units per world unit) along the image
* y-axis (height direction) for all frames.
*
* @param value the y scale factor
*/
public void setAllScalesY(double value) {
if(isLocked()) {
return;
}
if(scaleY.fill(value)) {
updateAllTransforms();
}
}
/**
* Sets the vertical scale factor for all frames in a specified range.
*
* @param start the start frame
* @param end the end frame
* @param value the y scale factor
*/
public void setScalesY(int start, int end, double value) {
if(isLocked()) {
return;
}
if(scaleY.fill(value, start, end)) {
updateTransforms(start, end);
}
}
/**
* Sets the scale factors (image units per world unit) along the
* x-axis and y-axis of the image for the specified frame number.
*
* @param n the frame number
* @param valueX the x scale factor
* @param valueY the y scale factor
*/
public void setScaleXY(int n, double valueX, double valueY) {
if(isLocked()) {
return;
}
if(isFixedScale()) {
setAllScalesXY(valueX, valueY);
return;
}
if(n>=length) {
setLength(n+1);
}
if (updateToKeyFrames) {
// end frame is just before first key frame following n
int end = length-1;
for (int next: keyFrames) {
if (next>n) {
end = next-1;
break;
}
}
setScalesXY(n, end, valueX, valueY);
keyFrames.add(n);
return;
}
boolean changed = scaleX.set(n, valueX);
changed = scaleY.set(n, valueY)||changed;
if(changed) {
try {
updateTransforms(n);
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
}
/**
* Sets the scale factors (image units per world unit) along the
* x-axis and y-axis of the image for all frames.
*
* @param valueX the x scale factor
* @param valueY the y scale factor
*/
public void setAllScalesXY(double valueX, double valueY) {
if(isLocked()) {
return;
}
boolean changed = scaleX.fill(valueX);
changed = scaleY.fill(valueY)||changed;
if(changed) {
updateAllTransforms();
}
}
/**
* Sets the scale factors for all frames in a specified range.
*
* @param start the start frame
* @param end the end frame
* @param valueX the x scale factor
* @param valueY the y scale factor
*/
public void setScalesXY(int start, int end, double valueX, double valueY) {
if(isLocked()) {
return;
}
boolean changed = scaleX.fill(valueX, start, end);
changed = scaleY.fill(valueY, start, end) || changed;
if(changed) {
updateTransforms(start, end);
}
}
/**
* Gets the image x position of the world origin for the specified
* frame number.
*
* @param n the frame number
* @return the image x position of the world origin
*/
public double getOriginX(int n) {
return originX.get(n);
}
/**
* Gets the image y position of the world origin for the specified
* frame number.
*
* @param n the frame number
* @return the image y position of the world origin
*/
public double getOriginY(int n) {
return originY.get(n);
}
/**
* Sets the image position of the world origin for the specified
* frame number.
*
* @param n the frame number
* @param valueX the image x position of the world origin
* @param valueY the image y position of the world origin
*/
public void setOriginXY(int n, double valueX, double valueY) {
if(isLocked()) {
return;
}
if(isFixedOrigin()) {
setAllOriginsXY(valueX, valueY);
return;
}
if(n>=length) {
setLength(n+1);
}
if (updateToKeyFrames) {
// end frame is just before first key frame following n
int end = length-1;
for (int next: keyFrames) {
if (next>n) {
end = next-1;
break;
}
}
setOriginsXY(n, end, valueX, valueY);
keyFrames.add(n);
return;
}
boolean changed = originX.set(n, valueX);
changed = originY.set(n, valueY)||changed;
if(changed) {
try {
updateTransforms(n);
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
}
/**
* Sets the image position of the world origin for all frames.
*
* @param valueX the image x position of the world origin
* @param valueY the image y position of the world origin
*/
public void setAllOriginsXY(double valueX, double valueY) {
if(isLocked()) {
return;
}
boolean changed = originX.fill(valueX);
changed = originY.fill(valueY)||changed;
if(changed) {
updateAllTransforms();
}
}
/**
* Sets the image position of the world origin for all frames
* in a specified range.
*
* @param start the start frame
* @param end the end frame
* @param valueX the image x position of the world origin
* @param valueY the image y position of the world origin
*/
public void setOriginsXY(int start, int end, double valueX, double valueY) {
if(isLocked()) {
return;
}
boolean changed = originX.fill(valueX, start, end);
changed = originY.fill(valueY, start, end) || changed;
if(changed) {
updateTransforms(start, end);
}
}
/**
* Gets the cosine of the angle of the world x-axis measured
* ccw from the image x-axis for the specified frame number.
*
* @param n the frame number
* @return the cosine of the angle
*/
public double getCosine(int n) {
return cosine.get(n);
}
/**
* Gets the sine of the angle of the world x-axis measured
* ccw from the image x-axis for the specified frame number.
*
* @param n the frame number
* @return the sine of the angle
*/
public double getSine(int n) {
return sine.get(n);
}
/**
* Sets the cosine and sine of the angle of the world x-axis measured
* ccw from the image x-axis for the specified frame number.
*
* @param n the frame number
* @param cos the cosine of the angle
* @param sin the sine of the angle
*/
public void setCosineSine(int n, double cos, double sin) {
if(isLocked()) {
return;
}
if(isFixedAngle()) {
setAllCosineSines(cos, sin);
return;
}
if(n>=length) {
setLength(n+1);
}
if (updateToKeyFrames) {
// end frame is just before first key frame following n
int end = length-1;
for (int next: keyFrames) {
if (next>n) {
end = next-1;
break;
}
}
setCosineSines(n, end, cos, sin);
keyFrames.add(n);
return;
}
double d = Math.sqrt(cos*cos+sin*sin);
boolean changed;
if(d==0) {
changed = cosine.set(n, 1);
changed = sine.set(n, 0)||changed;
} else {
changed = cosine.set(n, cos/d);
changed = sine.set(n, sin/d)||changed;
}
if(changed) {
try {
updateTransforms(n);
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
}
/**
* Sets the cosine and sine of the angle of the world x-axis measured
* ccw from the image x-axis for all frames.
*
* @param cos the cosine of the angle
* @param sin the sine of the angle
*/
public void setAllCosineSines(double cos, double sin) {
if(isLocked()) {
return;
}
double d = Math.sqrt(cos*cos+sin*sin);
boolean changed;
if(d==0) {
changed = cosine.fill(1);
changed = sine.fill(0)||changed;
} else {
changed = cosine.fill(cos/d);
changed = sine.fill(sin/d)||changed;
}
if(changed) {
updateAllTransforms();
}
}
/**
* Sets the cosine and sine for all frames in a specified range.
*
* @param start the start frame
* @param end the end frame
* @param cos the cosine of the angle
* @param sin the sine of the angle
*/
public void setCosineSines(int start, int end, double cos, double sin) {
if(isLocked()) {
return;
}
double d = Math.sqrt(cos*cos+sin*sin);
boolean changed;
if(d==0) {
changed = cosine.fill(1, start, end);
changed = sine.fill(0, start, end) || changed;
} else {
changed = cosine.fill(cos/d, start, end);
changed = sine.fill(sin/d, start, end) || changed;
}
if(changed) {
updateTransforms(start, end);
}
}
/**
* Gets the angle of the world x-axis measured ccw from the image
* x-axis for the specified frame number.
*
* @param n the frame number
* @return the angle in radians
*/
public double getAngle(int n) {
return Math.atan2(getSine(n), getCosine(n));
}
/**
* Sets the angle of the world x-axis measured ccw from the image
* x-axis for the specified frame number.
*
* @param n the frame number
* @param theta the angle in radians
*/
public void setAngle(int n, double theta) {
if(isLocked()) {
return;
}
setCosineSine(n, Math.cos(theta), Math.sin(theta));
}
/**
* Sets the angle of the world x-axis measured ccw from the image
* x-axis for all frames.
*
* @param theta the angle in radians
*/
public void setAllAngles(double theta) {
if(isLocked()) {
return;
}
setAllCosineSines(Math.cos(theta), Math.sin(theta));
}
/**
* Sets the length of this image coordinate system.
*
* @param count the total number of frames
*/
public void setLength(int count) {
if(isLocked()) {
return;
}
length = count;
toImage.setLength(length);
toWorld.setLength(length);
scaleX.setLength(length);
scaleY.setLength(length);
originX.setLength(length);
originY.setLength(length);
cosine.setLength(length);
sine.setLength(length);
}
/**
* Gets the length of this image coordinate system.
*
* @return the length
*/
public int getLength() {
return length;
}
/**
* Converts the specified image position to a world
* x position for the specified frame number.
*
* @param n the frame number
* @param imageX the image x position
* @param imageY the image y position
* @return the x coordinate in world space
*/
public double imageToWorldX(int n, double imageX, double imageY) {
if(n>=length) {
setLength(n+1);
}
point.setLocation(imageX, imageY);
toWorld.get(n).transform(point, point);
return point.getX();
}
/**
* Converts the specified image position to a world
* y position for the specified frame number.
*
* @param n the frame number
* @param imageX the image x position
* @param imageY the image y position
* @return the y coordinate in world space
*/
public double imageToWorldY(int n, double imageX, double imageY) {
if(n>=length) {
setLength(n+1);
}
point.setLocation(imageX, imageY);
toWorld.get(n).transform(point, point);
return point.getY();
}
/**
* Converts the specified world position to an image
* x position for the specified frame number.
*
* @param n the frame number
* @param worldX the world x position
* @param worldY the world y position
* @return the x coordinate in image space
*/
public double worldToImageX(int n, double worldX, double worldY) {
if(n>=length) {
setLength(n+1);
}
point.setLocation(worldX, worldY);
toImage.get(n).transform(point, point);
return point.getX();
}
/**
* Converts the specified world position to an image
* y position for the specified frame number.
*
* @param n the frame number
* @param worldX the world x position
* @param worldY the world y position
* @return the y coordinate in image space
*/
public double worldToImageY(int n, double worldX, double worldY) {
if(n>=length) {
setLength(n+1);
}
point.setLocation(worldX, worldY);
toImage.get(n).transform(point, point);
return point.getY();
}
/**
* Converts the specified image vector components to a world
* vector x component for the specified frame number.
*
* @param n the frame number
* @param imageX the image x components
* @param imageY the image y components
* @return the vector x component in world space
*/
public double imageToWorldXComponent(int n, double imageX, double imageY) {
if(n>=length) {
setLength(n+1);
}
point.setLocation(imageX, imageY);
toWorld.get(n).deltaTransform(point, point);
return point.getX();
}
/**
* Converts the specified image vector components to a world
* vector y component for the specified frame number.
*
* @param n the frame number
* @param imageX the image x components
* @param imageY the image y components
* @return the vector y component in world space
*/
public double imageToWorldYComponent(int n, double imageX, double imageY) {
if(n>=length) {
setLength(n+1);
}
point.setLocation(imageX, imageY);
toWorld.get(n).deltaTransform(point, point);
return point.getY();
}
/**
* Converts the specified world vector components to an image
* vector x component for the specified frame number.
*
* @param n the frame number
* @param worldX the world x position
* @param worldY the world y position
* @return the vector x component in image space
*/
public double worldToImageXComponent(int n, double worldX, double worldY) {
if(n>=length) {
setLength(n+1);
}
point.setLocation(worldX, worldY);
toImage.get(n).deltaTransform(point, point);
return point.getX();
}
/**
* Converts the specified world vector components to an image
* vector y component for the specified frame number.
*
* @param n the frame number
* @param worldX the world x position
* @param worldY the world y position
* @return the vector y component in image space
*/
public double worldToImageYComponent(int n, double worldX, double worldY) {
if(n>=length) {
setLength(n+1);
}
point.setLocation(worldX, worldY);
toImage.get(n).deltaTransform(point, point);
return point.getY();
}
/**
* Gets a copy of the affine transform used to convert from worldspace
* to imagespace for the specified frame number.
*
* @param n the frame number
* @return the worldspace to imagespace transform
*/
public AffineTransform getToImageTransform(int n) {
if(n>=length) {
setLength(n+1);
}
return(AffineTransform) toImage.get(n).clone();
}
/**
* Gets a copy of the affine transform used to convert from imagespace
* to worldspace for the specified frame number.
*
* @param n the frame number
* @return the imagespace to worldspace transform
*/
public AffineTransform getToWorldTransform(int n) {
if(n>=length) {
setLength(n+1);
}
return (AffineTransform)toWorld.get(n).clone();
}
/**
* Sets the adjusting flag.
*
* @param adjusting true if adjusting
*/
public void setAdjusting(boolean adjusting) {
if (isAdjusting==adjusting)
return;
isAdjusting = adjusting;
support.firePropertyChange("adjusting", null, adjusting); //$NON-NLS-1$
}
/**
* Gets the adjusting flag.
*
* @return true if adjusting
*/
public boolean isAdjusting() {
return isAdjusting;
}
/**
* Returns a String containing all the data for a single frame number.
* Note: this will be deprecated since its functionality has been replaced
* by the inner FrameData and FrameDataLoader classes.
*
* @param n the frame number
* @return the data string
*/
public String getDataString(int n) {
String originXStr = decimal.format(getOriginX(n));
String originYStr = decimal.format(getOriginY(n));
String thetaStr = decimal.format(getAngle(n));
String scaleXStr = sci.format(getScaleX(n));
String scaleYStr = sci.format(getScaleY(n));
return n+"\t"+originXStr //$NON-NLS-1$
+"\t"+originYStr //$NON-NLS-1$
+"\t"+thetaStr //$NON-NLS-1$
+"\t"+scaleXStr //$NON-NLS-1$
+"\t"+scaleYStr; //$NON-NLS-1$
}
/**
* Gets the set of key frames. This returns the actual Set, not a copy, so the entries
* can be converted when exporting a video clip/tracker panel combination.
*
* @return the Set
*/
public TreeSet<Integer> getKeyFrames() {
return keyFrames;
}
//_____________________________ static methods _______________________________
/**
* Returns an XML.ObjectLoader to save and load ImageCoordSystem data.
*
* @return the object loader
*/
public static XML.ObjectLoader getLoader() {
XML.setLoader(FrameData.class, new FrameDataLoader());
return new Loader();
}
/**
* A class to save and load ImageCoordSystem data.
*/
public static class Loader implements XML.ObjectLoader {
/**
* Saves ImageCoordSystem data to an XMLControl.
*
* @param control the control to save to
* @param obj the ImageCoordSystem object to save
*/
public void saveObject(XMLControl control, Object obj) {
ImageCoordSystem coords = (ImageCoordSystem) obj;
control.setValue("fixedorigin", coords.isFixedOrigin()); //$NON-NLS-1$
control.setValue("fixedangle", coords.isFixedAngle()); //$NON-NLS-1$
control.setValue("fixedscale", coords.isFixedScale()); //$NON-NLS-1$
control.setValue("locked", coords.isLocked()); //$NON-NLS-1$
// save frame data: frame 0 and key frames only
int count = coords.getLength();
if(coords.isFixedAngle()&&coords.isFixedOrigin()&&coords.isFixedScale()) {
count = 1;
}
FrameData[] data = new FrameData[count];
for(int n = 0; n<count; n++) {
if (n==0 || coords.keyFrames.contains(n)) {
data[n] = new FrameData(coords, n);
}
}
control.setValue("framedata", data); //$NON-NLS-1$
}
/**
* Creates a new ImageCoordSystem.
*
* @param control the control
* @return the new ImageCoordSystem
*/
public Object createObject(XMLControl control) {
return new ImageCoordSystem();
}
/**
* Loads an ImageCoordSystem with data from an XMLControl.
*
* @param control the control
* @param obj the ImageCoordSystem object
* @return the loaded object
*/
public Object loadObject(XMLControl control, Object obj) {
ImageCoordSystem coords = (ImageCoordSystem) obj;
coords.setLocked(false);
// load fixed origin, angle and scale
coords.setFixedOrigin(control.getBoolean("fixedorigin")); //$NON-NLS-1$
coords.setFixedAngle(control.getBoolean("fixedangle")); //$NON-NLS-1$
coords.setFixedScale(control.getBoolean("fixedscale")); //$NON-NLS-1$
// load frame data
FrameData[] data = (FrameData[]) control.getObject("framedata"); //$NON-NLS-1$
coords.setLength(Math.max(coords.getLength(), data.length));
for(int n = 0; n<data.length; n++) {
if (data[n]==null) continue;
coords.setOriginXY(n, data[n].xo, data[n].yo);
coords.setAngle(n, data[n].an*Math.PI/180); // convert from degrees to radians
coords.setScaleXY(n, data[n].xs, data[n].ys);
}
// load locked
coords.setLocked(control.getBoolean("locked")); //$NON-NLS-1$
return obj;
}
}
//___________________________ private methods ___________________________
/**
* Updates all transforms used to convert between drawing spaces.
*/
private void updateAllTransforms() {
try {
// don't fire property changes for individual updates
firePropChange = false;
for(int i = 0; i<length; i++) {
updateTransforms(i);
}
firePropChange = true;
// fire property change for overall updates
support.firePropertyChange("transform", null, null); //$NON-NLS-1$
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
/**
* Updates the transforms used to convert between drawing spaces
* for the specified frame number. The toImage transform first rotates
* about the world origin by an angle determined by cos and sin, then
* scales to image units, then translates to the image origin. The
* toWorld transform is the inverse.
*
* @param n the frame number
* @throws NoninvertibleTransformException
*/
private void updateTransforms(int n) throws NoninvertibleTransformException {
// update toImage transform
AffineTransform at = toImage.get(n);
double tx = originX.get(n);
double ty = originY.get(n);
double sx = scaleX.get(n);
double sy = scaleY.get(n);
double cos = cosine.get(n);
double sin = sine.get(n);
at.setTransform(sx*cos, -sy*sin, -sx*sin, -sy*cos, tx, ty);
// toWorld is inverse of toImage
toWorld.get(n).setTransform(at.createInverse());
// fire property change
if(firePropChange) {
support.firePropertyChange("transform", null, new Integer(n)); //$NON-NLS-1$
}
}
/**
* Updates a range of transforms.
*
* @param start the start frame number
* @param end the end frame number
*/
private void updateTransforms(int start, int end) {
try {
// don't fire property changes for individual updates
firePropChange = false;
for(int i = start; i<length; i++) {
updateTransforms(i);
}
firePropChange = true;
// fire property change for overall updates
support.firePropertyChange("transform", null, null); //$NON-NLS-1$
} catch(NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
/**
* Inner class containing the coords data for a single frame number.
*/
public static class FrameData {
double xo, yo, an, xs, ys;
FrameData() {
/** empty block */
}
FrameData(ImageCoordSystem coords, int n) {
xo = coords.getOriginX(n);
yo = coords.getOriginY(n);
an = coords.getAngle(n)*180/Math.PI; // angle in degrees
xs = coords.getScaleX(n);
ys = coords.getScaleY(n);
}
}
/**
* A class to save and load a FrameData.
*/
private static class FrameDataLoader implements XML.ObjectLoader {
public void saveObject(XMLControl control, Object obj) {
FrameData data = (FrameData) obj;
control.setValue("xorigin", data.xo); //$NON-NLS-1$
control.setValue("yorigin", data.yo); //$NON-NLS-1$
control.setValue("angle", data.an); //$NON-NLS-1$ // angle in degrees
control.setValue("xscale", data.xs); //$NON-NLS-1$
control.setValue("yscale", data.ys); //$NON-NLS-1$
}
public Object createObject(XMLControl control) {
return new FrameData();
}
public Object loadObject(XMLControl control, Object obj) {
FrameData data = (FrameData) obj;
data.xo = control.getDouble("xorigin"); //$NON-NLS-1$
data.yo = control.getDouble("yorigin"); //$NON-NLS-1$
data.an = control.getDouble("angle"); //$NON-NLS-1$ // angle in degrees
data.xs = control.getDouble("xscale"); //$NON-NLS-1$
data.ys = control.getDouble("yscale"); //$NON-NLS-1$
return obj;
}
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/