diff --git a/diylc/diylc-core/src/org/diylc/common/ComponentType.java b/diylc/diylc-core/src/org/diylc/common/ComponentType.java index ae50ace5..87b5bda9 100644 --- a/diylc/diylc-core/src/org/diylc/common/ComponentType.java +++ b/diylc/diylc-core/src/org/diylc/common/ComponentType.java @@ -48,11 +48,12 @@ public class ComponentType { private IComponentTransformer transformer; private KeywordPolicy keywordPolicy; private String keywordTag; + private boolean enableCache; public ComponentType(String name, String description, CreationMethod creationMethod, String category, String namePrefix, String author, Icon icon, Class> instanceClass, double zOrder, boolean flexibleZOrder, BomPolicy bomPolicy, boolean autoEdit, IComponentTransformer transformer, - KeywordPolicy keywordPolicy, String keywordTag) { + KeywordPolicy keywordPolicy, String keywordTag, boolean enableCache) { super(); this.name = name; this.description = description; @@ -69,6 +70,7 @@ public class ComponentType { this.transformer = transformer; this.keywordPolicy = keywordPolicy; this.keywordTag = keywordTag; + this.enableCache = enableCache; } public String getName() { @@ -130,6 +132,10 @@ public class ComponentType { public String getKeywordTag() { return keywordTag; } + + public boolean getEnableCache() { + return enableCache; + } @Override public String toString() { diff --git a/diylc/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java b/diylc/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java index 367811f0..5a52e663 100644 --- a/diylc/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java +++ b/diylc/diylc-core/src/org/diylc/core/annotations/ComponentDescriptor.java @@ -110,5 +110,12 @@ public @interface ComponentDescriptor { * * @return */ - String keywordTag() default ""; + String keywordTag() default ""; + + /** + * Only to be used for components that span in its eternity in the rectangle defined by the two outside control points. + * + * @return true if the component drawing should be cached + */ + boolean enableCache() default false; } diff --git a/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java b/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java index b5ef3e3f..4a269309 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java +++ b/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java @@ -93,7 +93,8 @@ public class ComponentProcessor { double zOrder; boolean flexibleZOrder; BomPolicy bomPolicy; - boolean autoEdit; + boolean autoEdit; + boolean cacheDrawing; IComponentTransformer transformer; KeywordPolicy keywordPolicy; String keywordTag; @@ -111,7 +112,8 @@ public class ComponentProcessor { autoEdit = annotation.autoEdit(); transformer = getComponentTransformer(annotation.transformer()); keywordPolicy = annotation.keywordPolicy(); - keywordTag = annotation.keywordTag(); + keywordTag = annotation.keywordTag(); + cacheDrawing = annotation.enableCache(); } else { // default return null; } @@ -133,7 +135,7 @@ public class ComponentProcessor { } ComponentType componentType = new ComponentType(name, description, creationMethod, category, namePrefix, author, icon, clazz, zOrder, - flexibleZOrder, bomPolicy, autoEdit, transformer, keywordPolicy, keywordTag); + flexibleZOrder, bomPolicy, autoEdit, transformer, keywordPolicy, keywordTag, cacheDrawing); componentTypeMap.put(clazz.getName(), componentType); return componentType; } diff --git a/diylc/diylc-core/src/org/diylc/presenter/Connection.java b/diylc/diylc-core/src/org/diylc/presenter/Connection.java index 73d41b10..81f66edc 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/Connection.java +++ b/diylc/diylc-core/src/org/diylc/presenter/Connection.java @@ -1,7 +1,29 @@ +/* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC 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 3 of the + * License, or (at your option) any later version. + * + * DIYLC 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 DIYLC. If not, see + * . + */ package org.diylc.presenter; import java.awt.geom.Point2D; +/** + * Represents a continuity connection between two points. + * + * @author bancika + */ public class Connection { private Point2D p1; diff --git a/diylc/diylc-core/src/org/diylc/presenter/DrawingCache.java b/diylc/diylc-core/src/org/diylc/presenter/DrawingCache.java new file mode 100644 index 00000000..eff871f6 --- /dev/null +++ b/diylc/diylc-core/src/org/diylc/presenter/DrawingCache.java @@ -0,0 +1,147 @@ +/* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC 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 3 of the + * License, or (at your option) any later version. + * + * DIYLC 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 DIYLC. If not, see + * . + */ +package org.diylc.presenter; + +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.diylc.common.ComponentType; +import org.diylc.core.ComponentState; +import org.diylc.core.IDIYComponent; +import org.diylc.core.IDrawingObserver; +import org.diylc.core.Project; + +/** + * A layer that injects itself between the provided {@link G2DWrapper} instance and the underlying {@link Graphics2D} instance. + * For all components that are enabled for caching, it stores a {@link BufferedImage} representing the component. + * The state of the component is recorded and compared next time it needs to be rendered to make sure that the cache is up to date. + * + * @author bancika + */ +public class DrawingCache { + + private Map, CacheValue> imageCache = new HashMap, CacheValue>(); + + private static final Logger LOG = Logger.getLogger(DrawingCache.class); + + public static DrawingCache Instance = new DrawingCache(); + + @SuppressWarnings("unchecked") + public void draw(IDIYComponent component, G2DWrapper g2d, ComponentState componentState, + boolean outlineMode, Project project, double zoom, boolean trackArea) { + ComponentType type = ComponentProcessor.getInstance() + .extractComponentTypeFrom((Class>) component.getClass()); + + if (type.getEnableCache()) { + // if we need to apply caching + Point firstPoint = component.getControlPoint(0); + Point lastPoint = component.getControlPoint(component.getControlPointCount() - 1); + + // cache hit! + CacheValue value = null; + if (imageCache.containsKey(component)) + value = imageCache.get(component); + + // only honor the cache if the component hasn't changed in the meantime + if (value == null || !value.getComponent().equalsTo(component) || value.getState() != componentState) { + LOG.trace("Rendering " + component.getName() + " for cache."); + int width = (int) (Math.abs(firstPoint.x - lastPoint.x) * zoom) + 2; + int height = (int) (Math.abs(firstPoint.y - lastPoint.y) * zoom) + 2; + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D cg2d = image.createGraphics(); + + // copy over rendering settings + cg2d.setRenderingHints(g2d.getRenderingHints()); + + cg2d.setClip(0, 0, width, height); + + // translate to make the top-left corner of the component match (0, 0) + cg2d.translate(-firstPoint.x, -firstPoint.y); + G2DWrapper wrapper = new G2DWrapper(cg2d, zoom); + + wrapper.startedDrawingComponent(); + if (!trackArea) { + wrapper.stopTracking(); + } + + // now draw the component to the buffer image + component.draw(wrapper, componentState, outlineMode, project, wrapper); + + wrapper.finishedDrawingComponent(); + + cg2d.dispose(); + + value = new CacheValue(component, image, wrapper, componentState); + imageCache.put(component, value); + } + + // draw cached image + g2d.getCanvasGraphics().drawImage(value.getImage(), firstPoint.x, firstPoint.y, null); + + // copy over tracking area from the cache + g2d.merge(value.getWrapper()); + } else { + // no caching, just draw as usual + component.draw(g2d, componentState, outlineMode, project, g2d); + } + } + + private class CacheValue { + IDIYComponent component; + BufferedImage image; + G2DWrapper wrapper; + ComponentState state; + + public CacheValue(IDIYComponent component, BufferedImage image, G2DWrapper wrapper, ComponentState state) { + super(); + // take a copy of the component, so we can check if it changed in the meantime + try { + this.component = component.clone(); + } catch (CloneNotSupportedException e) { + } + this.image = image; + this.wrapper = wrapper; + this.state = state; + } + + public IDIYComponent getComponent() { + return component; + } + + public BufferedImage getImage() { + return image; + } + + public G2DWrapper getWrapper() { + return wrapper; + } + + public ComponentState getState() { + return state; + } + + @Override + public String toString() { + return component.getName() + ":" + state; + } + } +} diff --git a/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java b/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java index 97d19125..352cb719 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java +++ b/diylc/diylc-core/src/org/diylc/presenter/DrawingManager.java @@ -83,6 +83,7 @@ public class DrawingManager { private Theme theme = (Theme) ConfigurationManager.getInstance().readObject(IPlugInPort.THEME_KEY, Constants.DEFAULT_THEME); + // Maps keyed by object reference. // Keeps Area object of each drawn component. private Map, ComponentArea> componentAreaMap; // Maps components to the last state they are drawn in. Also, used to @@ -263,7 +264,7 @@ public class DrawingManager { // Do not track the area if component is not invalidated and was // drawn in the same state. boolean trackArea = lastDrawnStateMap.get(component) != state; - + synchronized (g2d) { g2dWrapper.startedDrawingComponent(); if (!trackArea) { @@ -275,11 +276,15 @@ public class DrawingManager { } // Draw the component through the g2dWrapper. try { - component.draw(g2dWrapper, state, drawOptions.contains(DrawOption.OUTLINE_MODE), project, g2dWrapper); +// component.draw(g2dWrapper, state, drawOptions.contains(DrawOption.OUTLINE_MODE), project, g2dWrapper); + DrawingCache.Instance.draw(component, g2dWrapper, state, drawOptions.contains(DrawOption.OUTLINE_MODE), project, zoom, trackArea); if (g2dWrapper.isTrackingContinuityArea()) { - LOG.info("Component " + component.getName() + " of type " + component.getClass().getName() + " did not stop tracking continuity area."); - g2dWrapper.stopTrackingContinuityArea(); + LOG.info("Component " + component.getName() + " of type " + component.getClass().getName() + " did not stop tracking continuity area."); } + + // just in case, stop all tracking + g2dWrapper.stopTrackingContinuityArea(); + g2dWrapper.stopTracking(); } catch (Exception e) { LOG.error("Error drawing " + component.getName(), e); failedComponents.add(component); diff --git a/diylc/diylc-core/src/org/diylc/presenter/G2DWrapper.java b/diylc/diylc-core/src/org/diylc/presenter/G2DWrapper.java index 42a969ae..16f9d9ec 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/G2DWrapper.java +++ b/diylc/diylc-core/src/org/diylc/presenter/G2DWrapper.java @@ -120,6 +120,10 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver { continuityMarker = null; currentTx = new AffineTransform(); } + + public Graphics2D getCanvasGraphics() { + return canvasGraphics; + } /** * Clears out the current area and caches canvas settings. @@ -157,6 +161,12 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver { // Map sorted = sortByComparator(trackingStacks, false); return new ComponentArea(currentArea, continuityPositiveAreas.values(), continuityNegativeAreas.values()); } + + public void merge(G2DWrapper wrapper) { + currentArea = wrapper.currentArea; + continuityPositiveAreas = wrapper.continuityPositiveAreas; + continuityNegativeAreas = wrapper.continuityNegativeAreas; + } @Override public void startTracking() { @@ -721,6 +731,7 @@ class G2DWrapper extends Graphics2D implements IDrawingObserver { canvasGraphics.setXORMode(c1); } + @SuppressWarnings("unused") private static Map sortByComparator(Map unsortMap, final boolean order) { List> list = diff --git a/diylc/diylc-core/src/org/diylc/presenter/InstantiationManager.java b/diylc/diylc-core/src/org/diylc/presenter/InstantiationManager.java index 3a72207e..a7b91fb6 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/InstantiationManager.java +++ b/diylc/diylc-core/src/org/diylc/presenter/InstantiationManager.java @@ -65,10 +65,10 @@ public class InstantiationManager { public static final ComponentType clipboardType = new ComponentType("Clipboard contents", "Components from the clipboard", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null, - false, null, KeywordPolicy.NEVER_SHOW, null); + false, null, KeywordPolicy.NEVER_SHOW, null, false); public static final ComponentType blockType = new ComponentType("Building block", "Components from the building block", CreationMethod.SINGLE_CLICK, "Multi", "", "", null, null, 0, false, null, - false, null, KeywordPolicy.NEVER_SHOW, null); + false, null, KeywordPolicy.NEVER_SHOW, null, false); public InstantiationManager() {} diff --git a/diylc/diylc-library/lib/diylc-core.jar b/diylc/diylc-library/lib/diylc-core.jar index 5e292c76..8be14863 100644 Binary files a/diylc/diylc-library/lib/diylc-core.jar and b/diylc/diylc-library/lib/diylc-core.jar differ diff --git a/diylc/diylc-library/src/org/diylc/components/boards/Breadboard.java b/diylc/diylc-library/src/org/diylc/components/boards/Breadboard.java index f8d2d4a0..ff7c4cb7 100644 --- a/diylc/diylc-library/src/org/diylc/components/boards/Breadboard.java +++ b/diylc/diylc-library/src/org/diylc/components/boards/Breadboard.java @@ -49,7 +49,7 @@ import org.diylc.core.measures.SizeUnit; @ComponentDescriptor(name = "Breadboard", category = "Boards", author = "Branislav Stojkovic", description = "Prototyping solderless breadboard", instanceNamePrefix = "BB", zOrder = IDIYComponent.BOARD, bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, - keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME) + keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, enableCache = true) public class Breadboard extends AbstractComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/org/diylc/components/boards/EyeletBoard.java b/diylc/diylc-library/src/org/diylc/components/boards/EyeletBoard.java index 66dff972..61723414 100644 --- a/diylc/diylc-library/src/org/diylc/components/boards/EyeletBoard.java +++ b/diylc/diylc-library/src/org/diylc/components/boards/EyeletBoard.java @@ -43,7 +43,7 @@ import org.diylc.utils.Constants; @ComponentDescriptor(name = "Eyelet Board", category = "Boards", author = "Branislav Stojkovic", zOrder = IDIYComponent.BOARD, instanceNamePrefix = "Board", description = "Perforated board with eyelets", bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, - transformer = SimpleComponentTransformer.class) + transformer = SimpleComponentTransformer.class, enableCache = true) public class EyeletBoard extends AbstractBoard { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/org/diylc/components/boards/PerfBoard.java b/diylc/diylc-library/src/org/diylc/components/boards/PerfBoard.java index 905b0c3a..85e9d34b 100644 --- a/diylc/diylc-library/src/org/diylc/components/boards/PerfBoard.java +++ b/diylc/diylc-library/src/org/diylc/components/boards/PerfBoard.java @@ -44,7 +44,7 @@ import org.diylc.utils.Constants; @ComponentDescriptor(name = "Perf Board w/ Pads", category = "Boards", author = "Branislav Stojkovic", zOrder = IDIYComponent.BOARD, instanceNamePrefix = "Board", description = "Perforated board with solder pads", bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, keywordPolicy = KeywordPolicy.SHOW_TAG, - keywordTag = "Perf Board", transformer = SimpleComponentTransformer.class) + keywordTag = "Perf Board", transformer = SimpleComponentTransformer.class, enableCache = true) public class PerfBoard extends AbstractBoard { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/org/diylc/components/boards/ProtoBoard.java b/diylc/diylc-library/src/org/diylc/components/boards/ProtoBoard.java index 0b5988f5..c3c640dc 100644 --- a/diylc/diylc-library/src/org/diylc/components/boards/ProtoBoard.java +++ b/diylc/diylc-library/src/org/diylc/components/boards/ProtoBoard.java @@ -47,7 +47,7 @@ import org.diylc.core.measures.SizeUnit; @ComponentDescriptor(name = "P-0+o (Proto) Board", category = "Boards", author = "Branislav Stojkovic", description = "A prototyping board for modular synths, central bus with V+ GND and V-", instanceNamePrefix = "BB", zOrder = IDIYComponent.BOARD, bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, - keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME) + keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, enableCache = true) public class ProtoBoard extends AbstractComponent { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/org/diylc/components/boards/TriPadBoard.java b/diylc/diylc-library/src/org/diylc/components/boards/TriPadBoard.java index a9c95edd..eca992cc 100644 --- a/diylc/diylc-library/src/org/diylc/components/boards/TriPadBoard.java +++ b/diylc/diylc-library/src/org/diylc/components/boards/TriPadBoard.java @@ -45,7 +45,7 @@ import org.diylc.utils.Constants; @ComponentDescriptor(name = "TriPad Board", category = "Boards", author = "Hauke Juhls", zOrder = IDIYComponent.BOARD, instanceNamePrefix = "Board", description = "Perforated FR4 board with copper strips connecting 3 holes in a row (aka TriPad Board)", - keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, transformer = SimpleComponentTransformer.class) + keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, transformer = SimpleComponentTransformer.class, enableCache = true) public class TriPadBoard extends AbstractBoard { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-library/src/org/diylc/components/boards/VeroBoard.java b/diylc/diylc-library/src/org/diylc/components/boards/VeroBoard.java index 32298eb4..a87019d7 100644 --- a/diylc/diylc-library/src/org/diylc/components/boards/VeroBoard.java +++ b/diylc/diylc-library/src/org/diylc/components/boards/VeroBoard.java @@ -47,7 +47,7 @@ import org.diylc.utils.Constants; zOrder = IDIYComponent.BOARD, instanceNamePrefix = "Board", description = "Perforated FR4 board with copper strips connecting all holes in a row", bomPolicy = BomPolicy.SHOW_ONLY_TYPE_NAME, autoEdit = false, keywordPolicy = KeywordPolicy.SHOW_TYPE_NAME, - transformer = SimpleComponentTransformer.class) + transformer = SimpleComponentTransformer.class, enableCache = true) public class VeroBoard extends AbstractBoard { private static final long serialVersionUID = 1L; diff --git a/diylc/diylc-swing/lib/diylc-core.jar b/diylc/diylc-swing/lib/diylc-core.jar index 5e292c76..8be14863 100644 Binary files a/diylc/diylc-swing/lib/diylc-core.jar and b/diylc/diylc-swing/lib/diylc-core.jar differ diff --git a/diylc/diylc-swing/library/main.jar b/diylc/diylc-swing/library/main.jar index 6ea5d242..2fa6c9f8 100644 Binary files a/diylc/diylc-swing/library/main.jar and b/diylc/diylc-swing/library/main.jar differ