/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package tufts.vue; import tufts.Util; import java.awt.Image; import java.awt.Point; import java.awt.Color; import java.awt.Font; import java.awt.Shape; import java.awt.BasicStroke; import java.awt.geom.*; import java.awt.AlphaComposite; /** * Handle the presentation of an image resource, allowing resize. * Also provides special support for appear as a "node icon" -- a fixed * size image inside a node to represent it's resource. * * @version $Revision: 1.82 $ / $Date: 2007/11/19 06:20:27 $ / $Author: sfraize $ */ public class LWImage extends LWComponent implements ImageRef.Listener { //private static final class X__________ {} //private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(X__________.class); // debug marker private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(LWImage.class); public static final boolean SLIDE_LABELS = false; private static final int DefaultIconMaxSide = 128; private static final int DefaultWidth = 128; private static final int DefaultHeight = 128; private final static int MinWidth = 16; private final static int MinHeight = 16; private volatile ImageRef mImageRef = ImageRef.EMPTY; // point on AWT thread where the undo queue was before an async load operation was triggered private volatile Object mAWTUndoMark; private void initImage() { disableProperty(LWKey.FontSize); // prevent 0 font size warnings (font not used on images) takeFillColor(null); takeSize(DefaultWidth, DefaultHeight); setFlag(Flag.UNSIZED); } public LWImage() { initImage(); } public LWImage(Resource r) { initImage(); if (r == null) throw new IllegalArgumentException("resource is not image content: " + r); if (!r.isImage()) Log.warn("making LWImage: may not be image content: " + r); setResource(r); } static LWImage create(Resource r) { return new LWImage(r); // auto set title based on MapDropTarget.makeNodeTitle? } public static LWImage createNodeIcon(Resource r) { if (DEBUG.IMAGE) Log.debug("createNodeIcon: " + r); final LWImage icon = new LWImage(); icon.setNodeIcon(true); icon.setResource(r); return icon; } static LWImage createNodeIcon(LWImage i, Resource r) { if (DEBUG.IMAGE) Log.debug("createNodeIcon: " + i + "; " + r); final LWImage icon = (LWImage) i.duplicate(); // copy styling, title & notes // note: above will copy the resource over, but will skip initRef as mXMLRestoreUnderway is true during duplicates icon.setNodeIcon(true); icon.setFlag(Flag.UNSIZED); icon.setResource(r); // as we've created a duplicate LWImage, which will have the same // size as the original, the newly loaded image Resource will // be automatically aspect-fit to the existing image size. return icon; } /** @return true -- an image is always it's own content */ @Override public boolean hasContent() { return true; } @Override public LWImage duplicate(CopyContext cc) { // note: do not duplicate the isNodeIcon bit -- leave unset // note: when the resource is copied over, the new LWImage will // init the new ImageRef. // note: if the ImageRef has a bad status (e.g., ERROR), that's // not being transferred to the new ImageRef. The new ImageRef // should just try to access the image again and generate it's // own status. final LWImage newImage = new LWImage(); if (!hasFlag(Flag.UNSIZED)) newImage.clearFlag(Flag.UNSIZED); return super.duplicateTo(newImage, cc); } /** @return false: images are never auto-sized */ @Override public boolean isAutoSized() { return false; } /** @return false: images are never transparent */ @Override public boolean isTransparent() { return false; } /** @return false: images are never translucent */ @Override public boolean isTranslucent() { return false; } /** @return 0 */ @Override public int getFocalMargin() { return 0; } /** @return true if this LWImage is being used as an icon for an LWNode */ public boolean isNodeIcon() { return hasFlag(Flag.ICON); } void setNodeIcon(boolean t) { if (DEBUG.IMAGE) out("setNodeIcon " + t + "; " + this); setFlag(Flag.ICON, t); } @Override public boolean supportsCopyOnDrag() { return !hasFlag(Flag.SLIDE_STYLE) && isNodeIcon(); } /** @return true unless this is a node icon image */ @Override public boolean supportsUserResize() { return hasFlag(Flag.SLIDE_STYLE) || !isNodeIcon(); } @Override public boolean supportsUserLabel() { return SLIDE_LABELS && hasFlag(Flag.SLIDE_STYLE); } /** this for backward compat with old save files to establish the image as a special "node" image */ @Override public void XML_addNotify(Object context, String name, Object parent) { super.XML_addNotify(context, name, parent); if (parent instanceof LWNode) updateNodeIconStatus((LWNode)parent); } @Override protected void setParent(LWContainer parent) { super.setParent(parent); updateNodeIconStatus(parent); } private void updateNodeIconStatus(LWContainer parent) { //tufts.Util.printStackTrace("updateNodeIconStatus, mImage=" + mImage + " parent=" + parent); if (DEBUG.IMAGE) out("updateNodeIconStatus " + mImageRef + "; parent=" + parent); if (parent == null) return; if (parent instanceof LWNode && parent.getChild(0) == this && getResource() != null && getResource().equals(parent.getResource())) { // if first child of a LWNode is an LWImage, treat it as an icon setNodeIcon(true); } else { setNodeIcon(false); } } /** used by Actions to size the image */ void setMaxDimension(final float max) { //======================================================================================== // [FIXED]: if an image has an icon in cache, and we're creating a NEW RESOURCE, // such that resource properties image.width & image.height were never set, we can't know // the full pixel size, thus we can't be certain of the *precise* aspect, which is // important to prevent minor pixel size tweaking later. The only way around that // w/out forcing a load of a the whole image (which defeats the purpose of the image code // entirely) is to store the full pixel size in the icon itself. We now do this // when we write generated icons to disk, by saving the original full pixel size // in the image meta-data. // ======================================================================================== final int[] rawPixels = getFullPixelSize(); // note: we used to do this via aspect, not full pixel size (which is much safer), tho that // means we can no longer adjust the icon size until we have the full pixel size loaded. if (rawPixels == ImageRep.ZERO_SIZE) { Log.warn("setMaxDimension: image rep has unset size; " + ref()); } else { Size newSize = Images.fitInto(max, rawPixels); if (DEBUG.Enabled) out("setMaxDimension " + max + " -> " + newSize); setSize(newSize); } } public boolean hasImageError() { return ref().hasError(); } @Override public void setSelected(boolean selected) { boolean wasSelected = isSelected(); super.setSelected(selected); if (DEBUG.IMAGE) out("setSelected="+selected); if (selected && !wasSelected && hasImageError() && hasResource()) { // TODO: this check wants to be on LWComponent or LWNode, in case this is a regular // node containing an LWImage, we want the image to update, as it doesn't get selected. // Even better is actually to handle this in the global selection listener or // ActiveInstance of LWComponent listener. //Util.printStackTrace("ADD SELCTED IMAGE CLEANUP " + this); // don't know if this really needs to be a cleanup task, // or just an after-AWT task, but safer to do one of them: // TODO: this may be conflicting with our new image update code, and this would much // better be handled in the ActiveComponentHandler than via a cleanup task (which // should generally be a solution of last resort) See if we can handle this in // VUE.checkForAndHandleResourceUpdate // Note that this code does however also deal with a missing network resource suddenly // appearing, and then we can load the image from that //----------------------------------------------------------------------------- // PROBLEM: raw image focals can become selected during a presentation (a // result of our code auto-handling the selection of the current pathway // entry I presume), but CLEAUP TASKS are only run when undo marks are // generated, which is presumably NOT happening during a presentation, as no // edits should be happening, so in any case it may be fundamentally flawed // to add a cleanup tasks simply due to selection changes? Also, the net // problem is that MapViewers will NOT repaint with outstanding cleanup tasks! // // That results in failures to repaint raw image focals during presentations // after recovery from low-memory repaints -- e.g., the scaled up icon // remains on screen, even tho the full image data has actually arrived. // // Solution: for now, MapViewers will repaint even with outstanding cleanup // tasks when the focal is anything other than a full map. // // A fuller solution would distingush an EOM error above from // an IO error -- we actually only want to attempt re-init on IO errors, // NOT memory errors. // ----------------------------------------------------------------------------- //Util.printStackTrace("SELECTED WITH ERROR " + this); addCleanupTask(new Runnable() { public void run() { if (hasResource() && VUE.getSelection().only() == LWImage.this) // TODO: would need an UNDO-MARK for this, so really // this wants to trigger our new LWNode image0 replacement code initRef(getResource()); }}); } } @Override public void XML_completed(Object context) { super.XML_completed(context); if (super.width < MinWidth || super.height < MinHeight) { Log.info(String.format("bad size: adjusting to minimum %dx%d: %s", MinWidth, MinHeight, this)); takeSize(MinWidth, MinHeight); } // we can't rely on this being cleared via setSizeImpl, as persistance currently accesses width/height directly clearFlag(Flag.UNSIZED); } @Override public void setResource(Resource r) { super.setResource(r); // note: mXMLRestoreUnderway is set to true during duplicate operations if (mXMLRestoreUnderway || r == null) { // don't need to init anything -- size already known if (r == null) setFlag(Flag.UNSIZED); } else { initRef(r); } } private void recordUndoMark() { // if (!javax.swing.SwingUtilities.isEventDispatchThread()) // throw new Error("can only record marks in AWT"); // mAWTUndoMark = UndoManager.getKeyForNextMark(this); // out("SET MARK TO " + Util.tags(mAWTUndoMark)); // Util.printStackTrace("SET-MARK " + mAWTUndoMark); } private void syncFurtherEventsToLastMark() { // if (javax.swing.SwingUtilities.isEventDispatchThread()) { // out("can only sync to events in non-AWT threads"); // } else { // UndoManager.attachCurrentThreadToMark(mAWTUndoMark); // out("ATTACHED TO MARK " + Util.tags(mAWTUndoMark)); // if (mAWTUndoMark != null) Util.printStackTrace("ATTACHED TO MARK " + mAWTUndoMark); // } } // private void forceUndoSizeRecords() { // UndoManager undoQueue = getUndoManager(); // if (undoQueue == null) // return; // LWComponent sizeMayChange = getParent(); // while (sizeMayChange != null && !sizeMayChange.isTopLevel()) { // // Simulate a size record to make certain there's one in there, just in case. // // There's no problem if the size doesn't actually end up changing -- the event // // will be applied but then ignored. // // Oh, crap -- this won't handle the REDO case for user-sized nodes tho, will it -- the // // size they took on after the image load completed was ignored! // // Oh, and our undo-mark hack won't even work now, as image-load threads are reused.... // undoQueue.LWCChanged(new LWCEvent(this, sizeMayChange, LWKey.Size, sizeMayChange.getSize())); // Log.info("FORCED SIZE RECORDING FOR " + sizeMayChange + ": " + sizeMayChange.getSize()); // sizeMayChange = sizeMayChange.getParent(); // } // } /** @see ImageRef.Listener */ public synchronized void imageRefUpdate(Object cause) { // We will ONLY get this message once we already know the full image size. // note: this WILL NOT be in the AWT thread, so for full thread safety any // size changes should happen on AWT, tho that may conflict with our undo-tracking // for threaded inits? Tho as long as we make use of the mark we obtain, // that shouldn't matter, right? // // Oh -- that happens via UndoManager.attachCurrentThreadToMark(mUndoMarkForThread), // which if we do in AWT will then attach all of AWT to that undo mark? That // whole mechanism probably really needs to passed all the way through to the // notify so the undo manager can detect that there? if (DEBUG.IMAGE||DEBUG.WORK) out("imageRefUpdate: cause=" + Util.tags(cause)); if (cause == ImageRef.KICKED) { // doesn't work: the mark's already been made //forceUndoSizeRecords(); recordUndoMark(); // In any case, now that we ignore non-AWT size updates, in practice, // this is a rare bug -- only when an image load is so slow that even // the size comes in delayed. return; } //--------------------------------------------------------------------------------------------------- // Note: there are an absurd number of codepaths to test for the proper handlding of size & aspect setting: // - runtime 0: fresh raw disk image load, then w/icon generation // - runtime 0: now icon is generated, is now in memory cache (TODO: check: is the full sourceSize in the cache?) // - new runtime, icon in disk cache // - new runtime, icon in memory cache // - quick import // - file system drags // - web browser URL field drag // - web browser image drag (different for different browsers) // - web browser image search light-tray drag (sizes sometimes decoded from the URLs) // - persisted map restores // ++ PLUS POSSIBLE COMBINATIONS OF THE ABOVE (!) //--------------------------------------------------------------------------------------------------- if (hasFlag(Flag.UNSIZED) && ref().fullPixelSize() != ImageRef.ZERO_SIZE) { syncFurtherEventsToLastMark(); if (DEBUG.IMAGE) out("imageRefUpdate: SIZE IS UNSET; pixels=" + Util.tags(ref().fullPixelSize())); //======================================================================================== // todo: call a method, that will have half of the guessAtBestSizeCode, // which if UNSIZED is true, will handle aspecting for both // node icons and slide styles. The our guess method will become much simpler. // This might even be able to happen in setSizeImpl? //======================================================================================== if (isNodeIcon()) autoShape(); else setToNaturalSize(); // PROBLEM?: shouldn't be undoable, tho it's already working that way because of general setSizeImpl? } // always repaint -- e.g., we may already have the valid size, so no // new size event will trigger a repaint, or this just may mean the // pixels have arrived. Technically, we should be able to skip this // unless we've got new pixel data, but we don't get a separate message // for that at the moment. //if (cause == ImageRef.REPAINT) repaintPixels(); } public void reloadImage() { mImageRef = ImageRef.EMPTY; initRef(getResource()); } private void repaintPixels() { //Log.debug("ISSUING PIXEL REPAINT " + getLabel()); // tho we're not going to be on the AWT thread, the result of a Repaint/RepaintRegion is // ultimately going to be to safely stick a repaint request into the AWT queue, so we // should be okay. We will have to pull the bounds of this LWImage non-synchronized // tho. if (alive()) { notify(LWKey.RepaintRegion); // // BELOW TURNED OFF: BETTER FOR PRESENTATIONS (e.g., image cycling can settle down) // // Note: settle-down isn't actually working on our 4 images on a slide test where on // // only 3 fit in memory... // if (false && isSelected()) { // can force on for seeing DEBUG.BOXES behind-scenes status changes in other images // // we check selected, because we need to also redraw selection boxes if they're // // visible -- a better way to handle that would be to increase the size of the // // region here, or better yet, have MapViewer automatically handle that // // if the requested region repaint was on a selected component. // notify(LWKey.Repaint); // } else // notify(LWKey.RepaintRegion); } } @Override public void setToNaturalSize() { setSize(getFullPixelSize()); } private int[] getFullPixelSize() { int[] size = ref().fullPixelSize(); if (size == ImageRep.ZERO_SIZE && hasResource()) { // It's possible to have only the icon loaded, and not the full representation, and // thus not immediately know the size of the full representation. // todo: note that even if one ImageRep loads it's full size, others that haven't // reqested it yet will still end up using this method. As soon as one rep has a real // size, all should get their real size. (also another argument for making them // singleton) int[] suggested = getResourceImageSize(getResource()); if (suggested != null) return suggested; // if no resource suggestion, be sure to return ImageRep.ZERO_SIZE } return size; } private int[] getResourceImageSize(Resource r) { if (r == null || r.getProperties() == null) return null; if (DEBUG.IMAGE) out("checking for resource size props in: " + r.getProperties().asProperties()); final int w = r.getProperty(Resource.IMAGE_WIDTH, -1); final int h = r.getProperty(Resource.IMAGE_HEIGHT, -1); if (w > 0 && h > 0) { final int[] size = new int[2]; size[0] = w; size[1] = h; // todo: would be better to NOT let this be used as a "valid" size -- if it later // disagrees with something found on disk (the image has changed on disk since this // resource was created), the new, real image size should take priority. return size; } else { return null; } } private void setSize(Size s) { setSize(s.width, s.height); } private void setSize(int[] size) { if (size == ImageRep.ZERO_SIZE) { Log.warn("skipping setSize of ZERO_SIZE; " + this); } else { setSize(size[0], size[1]); } } public void suggestSize(int w, int h) { if (DEBUG.Enabled) out("suggestSize " + w + "x" + h); setTmpSize(w, h); } void setTmpSize(float w, float h) { setSizeImpl(w, h, true); } @Override protected synchronized void setSizeImpl(float w, float h, final boolean internal) { if (DEBUG.Enabled) out("setSizeImpl " + w + "x" + h + "; internal=" + internal); // Tracking UNSIZED lets us skip the undo-queue for size changes, but not for size // changes to auto-sizing containers (e.g., LWNode) that happen due to re-layout. E.g., you // drop an image, it takes a long time to load, you do other stuff to the map, the image // finally gets it's size, resizing it's parents -- if we purely ignore all those size // changes, then you UNDO THE DROP, those nodes won't be sized back. We used to handle // this via our undo-mark hack. // This is now handled very nicely in LWComponent by skipping undo events for // size reports from non-AWT threads. This works most of the time because // most nodes are auto-sized and auto-relayout. But user-sized nodes are still // a problem. // A way to handle that could be to force a size report for all parents when we begin a // threaded image load, so no matter what there will be a size recorded into the undo-queue // at that point. This is much simpler than our our undo-mark hack. The problem is doing // this BEFORE the "Drop" mark happens -- it will only work if that's possible. super.setSizeImpl(w, h, internal); if (internal) { if (!hasFlag(Flag.UNSIZED)) { if (DEBUG.Enabled) Log.error("ATTEMPT TO DE-VALIDATE SIZE! " + this, new Throwable("HERE")); //setFlag(Flag.UNSIZED); } } else { if (hasFlag(Flag.UNSIZED)) { if (DEBUG.Enabled) out("setting first VALID size " + w + "x" + h); clearFlag(Flag.UNSIZED); } } } private float aspect() { return ref().aspect(); } // private void autoShape() { // float maxSide = (float) Math.max(this.width, this.height); // final Size newSize = Images.fitInto(maxSide, this.width, this.height); // if (DEBUG.Enabled) out("autoShape: maxSiide=" + maxSide // + "\n\t in: " + width + "," + height // + "\n\tout: " + newSize); // setSize(newSize); // } private void autoShape() { shapeToAspect(aspect()); } private void shapeToAspect(float aspect) { if (aspect <= 0) { Log.warn("bad aspect in shapeToAspect: " + aspect); return; } // TODO: reconcile w/Imags.fitInto used in setMaxDimension final Size newSize = ConstrainToAspect(aspect, this.width, this.height); //final Size newSize = Images.fitInto( if (DEBUG.Enabled) out("shapeToAspect " + aspect + "\n\t in: " + width + "," + height + "\n\tout: " + newSize); setSize(newSize); } /** * Don't let us get bigger than the size of our image, or * smaller than MinWidth/MinHeight. */ protected void userSetSize(float width, float height, MapMouseEvent e) { final float aspect = aspect(); if (DEBUG.IMAGE) out("userSetSize " + Util.fmt(new Point2D.Float(width, height)) + "; aspect=" + aspect + "; e=" + e); //Util.printStackTrace("HERE"); if (e != null && e.isShiftDown()) { // Unconstrained aspect ratio scaling super.userSetSize(width, height, e); } else if (aspect > 0) { Size newSize = ConstrainToAspect(aspect, width, height); setSize(newSize.width, newSize.height); } else if (width > 0 && height > 0) { setSize(width, height); } else { Log.error("unhandled userSetSize w/aspect " + aspect + ": " + width + "x" + height + " on " + this); } // If (e != null && e.isShiftDown()) // croppingSetSize(width, height); // else // scalingSetSize(width, height); } private Shape getClipShape() { //return super.drawnShape; // todo: cache & handle knowing if we need to update return new Rectangle2D.Float(0,0, getWidth(), getHeight()); } private static final AlphaComposite HudTransparency = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f); //private static final Color IconBorderColor = new Color(255,255,255,64); //private static final Color IconBorderColor = new Color(0,0,0,64); // screwing up in composite drawing //private static final Color IconBorderColor = Color.gray; private ImageRef ref() { return mImageRef; } private ImageRef refLoaded() { if (mImageRef == ImageRef.EMPTY && hasResource()) return initRef(getResource()); else return ref(); } private ImageRef initRef(Resource r) { if (DEBUG.IMAGE||DEBUG.WORK) { out("initRef: " + r + "; props=" + r.getProperties()); if (!java.awt.EventQueue.isDispatchThread()) out("initRef: NOT ON AWT: " + Thread.currentThread()); } if (mImageRef != ImageRef.EMPTY && mImageRef.source().original == r) { Log.info("re-init of same image source: " + this + "; " + r, new Throwable("HERE")); } final ImageRef newRef = ImageRef.create(this, r); mImageRef = newRef; // we should also auto-shape if we already have the size info, which we should // if the image is in the cache -- both for proper node icones when alread in cache, // and for replacing the ImageRef on a raw images -- both to get the right aspect // THIS IS WHERE WE USED TO CREATE AN UNDO MARK -- I think we // can now handle this by just ignoring async size events? if (hasFlag(Flag.UNSIZED)) { // okay to do this after undo-mark was obtained, as this should NOT be generating // undoable events. guessAtBestSize(r); } return newRef; } // if the size in the ref() is already known, we'll be using that, otherwise, // if we find any size info in the resource, use that as a temporary size private void guessAtBestSize(Resource r) { if (DEBUG.IMAGE) out("guessAtBestSize: " + ref() + "; " + r); final int[] fullSize = ref().fullPixelSize(); Size guess = null; if (fullSize != ImageRef.ZERO_SIZE) { if (isNodeIcon()) { guess = Images.fitInto(DefaultIconMaxSide, fullSize); } else if (hasFlag(Flag.SLIDE_STYLE)) { guess = Images.fitInto(LWSlide.SlideWidth / 4, fullSize); } else { guess = new Size(fullSize); } // really, we want to just do setSize, but we need to do setSizeImpl so it's // not undoable, but then we have to clear our own UNSIZED bit setSizeImpl(guess.width, guess.height, true/*=INTERNAL*/); clearFlag(Flag.UNSIZED); } else { final int[] suggestSize = getResourceImageSize(r); if (suggestSize != null) { guess = new Size(suggestSize); if (isNodeIcon()) guess = Images.fitInto(DefaultIconMaxSide, guess); else if (hasFlag(Flag.SLIDE_STYLE)) guess = Images.fitInto(LWSlide.SlideWidth / 4, guess); setTmpSize(guess.width, guess.height); } } } @Override protected void preCacheImpl() { refLoaded().preCacheRef(); } /** This currently makes LWImages invisible to selection (they're locked in their parent node */ @Override protected LWComponent defaultPickImpl(PickContext pc) { if (!hasFlag(Flag.SLIDE_STYLE) && isNodeIcon()) return pc.pickDepth > 0 ? this : getParent(); else return this; } private void drawImage(DrawContext dc) { final ImageRef ref = refLoaded(); if (hasFlag(Flag.UNSIZED)) { // seems a bit overkill, but oddly needed if the image is already in the cache, as we // don't currently immediately pull the aspect from the icon image size data in the // cache at init time (we're still waiting for a draw to do that), which we should be // doing in ImageRef/ImageRep guessAtBestSize(getResource()); } if (isSelected() && dc.isInteractive() && dc.focal != this) { dc.g.setColor(COLOR_HIGHLIGHT); dc.g.setStroke(new BasicStroke(getStrokeWidth() + SelectionStrokeWidth)); dc.g.draw(getZeroShape()); } ref.drawInto(dc, getWidth(), getHeight()); } @Override protected void drawImpl(DrawContext dc) { drawImage(dc); } // private boolean isIndicatedIn(DrawContext dc) { // if (dc.hasIndicated()) { // final LWComponent indication = dc.getIndicated(); // return indication == this // || (isNodeIcon() && getParent() == indication); // } else { // return false; // } // } // public void drawWithoutShape(DrawContext dc) // { // // see comments on VUE-892 - this code does not seem to present the right behavior for search // // please re-enable this code and reopen the bug if this code is needed- Dan H // /* // if (isNodeIcon()) { // final LWComponent parent = getParent(); // if (parent != null && parent.isFiltered()) { // // this is a hack because images are currently special cased as tied to their parent node // return; // } // }*/ // final Shape shape = getZeroShape(); // if (isSelected() && dc.isInteractive() && dc.focal != this) { // dc.g.setColor(COLOR_HIGHLIGHT); // dc.g.setStroke(new BasicStroke(getStrokeWidth() + SelectionStrokeWidth)); // dc.g.draw(shape); // } // final boolean indicated = isIndicatedIn(dc); // if (indicated && dc.focal != this) { // Color c = getParent().getRenderFillColor(dc); // if (VueUtil.isTranslucent(c)) // c = Color.gray; // dc.g.setColor(c); // dc.g.fill(shape); // } // if (isNodeIcon && dc.focal != this) { // if (!indicated && DefaultMaxDimension > 0) // drawImageBox(dc); // // Forced border for node-icon's: // if ((mImage != null ||*/ indicated) && !getParent().isTransparent() && DefaultMaxDimension > 0) { // // this is somehow making itext PDF generation through a GC worse... (probably just a bad tickle) // dc.g.setStroke(STROKE_TWO); // //dc.g.setColor(IconBorderColor); // dc.g.setColor(getParent().getRenderFillColor(dc).darker()); // dc.g.draw(shape); // } // } else if (!indicated) { // if (!super.isTransparent()) { // final Color fill = getFillColor(); // if (fill == null || fill.getAlpha() == 0) { // Util.printStackTrace("isTransparent lied about fill " + fill); // } else { // dc.g.setColor(fill); // dc.g.fill(shape); // } // } // drawImageBox(dc); // if (getStrokeWidth() > 0) { // dc.g.setStroke(this.stroke); // dc.g.setColor(getStrokeColor()); // dc.g.draw(shape); // } // if (supportsUserLabel() && hasLabel()) { // initTextBoxLocation(getLabelBox()); // if (this.labelBox.getParent() == null) { // dc.g.translate(labelBox.getBoxX(), labelBox.getBoxY()); // this.labelBox.draw(dc); // } // } // } // //super.drawImpl(dc); // need this for label // } /** For interactive images as separate objects, which are currently disabled */ /* private void drawInteractive(DrawContext dc) { drawPathwayDecorations(dc); drawSelectionDecorations(dc); dc.g.translate(getX(), getY()); float _scale = getScale(); if (_scale != 1f) dc.g.scale(_scale, _scale); // if (getStrokeWidth() > 0) { // dc.g.setStroke(new BasicStroke(getStrokeWidth() * 2)); // dc.g.setColor(getStrokeColor()); // dc.g.draw(new Rectangle2D.Float(0,0, getWidth(), getHeight())); // } drawImage(dc); if (getStrokeWidth() > 0) { dc.g.setStroke(this.stroke); dc.g.setColor(getStrokeColor()); dc.g.draw(new Rectangle2D.Float(0,0, getWidth(), getHeight())); } if (isSelected() && dc.isInteractive()) { dc.g.setComposite(HudTransparency); dc.g.setColor(Color.WHITE); dc.g.fill(mIconBlock); dc.g.setComposite(AlphaComposite.Src); // TODO: set a clip so won't draw outside // image bounds if is very small mIconBlock.draw(dc); } if (_scale != 1f) dc.g.scale(1/_scale, 1/_scale); dc.g.translate(-getX(), -getY()); } */ //---------------------------------------------------------------------------------------- // some old status code //---------------------------------------------------------------------------------------- private static final Color EmptyColorDark = new Color(0,0,0,128); private static final Color LoadedColorDark = new Color(0,0,0,160); private static final Color EmptyColorLight = new Color(128,128,128,64); private static final Color LoadedColorLight = new Color(128,128,128,128); private Color getEmptyColor(DrawContext dc) { Color fill = getFinalFillColor(dc); return Color.black.equals(fill) ? EmptyColorLight : EmptyColorDark; // final LWComponent parent = getParent(); // Color pc; // if (parent != null && (pc=parent.getRenderFillColor(dc)) != null) { // return Color.black.equals(pc) ? EmptyColorLight : EmptyColorDark; // } else if (dc.getBackgroundFill() != null && dc.getBackgroundFill().equals(Color.black)) // return EmptyColorLight; // else // return EmptyColorDark; } private Color getLoadedColor(DrawContext dc) { final LWComponent parent = getParent(); Color pc; if (parent != null && (pc=parent.getRenderFillColor(dc)) != null) { return Color.black.equals(pc) ? LoadedColorLight : LoadedColorDark; } else return LoadedColorDark; } //---------------------------------------------------------------------------------------- // old experimental on-map text label code //---------------------------------------------------------------------------------------- @Override protected TextBox getLabelBox() { if (super.labelBox == null) { initTextBoxLocation(super.getLabelBox()); //layoutImpl("LWImage.labelBox-init"); } return this.labelBox; } @Override public void initTextBoxLocation(TextBox textBox) { textBox.setBoxLocation(0, -textBox.getHeight()); } //---------------------------------------------------------------------------------------- // Image rotation was old feature we removed, but this stuff may still be in some old save files. //---------------------------------------------------------------------------------------- public static final Key KEY_Rotation = new Key("image.rotation", KeyType.STYLE) { // rotation in radians public void setValue(LWComponent c, Object val) { ((LWImage)c).setRotation(((Double)val).doubleValue()); } public Object getValue(LWComponent c) { return new Double(((LWImage)c).getRotation()); } }; public void setRotation(double rad) { // Object old = new Double(mRotation); // this.mRotation = rad; // notify(KEY_Rotation, old); } public double getRotation() { //return mRotation; return 0; } // public static final Key Key_ImageOffset = new Key("image.pan", KeyType.STYLE) { // public void setValue(LWComponent c, Object val) { ((LWImage)c).setOffset((Point2D)val); } // public Object getValue(LWComponent c) { return ((LWImage)c).getOffset(); } // }; public void setOffset(Point2D p) { // if (p.getX() == mOffset.x && p.getY() == mOffset.y) // return; // Object oldValue = new Point2D.Float(mOffset.x, mOffset.y); // if (DEBUG.IMAGE) out("LWImage setOffset " + VueUtil.out(p)); // this.mOffset.setLocation(p.getX(), p.getY()); // notify(Key_ImageOffset, oldValue); } public Point2D getOffset() { return null; // return new Point2D.Float(mOffset.x, mOffset.y); } @Override protected void out(String s) { Log.debug(String.format("%s %s", this, s)); } // @Override protected boolean intersectsImpl(Rectangle2D mapRect) { // boolean i = super.intersectsImpl(mapRect); // Log.info("INTERSECTS " + Util.tags(i?"YES ":" NO ") + Util.tags(mapRect) + " " + getLabel()); // return i; // } // @Override protected Object requiresPaintImpl(DrawContext dc) { // Object o = super.requiresPaintImpl(dc); // //Log.info("REQRSPAINT " + Util.tags(i?"YES ":" NO ") + getLabel()); // Log.info("REQRSPAINT " + Util.tags(o) + " in " + dc + " " + getLabel()); // // we're see "noClip" here for some reason... // return o; // } public static void main(String args[]) throws Exception { // GUI init required for fully loading all image codecs (tiff gets left behind otherwise) // Ah: the TIFF reader in Java 1.5 apparently comes from the UI library: // [Loaded com.sun.imageio.plugins.tiff.TIFFImageReader // from /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Classes/ui.jar] VUE.init(args); System.out.println(java.util.Arrays.asList(javax.imageio.ImageIO.getReaderFormatNames())); System.out.println(java.util.Arrays.asList(javax.imageio.ImageIO.getReaderMIMETypes())); String filename = args[0]; java.io.File file = new java.io.File(filename); System.out.println("Reading " + file); System.out.println("ImageIO.read got: " + javax.imageio.ImageIO.read(file)); /* The below code requires the JAI libraries: // JAI (Java Advandced Imaging) libraries /System/Library/Java/Extensions/jai_core.jar /System/Library/Java/Extensions/jai_codec.jar Using this code below will also get us decoding .fpx images, tho we would need to convert it from the resulting RenderedImage / PlanarImage */ /* try { // Use the ImageCodec APIs com.sun.media.jai.codec.SeekableStream stream = new com.sun.media.jai.codec.FileSeekableStream(filename); String[] names = com.sun.media.jai.codec.ImageCodec.getDecoderNames(stream); System.out.println("ImageCodec API's found decoders: " + java.util.Arrays.asList(names)); com.sun.media.jai.codec.ImageDecoder dec = com.sun.media.jai.codec.ImageCodec.createImageDecoder(names[0], stream, null); java.awt.image.RenderedImage im = dec.decodeAsRenderedImage(); System.out.println("ImageCodec API's got RenderedImage: " + im); Object image = javax.media.jai.PlanarImage.wrapRenderedImage(im); System.out.println("ImageCodec API's got PlanarImage: " + image); } catch (Exception e) { e.printStackTrace(); } // We're not magically getting any new codec's added to ImageIO after the above code // finds the .fpx codec... System.out.println(java.util.Arrays.asList(javax.imageio.ImageIO.getReaderFormatNames())); System.out.println(java.util.Arrays.asList(javax.imageio.ImageIO.getReaderMIMETypes())); */ } }