/* 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. */ /* * DrawPoint.java * * Created on 11. Oktober 2001, 23:59 */ package org.geogebra.common.euclidian.draw; import org.geogebra.common.awt.GAffineTransform; import org.geogebra.common.awt.GAlphaComposite; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GComposite; import org.geogebra.common.awt.GGeneralPath; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.GPoint2D; import org.geogebra.common.awt.GRectangle; import org.geogebra.common.awt.GShape; import org.geogebra.common.awt.MyImage; import org.geogebra.common.euclidian.BoundingBox; import org.geogebra.common.euclidian.Drawable; import org.geogebra.common.euclidian.EuclidianView; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoImage; import org.geogebra.common.kernel.geos.GeoPoint; /** * * @author Markus */ public final class DrawImage extends Drawable { private GeoImage geoImage; private boolean isVisible; private MyImage image; private boolean absoluteLocation; private GAlphaComposite alphaComp; private double alpha = -1; private boolean isInBackground = false; private GAffineTransform at, atInverse, tempAT; private boolean needsInterpolationRenderingHint; private int screenX, screenY; private GRectangle boundingBox; private GGeneralPath highlighting; /** * Creates new drawable image * * @param view * view * @param geoImage * image */ public DrawImage(EuclidianView view, GeoImage geoImage) { this.view = view; this.geoImage = geoImage; geo = geoImage; // temp at = AwtFactory.getPrototype().newAffineTransform(); tempAT = AwtFactory.getPrototype().newAffineTransform(); boundingBox = AwtFactory.getPrototype().newRectangle(); selStroke = AwtFactory.getPrototype().newMyBasicStroke(1.5f); update(); } @Override final public void update() { isVisible = geo.isEuclidianVisible(); if (!isVisible) { return; } if (geo.getAlphaValue() != alpha) { alpha = geo.getAlphaValue(); alphaComp = AwtFactory.getPrototype() .newAlphaComposite(alpha); } image = geoImage.getFillImage(); int width = image.getWidth(); int height = image.getHeight(); absoluteLocation = geoImage.isAbsoluteScreenLocActive(); // ABSOLUTE SCREEN POSITION if (absoluteLocation) { screenX = geoImage.getAbsoluteScreenLocX(); screenY = geoImage.getAbsoluteScreenLocY() - height; labelRectangle.setBounds(screenX, screenY, width, height); } // RELATIVE SCREEN POSITION else { GeoPoint A = geoImage.getCorner(0); GeoPoint B = geoImage.getCorner(1); GeoPoint D = geoImage.getCorner(2); double ax = 0; double ay = 0; if (A != null) { if (!A.isDefined() || A.isInfinite()) { isVisible = false; return; } ax = A.inhomX; ay = A.inhomY; } // set transform according to corners at.setTransform(view.getCoordTransform()); // last transform: real // world // -> screen at.translate(ax, ay); // translate to first corner A if (B == null) { // we only have corner A if (D == null) { // use original pixel width and heigt of image at.scale(view.getInvXscale(), -view.getInvXscale()); } // we have corners A and D else { if (!D.isDefined() || D.isInfinite()) { isVisible = false; return; } // rotate to coord system (-ADn, AD) double ADx = D.inhomX - ax; double ADy = D.inhomY - ay; tempAT.setTransform(ADy, -ADx, ADx, ADy, 0, 0); at.concatenate(tempAT); // scale height of image to 1 double yscale = 1.0 / height; at.scale(yscale, -yscale); } } else { if (!B.isDefined() || B.isInfinite()) { isVisible = false; return; } // we have corners A and B if (D == null) { // rotate to coord system (AB, ABn) double ABx = B.inhomX - ax; double ABy = B.inhomY - ay; tempAT.setTransform(ABx, ABy, -ABy, ABx, 0, 0); at.concatenate(tempAT); // scale width of image to 1 double xscale = 1.0 / width; at.scale(xscale, -xscale); } else { // we have corners A, B and D if (!D.isDefined() || D.isInfinite()) { isVisible = false; return; } // shear to coord system (AB, AD) double ABx = B.inhomX - ax; double ABy = B.inhomY - ay; double ADx = D.inhomX - ax; double ADy = D.inhomY - ay; tempAT.setTransform(ABx, ABy, ADx, ADy, 0, 0); at.concatenate(tempAT); // scale width and height of image to 1 at.scale(1.0 / width, -1.0 / height); } } // move image up so that A becomes lower left corner at.translate(0, -height); labelRectangle.setBounds(0, 0, width, height); // calculate bounding box for isInside boundingBox.setBounds(0, 0, width, height); GShape shape = at.createTransformedShape(boundingBox); boundingBox = shape.getBounds(); try { // for hit testing atInverse = at.createInverse(); } catch (Exception e) { isVisible = false; return; } // improve rendering for sheared and scaled images (translations // don't need this) // turns false if the image doen't want interpolation needsInterpolationRenderingHint = (geoImage.isInterpolate()) && (!isTranslation(at) || view.getPixelRatio() != 1); } if (isInBackground != geoImage.isInBackground()) { isInBackground = !isInBackground; if (isInBackground) { view.addBackgroundImage(this); } else { view.removeBackgroundImage(this); view.updateBackgroundImage(); } } if (!view.isBackgroundUpdating() && isInBackground) { view.updateBackgroundImage(); } } private static boolean isTranslation(GAffineTransform at2) { return Kernel.isEqual(at2.getScaleX(), 1.0, Kernel.MAX_PRECISION) && Kernel.isEqual(at2.getScaleY(), 1.0, Kernel.MAX_PRECISION) && Kernel.isEqual(at2.getShearX(), 0.0, Kernel.MAX_PRECISION) && Kernel.isEqual(at2.getShearY(), 0.0, Kernel.MAX_PRECISION); } /** * If background flag changed, do immediate update. Otherwise mark for * update after next repaint. * * @return whether it was in background for the whole time */ public boolean checkInBackground() { if (isInBackground != geoImage.isInBackground()) { update(); } else { setNeedsUpdate(true); } return isInBackground && geoImage.isInBackground(); } @Override final public void draw(GGraphics2D g3) { if (isVisible) { GComposite oldComp = g3.getComposite(); if (alpha >= 0f && alpha < 1f) { if (alphaComp == null) { alphaComp = AwtFactory.getPrototype() .newAlphaComposite(alpha); } g3.setComposite(alphaComp); } if (absoluteLocation) { g3.drawImage(image, screenX, screenY); if (!isInBackground && geo.doHighlighting()) { // draw rectangle around image g3.setStroke(selStroke); g3.setPaint(GColor.LIGHT_GRAY); g3.draw(labelRectangle); } } else { g3.saveTransform(); g3.transform(at); // improve rendering quality for transformed images Object oldInterpolationHint = g3 .setInterpolationHint(needsInterpolationRenderingHint); g3.drawImage(image, 0, 0); g3.restoreTransform(); if (!isInBackground && geo.doHighlighting()) { // draw rectangle around image g3.setStroke(selStroke); g3.setPaint(GColor.LIGHT_GRAY); // changed to code below so that the line thicknesses aren't // transformed // g2.draw(labelRectangle); // draw parallelogram around edge GPoint2D corner1 = AwtFactory.getPrototype().newPoint2D( labelRectangle.getMinX(), labelRectangle.getMinY()); GPoint2D corner2 = AwtFactory.getPrototype().newPoint2D( labelRectangle.getMinX(), labelRectangle.getMaxY()); GPoint2D corner3 = AwtFactory.getPrototype().newPoint2D( labelRectangle.getMaxX(), labelRectangle.getMaxY()); GPoint2D corner4 = AwtFactory.getPrototype().newPoint2D( labelRectangle.getMaxX(), labelRectangle.getMinY()); at.transform(corner1, corner1); at.transform(corner2, corner2); at.transform(corner3, corner3); at.transform(corner4, corner4); if (highlighting == null) { highlighting = AwtFactory.getPrototype() .newGeneralPath(); } else { highlighting.reset(); } highlighting.moveTo(corner1.getX(), corner1.getY()); highlighting.lineTo(corner2.getX(), corner2.getY()); highlighting.lineTo(corner3.getX(), corner3.getY()); highlighting.lineTo(corner4.getX(), corner4.getY()); highlighting.lineTo(corner1.getX(), corner1.getY()); g3.draw(highlighting); } // reset previous values g3.resetInterpolationHint(oldInterpolationHint); } g3.setComposite(oldComp); } } /** * Returns whether this is background image * * @return true for background images */ boolean isInBackground() { return geoImage.isInBackground(); } /** * was this object clicked at? (mouse pointer location (x,y) in screen * coords) */ @Override final public boolean hit(int x, int y, int hitThreshold) { if (!isVisible || geoImage.isInBackground()) { return false; } hitCoords[0] = x; hitCoords[1] = y; // convert screen to image coordinate system if (!geoImage.isAbsoluteScreenLocActive()) { atInverse.transform(hitCoords, 0, hitCoords, 0, 1); } return labelRectangle.contains(hitCoords[0], hitCoords[1]); } @Override public boolean intersectsRectangle(GRectangle rect) { if (!isVisible || geoImage.isInBackground()) { return false; } return rect.intersects(boundingBox); } private double[] hitCoords = new double[2]; @Override final public boolean isInside(GRectangle rect) { if (!isVisible || geoImage.isInBackground()) { return false; } return rect.contains(boundingBox); } /** * Returns the bounding box of this DrawPoint in screen coordinates. */ @Override final public GRectangle getBounds() { if (!geo.isDefined() || !geo.isEuclidianVisible()) { return null; } return boundingBox; } /** * Returns false */ @Override public boolean hitLabel(int x, int y) { return false; } @Override final public GeoElement getGeoElement() { return geo; } @Override final public void setGeoElement(GeoElement geo) { this.geo = geo; } @Override public BoundingBox getBoundingBox() { // TODO Auto-generated method stub return null; } @Override public void updateBoundingBox() { // TODO Auto-generated method stub } }