/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program 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. */ package org.geogebra.common.kernel.geos; import java.util.ArrayList; import org.geogebra.common.awt.GBufferedImage; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.euclidian.EuclidianViewInterfaceCommon; import org.geogebra.common.euclidian.EuclidianViewInterfaceSlim; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.CircularDefinitionException; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Locateable; import org.geogebra.common.kernel.MatrixTransformable; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.ValueType; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoLineND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.main.App; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.util.StringUtil; /** * Image with given filename and corners */ public class GeoImage extends GeoElement implements Locateable, AbsoluteScreenLocateable, PointRotateable, Mirrorable, Translateable, Dilateable, MatrixTransformable, Transformable { // private String imageFileName = ""; // image file private GeoPoint[] corners; // corners of the image // private BufferedImage image; /** width in pixels */ protected int pixelWidth; /** height in pixels */ protected int pixelHeight; private boolean inBackground, defined; private boolean hasAbsoluteLocation; private boolean interpolate = true; // for absolute screen location private int screenX, screenY; private boolean hasAbsoluteScreenLocation = false; // corner points for transformations private GeoPoint[] tempPoints; /** * Creates new image * * @param c * construction */ public GeoImage(Construction c) { super(c); // moved from GeoElement's constructor // must be called from the subclass, see // http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/ setConstructionDefaults(); // init visual settings setAlphaValue(1f); // setAlgebraVisible(false); // don't show in algebra view setAuxiliaryObject(true); // three corners of the image: first, second and fourth corners = new GeoPoint[3]; kernel.getApplication().images.add(this); defined = true; } /** * Creates new labeled image * * @param c * construction * @param label * label * @param fileName * path to the image */ public GeoImage(Construction c, String label, String fileName) { this(c); setImageFileName(fileName); setLabel(label); } /** * Copy constructor * * @param img * source image */ public GeoImage(GeoImage img) { this(img.cons); set(img); } @Override public GeoElement copy() { return new GeoImage(this); } @Override public int getRelatedModeID() { return EuclidianConstants.MODE_IMAGE; /* * switch (this.image.getType()){ case 5: return * EuclidianConstants.MODE_IMAGE; case 6: return * EuclidianConstants.MODE_PEN; default: return -1; } */ } private void initTempPoints() { if (tempPoints == null) { // temp corner points for transformations and absolute location tempPoints = new GeoPoint[3]; for (int i = 0; i < tempPoints.length; i++) { tempPoints[i] = new GeoPoint(cons); } } if (corners[0] == null) { corners[0] = tempPoints[0]; } } @Override public void set(GeoElementND geo) { GeoImage img = (GeoImage) geo; setImageFileName(img.getGraphicsAdapter().getImageFileName()); // macro output: don't set corners if (cons != geo.getConstruction() && isAlgoMacroOutput()) { return; } // location settings hasAbsoluteScreenLocation = img.hasAbsoluteScreenLocation; if (hasAbsoluteScreenLocation) { screenX = img.screenX; screenY = img.screenY; } else { hasAbsoluteLocation = true; for (int i = 0; i < corners.length; i++) { if (img.corners[i] == null) { corners[i] = null; } else { initTempPoints(); tempPoints[i].setCoords(img.corners[i]); corners[i] = tempPoints[i]; } } } // interpolation settings interpolate = img.interpolate; defined = img.defined; } @Override public void setVisualStyle(GeoElement geo) { super.setVisualStyle(geo); if (geo.isGeoImage()) { inBackground = ((GeoImage) geo).inBackground; } } /** * Reloads images from internal image cache * * @param kernel * kernel for which we want to do the replacement */ public static void updateInstances(App kernel) { for (int i = kernel.images.size() - 1; i >= 0; i--) { GeoImage geo = kernel.images.get(i); geo.setImageFileName(geo.getGraphicsAdapter().getImageFileName()); geo.updateCascade(); } } @Override public boolean showToolTipText() { return !inBackground && super.showToolTipText(); } /** * True for background images * * @return true for background images */ final public boolean isInBackground() { return inBackground; } /** * Switch to background image (or vice versa) * * @param flag * true to make it background image */ public void setInBackground(boolean flag) { inBackground = flag; } /** * Tries to load the image using the given fileName. * * @param fileName * filename * @param width * width * @param height * height */ public void setImageFileName(String fileName, int width, int height) { if (fileName == null) { return; } if (fileName.equals(this.getGraphicsAdapter().getImageFileName())) { return; } this.getGraphicsAdapter().setImageFileNameOnly(fileName); this.getGraphicsAdapter().setImageOnly(kernel.getApplication() .getExternalImageAdapter(fileName, width, height)); if (this.getGraphicsAdapter().getImageOnly() != null) { pixelWidth = this.getGraphicsAdapter().getImageOnly().getWidth(); pixelHeight = this.getGraphicsAdapter().getImageOnly().getHeight(); } else { pixelWidth = 0; pixelHeight = 0; } } @Override public void setImageFileName(String fileName) { setImageFileName(fileName, 0, 0); } // final public BufferedImage getFillImage() { // return image; // } @Override public void setStartPoint(GeoPointND p) throws CircularDefinitionException { setCorner(p, 0); } @Override public void removeStartPoint(GeoPointND p) { for (int i = 0; i < corners.length; i++) { if (corners[i] == p) { setCorner(null, i); } } } @Override public void setStartPoint(GeoPointND p, int number) throws CircularDefinitionException { setCorner(p, number); } /** * Sets the startpoint without performing any checks. This is needed for * macros. */ @Override public void initStartPoint(GeoPointND p, int number) { corners[number] = (GeoPoint) p; } /** * Sets a corner of this image. * * @param p * corner point * @param number * 0, 1 or 2 (first, second and fourth corner) */ public void setCorner(GeoPointND p, int number) { // macro output uses initStartPoint() only if (isAlgoMacroOutput()) { return; } if (corners[0] == null && number > 0) { return; } // check for circular definition if (isParentOf(p)) { // throw new CircularDefinitionException(); return; } // set new location if (!(p instanceof GeoPoint)) { // remove old dependencies if (corners[number] != null) { corners[number].getLocateableList().unregisterLocateable(this); } // copy old first corner as absolute position if (number == 0 && corners[0] != null) { GeoPoint temp = new GeoPoint(cons); temp.setCoords(corners[0]); corners[0] = temp; } else { corners[number] = null; } } else { // check if this point is already available for (int i = 0; i < corners.length; i++) { if (p == corners[i]) { return; } } // remove old dependencies if (corners[number] != null) { corners[number].getLocateableList().unregisterLocateable(this); } corners[number] = (GeoPoint) p; // add new dependencies corners[number].getLocateableList().registerLocateable(this); } // absolute screen position should be deactivated setAbsoluteScreenLocActive(false); updateHasAbsoluteLocation(); } /** * Sets hasAbsoluteLocation flag to true iff all corners are absolute start * points (i.e. independent and unlabeled). */ private void updateHasAbsoluteLocation() { hasAbsoluteLocation = true; for (int i = 0; i < corners.length; i++) { if (!(corners[i] == null || corners[i].isAbsoluteStartPoint())) { hasAbsoluteLocation = false; return; } } } @Override public void doRemove() { kernel.getApplication().images.remove(this); // remove background image if (inBackground) { inBackground = false; notifyUpdate(); } super.doRemove(); for (int i = 0; i < corners.length; i++) { // tell corner if (corners[i] != null) { corners[i].getLocateableList().unregisterLocateable(this); } } } @Override public GeoPoint getStartPoint() { return corners[0]; } @Override public GeoPoint[] getStartPoints() { return corners; } /** * Returns n-th corner point * * @param number * 1 for boottom left, others clockwise * @return corner point */ final public GeoPoint getCorner(int number) { return corners[number]; } @Override final public boolean hasAbsoluteLocation() { return hasAbsoluteLocation; } /** * * @return true if the image wants to be interpolated */ final public boolean isInterpolate() { return interpolate; } /** * sets if the image want to be interpolated * * @param flag * true to turn interpolation on */ final public void setInterpolate(boolean flag) { interpolate = flag; } @Override public void setWaitForStartPoint() { // this can be ignored for an image // as the position of its startpoint // is irrelevant for the rest of the construction } @Override final public boolean isDefined() { if (!defined) { return false; } for (int i = 0; i < corners.length; i++) { if (corners[i] != null && !corners[i].isDefined()) { return false; } } return true; } /** * makes image invisible needed for Sequence's cached images */ @Override public void setUndefined() { defined = false; } @Override public String toValueString(StringTemplate tpl) { return toString(tpl); } @Override public String toString(StringTemplate tpl) { return label == null ? getLoc().getPlain("Image") : label; } @Override public boolean showInAlgebraView() { return true; } @Override protected boolean showInEuclidianView() { return getGraphicsAdapter().getImageOnly() != null && isDefined(); } @Override public GeoClass getGeoClassType() { return GeoClass.IMAGE; } /** * Returns whether this image can be moved in Euclidian View. */ @Override final public boolean isMoveable() { return (hasAbsoluteScreenLocation || hasAbsoluteLocation) && isPointerChangeable(); } /** * Returns whether this image can be rotated in Euclidian View. */ @Override final public boolean isRotateMoveable() { return !hasAbsoluteScreenLocation && hasAbsoluteLocation && isPointerChangeable(); } /** * Returns whether this image can be fixed. * * public boolean isFixable() { return (hasAbsoluteScreenLocation || * hasAbsoluteLocation) && isIndependent(); } */ @Override public boolean isFillable() { return true; } @Override public boolean hasFillType() { return false; } @Override public boolean isNumberValue() { return false; } @Override public boolean isGeoImage() { return true; } /** * returns all class-specific xml tags for getXML */ @Override protected void getXMLtags(StringBuilder sb) { // name of image file sb.append("\t<file name=\""); // Michael Borcherds 2007-12-10 this line restored (not needed now MD5 // code put in the correct place) sb.append(StringUtil .encodeXML(this.getGraphicsAdapter().getImageFileName())); sb.append("\"/>\n"); // name of image file sb.append("\t<inBackground val=\""); sb.append(inBackground); sb.append("\"/>\n"); // image has to be interpolated if (!isInterpolate()) { sb.append("\t<interpolate val=\"false\"/>\n"); } // locateion of image if (hasAbsoluteScreenLocation) { sb.append(getXMLabsScreenLoc()); } else { // store location of corners for (int i = 0; i < corners.length; i++) { if (corners[i] != null) { sb.append(getCornerPointXML(i)); } } } getAuxiliaryXML(sb); // sb.append(getXMLvisualTags()); // sb.append(getBreakpointXML()); super.getXMLtags(sb); } private String getXMLabsScreenLoc() { StringBuilder sb = new StringBuilder(); sb.append("\t<absoluteScreenLocation x=\""); sb.append(screenX); sb.append("\" y=\""); sb.append(screenY); sb.append("\"/>"); return sb.toString(); } private String getCornerPointXML(int number) { StringBuilder sb = new StringBuilder(); sb.append("\t<startPoint number=\""); sb.append(number); sb.append("\""); if (corners[number].isAbsoluteStartPoint()) { sb.append(" x=\"" + corners[number].x + "\""); sb.append(" y=\"" + corners[number].y + "\""); sb.append(" z=\"" + corners[number].z + "\""); } else { sb.append(" exp=\""); StringUtil.encodeXML(sb, corners[number].getLabel(StringTemplate.xmlTemplate)); sb.append("\""); } sb.append("/>\n"); return sb.toString(); } @Override public void setAbsoluteScreenLoc(int x, int y) { screenX = x; screenY = y; if (!hasScreenLocation() && (x != 0 && y != 0)) { setScreenLocation(x, y); } } @Override public int getAbsoluteScreenLocX() { return screenX; } @Override public int getAbsoluteScreenLocY() { return screenY; } @Override public void setRealWorldLoc(double x, double y) { GeoPoint locPoint = getStartPoint(); if (locPoint == null) { locPoint = new GeoPoint(cons); setCorner(locPoint, 0); } locPoint.setCoords(x, y, 1.0); } @Override public double getRealWorldLocX() { if (corners[0] == null) { return 0; } return corners[0].inhomX; } @Override public double getRealWorldLocY() { if (corners[0] == null) { return 0; } return corners[0].inhomY; } @Override public void setAbsoluteScreenLocActive(boolean flag) { hasAbsoluteScreenLocation = flag; if (flag) { // remove startpoints for (int i = 0; i < 3; i++) { if (corners[i] != null) { corners[i].getLocateableList().unregisterLocateable(this); } } corners[1] = null; corners[2] = null; } } @Override public boolean isAbsoluteScreenLocActive() { return hasAbsoluteScreenLocation; } @Override public boolean isAbsoluteScreenLocateable() { return isIndependent(); } /* * ************************************** Transformations * ************************************* */ /** * Calculates the n-th corner point of this image in real world coordinates. * Note: if this image has an absolute screen location, result is set to * undefined. * * @param result * here the result is stored. * @param n * number of the corner point (1, 2, 3 or 4) */ public void calculateCornerPoint(GeoPoint result, int n) { if (hasAbsoluteScreenLocation) { result.setUndefined(); return; } if (corners[0] == null) { initTempPoints(); } switch (n) { case 1: // get A result.setCoords(corners[0]); break; case 2: // get B getInternalCornerPointCoords(tempCoords, 1); result.setCoords(tempCoords[0], tempCoords[1], 1.0); break; case 3: // get C double[] b = new double[2]; double[] d = new double[2]; getInternalCornerPointCoords(b, 1); getInternalCornerPointCoords(d, 2); result.setCoords(d[0] + b[0] - corners[0].inhomX, d[1] + b[1] - corners[0].inhomY, 1.0); break; case 4: // get D getInternalCornerPointCoords(tempCoords, 2); result.setCoords(tempCoords[0], tempCoords[1], 1.0); break; default: result.setUndefined(); } } // coords is the 2d result array for (x, y); n is 0, 1, or 2 private double[] tempCoords = new double[2]; private void getInternalCornerPointCoords(double[] coords, int n) { GeoPoint A = corners[0]; GeoPoint B = corners[1]; GeoPoint D = corners[2]; double xscale = kernel.getXscale(); double yscale = kernel.getYscale(); double width = pixelWidth; double height = pixelHeight; // different scales: change height if (xscale != yscale) { height = height * yscale / xscale; } switch (n) { case 0: // get A coords[0] = A.inhomX; coords[1] = A.inhomY; break; case 1: // get B if (B != null) { coords[0] = B.inhomX; coords[1] = B.inhomY; } else { // B is not defined if (D == null) { // B and D are not defined coords[0] = A.inhomX + width / xscale; coords[1] = A.inhomY; } else { // D is defined, B isn't double nx = D.inhomY - A.inhomY; double ny = A.inhomX - D.inhomX; double factor = width / height; coords[0] = A.inhomX + factor * nx; coords[1] = A.inhomY + factor * ny; } } break; case 2: // D if (D != null) { coords[0] = D.inhomX; coords[1] = D.inhomY; } else { // D is not defined if (B == null) { // B and D are not defined coords[0] = A.inhomX; coords[1] = A.inhomY + height / yscale; } else { // B is defined, D isn't double nx = A.inhomY - B.inhomY; double ny = B.inhomX - A.inhomX; double factor = height / width; coords[0] = A.inhomX + factor * nx; coords[1] = A.inhomY + factor * ny; } } break; default: coords[0] = Double.NaN; coords[1] = Double.NaN; } } private boolean initTransformPoints() { if (hasAbsoluteScreenLocation || !hasAbsoluteLocation) { return false; } initTempPoints(); calculateCornerPoint(tempPoints[0], 1); calculateCornerPoint(tempPoints[1], 2); calculateCornerPoint(tempPoints[2], 4); return true; } /** * rotate this image by angle phi around (0,0) */ @Override final public void rotate(NumberValue phiValue) { if (!initTransformPoints()) { return; } // calculate the new corner points for (int i = 0; i < corners.length; i++) { tempPoints[i].rotate(phiValue); corners[i] = tempPoints[i]; } } /** * rotate this image by angle phi around Q */ @Override final public void rotate(NumberValue phiValue, GeoPointND Q) { if (!initTransformPoints()) { return; } // calculate the new corner points for (int i = 0; i < corners.length; i++) { tempPoints[i].rotate(phiValue, Q); corners[i] = tempPoints[i]; } } @Override public void mirror(Coords Q) { if (!initTransformPoints()) { return; } // calculate the new corner points for (int i = 0; i < corners.length; i++) { tempPoints[i].mirror(Q); corners[i] = tempPoints[i]; } } @Override public void matrixTransform(double a, double b, double c, double d) { if (!initTransformPoints()) { return; } // calculate the new corner points for (int i = 0; i < corners.length; i++) { GeoVec2D vec = tempPoints[i].getVector(); vec.matrixTransform(a, b, c, d); if (corners[i] == null) { corners[i] = new GeoPoint(cons); } corners[i].setCoords(vec); } } @Override public boolean isMatrixTransformable() { return true; } @Override public void mirror(GeoLineND g) { if (!initTransformPoints()) { return; } // calculate the new corner points for (int i = 0; i < corners.length; i++) { tempPoints[i].mirror(g); corners[i] = tempPoints[i]; } } @Override public void translate(Coords v) { if (!initTransformPoints()) { return; } // calculate the new corner points for (int i = 0; i < corners.length; i++) { if (corners[i] != null) { tempPoints[i].translate(v); corners[i] = tempPoints[i]; } } } @Override final public boolean isTranslateable() { return true; } @Override public void dilate(NumberValue r, Coords S) { if (!initTransformPoints()) { return; } // calculate the new corner points for (int i = 0; i < corners.length; i++) { tempPoints[i].dilate(r, S); corners[i] = tempPoints[i]; } } // Michael Borcherds 2008-04-30 @Override final public boolean isEqual(GeoElementND geo) { // return false if it's a different type if (!geo.isGeoImage()) { return false; } // check sizes if (((GeoImage) geo).pixelWidth != this.pixelWidth) { return false; } if (((GeoImage) geo).pixelHeight != this.pixelHeight) { return false; } String imageFileName = this.getGraphicsAdapter().getImageFileName(); String md5A = imageFileName.substring(0, kernel.getApplication().getMD5folderLength(imageFileName)); String imageFileName2 = ((GeoImage) geo).getGraphicsAdapter() .getImageFileName(); String md5B = imageFileName2.substring(0, kernel.getApplication().getMD5folderLength(imageFileName)); // MD5 checksums equal, so images almost certainly identical if (md5A.equals(md5B)) { return true; } return false; } @Override public boolean isAlwaysFixed() { return false; } @Override public boolean hasMoveableInputPoints(EuclidianViewInterfaceSlim view) { if (hasAbsoluteLocation()) { return false; } for (int i = 0; i < corners.length; i++) { if (corners[i] != null && !corners[i].isMoveable(view)) { return false; } } return true; } private ArrayList<GeoPointND> al = null; /** * Returns all free parent points of this GeoElement. */ @Override public ArrayList<GeoPointND> getFreeInputPoints( EuclidianViewInterfaceSlim view) { if (hasAbsoluteLocation()) { return null; } if (al == null) { al = new ArrayList<GeoPointND>(); } else { al.clear(); } for (int i = 0; i < corners.length; i++) { if (corners[i] != null) { al.add(corners[i]); } } return al; } @Override final public boolean isAuxiliaryObjectByDefault() { return true; } @Override final public boolean isAlgebraViewEditable() { return !isIndependent(); } @Override public void matrixTransform(double a00, double a01, double a02, double a10, double a11, double a12, double a20, double a21, double a22) { // http://forum.geogebra.org/viewtopic.php?f=8&t=38230 initTempPoints(); for (int i = 0; i < corners.length; i++) { GeoVec2D vec = tempPoints[i].getVector(); vec.matrixTransform(a00, a01, a02, a10, a11, a12, a20, a21, a22); if (corners[i] == null) { corners[i] = new GeoPoint(cons); } corners[i].setCoords(vec); } } /** * Clears the image */ public void clearFillImage() { this.getGraphicsAdapter() .setImageOnly(AwtFactory.getPrototype().newMyImage(pixelWidth, pixelHeight, GBufferedImage.TYPE_INT_ARGB)); this.updateRepaint(); } @Override public boolean isPinnable() { return true; } @Override public void updateLocation() { updateGeo(false); kernel.notifyUpdateLocation(this); } @Override final public HitType getLastHitType() { return HitType.ON_FILLING; } @Override public ValueType getValueType() { return ValueType.VOID; } public int getTotalWidth(EuclidianViewInterfaceCommon ev) { return pixelWidth; } public int getTotalHeight(EuclidianViewInterfaceCommon ev) { return pixelHeight; } @Override public boolean isAlgebraDuplicateable() { return false; } }