cache images to improve rendering performance
Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/layout/FakeGraphics.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/layout/FakeGraphics.java
index 379802e..c765b76 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/layout/FakeGraphics.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/layout/FakeGraphics.java
@@ -70,6 +70,9 @@
}
};
+ public void resetOrigin() {
+ }
+
public void moveOrigin(final int offsetX, final int offsetY) {
}
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/layout/endtoend/TracingGraphics.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/layout/endtoend/TracingGraphics.java
index 45668f0..bb6331c 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/layout/endtoend/TracingGraphics.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/layout/endtoend/TracingGraphics.java
@@ -67,6 +67,11 @@
}
@Override
+ public void resetOrigin() {
+ tracer.trace("Graphics.resetOrigin()");
+ }
+
+ @Override
public void moveOrigin(final int offsetX, final int offsetY) {
tracer.trace("Graphics.moveOrigin({0}, {1})", offsetX, offsetY);
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/core/Graphics.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/core/Graphics.java
index 4dd6e5b..77025de 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/core/Graphics.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/core/Graphics.java
@@ -28,6 +28,8 @@
public void dispose();
+ public void resetOrigin();
+
public void moveOrigin(int offsetX, int offsetY);
public int asAbsoluteX(int relativeX);
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/core/Point.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/core/Point.java
index 20f906d..cf5c0aa 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/core/Point.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/core/Point.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2004, 2008 John Krasnay and others.
+ * Copyright (c) 2004, 2016 John Krasnay and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -7,6 +7,7 @@
*
* Contributors:
* John Krasnay - initial API and implementation
+ * Florian Thienel - hashCode, equals, toString
*******************************************************************************/
package org.eclipse.vex.core.internal.core;
@@ -19,8 +20,6 @@
private final int y;
/**
- * Class constructor.
- *
* @param x
* X-coordinate.
* @param y
@@ -31,30 +30,52 @@
this.y = y;
}
- @Override
- public String toString() {
- final StringBuffer sb = new StringBuffer(80);
- sb.append(Point.class.getName());
- sb.append("[x=");
- sb.append(getX());
- sb.append(",y=");
- sb.append(getY());
- sb.append("]");
- return sb.toString();
- }
-
/**
- * Returns the x-coordinate.
+ * @return the x-coordinate.
*/
public int getX() {
return x;
}
/**
- * Returns the y-coordinate.
+ * @return the y-coordinate.
*/
public int getY() {
return y;
}
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + x;
+ result = prime * result + y;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Point other = (Point) obj;
+ if (x != other.x) {
+ return false;
+ }
+ if (y != other.y) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Point [x=" + x + ", y=" + y + "]";
+ }
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/DoubleBufferedRenderer.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/DoubleBufferedRenderer.java
index f92249a..abfc4f2 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/DoubleBufferedRenderer.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/DoubleBufferedRenderer.java
@@ -10,13 +10,19 @@
*******************************************************************************/
package org.eclipse.vex.core.internal.widget.swt;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Scrollable;
+import org.eclipse.vex.core.internal.core.Color;
import org.eclipse.vex.core.internal.core.Graphics;
import org.eclipse.vex.core.internal.core.Rectangle;
import org.eclipse.vex.core.internal.widget.IRenderer;
@@ -35,8 +41,11 @@
private final Scrollable control;
- private Image bufferImage;
private final Object bufferMonitor = new Object();
+ private final RenderBuffer[] buffer = new RenderBuffer[2];
+ private int visibleIndex = 0;
+
+ private final Map<URL, SwtImage> imageCache = new HashMap<URL, SwtImage>();
public DoubleBufferedRenderer(final Scrollable control) {
this.control = control;
@@ -56,63 +65,79 @@
private void widgetDisposed(final DisposeEvent e) {
synchronized (bufferMonitor) {
- if (bufferImage != null) {
- bufferImage.dispose();
+ for (int i = 0; i < buffer.length; i += 1) {
+ if (buffer[i] != null) {
+ buffer[i].dispose();
+ }
+ buffer[i] = null;
}
- bufferImage = null;
}
}
private void paintControl(final PaintEvent event) {
- event.gc.drawImage(getBufferImage(), 0, 0);
+ event.gc.drawImage(getVisibleImage(), 0, 0);
}
- private Image getBufferImage() {
+ private Image getVisibleImage() {
synchronized (bufferMonitor) {
- if (bufferImage == null) {
- bufferImage = createImage(Rectangle.NULL);
+ if (buffer[visibleIndex] == null) {
+ buffer[visibleIndex] = createRenderBuffer(Rectangle.NULL);
}
- return bufferImage;
+ return buffer[visibleIndex].image;
}
}
+ private RenderBuffer getRenderBuffer(final Rectangle viewPort) {
+ synchronized (bufferMonitor) {
+ final int index = (visibleIndex + 1) % 2;
+ if (buffer[index] == null) {
+ buffer[index] = createRenderBuffer(viewPort);
+ } else if (!viewPortFitsIntoImage(viewPort, buffer[index].image)) {
+ buffer[index].dispose();
+ buffer[index] = createRenderBuffer(viewPort);
+ }
+ return buffer[index];
+ }
+ }
+
+ private static boolean viewPortFitsIntoImage(final Rectangle viewPort, final Image image) {
+ return image.getBounds().contains(viewPort.getWidth() - 1, viewPort.getHeight() - 1);
+ }
+
@Override
public void render(final Rectangle viewPort, final IRenderStep... steps) {
- final Image image = createImage(viewPort);
- final GC gc = new GC(image);
- final Graphics graphics = new SwtGraphics(gc);
+ final RenderBuffer buffer = getRenderBuffer(viewPort);
- moveOriginToViewPort(viewPort, graphics);
+ buffer.graphics.resetOrigin();
+ clearViewPort(viewPort, buffer.graphics);
+ moveOriginToViewPort(viewPort, buffer.graphics);
- try {
- for (final IRenderStep step : steps) {
- try {
- step.render(graphics);
- } catch (final Throwable t) {
- t.printStackTrace(); //TODO proper logging
- }
+ for (final IRenderStep step : steps) {
+ try {
+ step.render(buffer.graphics);
+ } catch (final Throwable t) {
+ t.printStackTrace(); //TODO proper logging
}
- } finally {
- graphics.dispose();
- gc.dispose();
}
- makeRenderedImageVisible(image);
+ makeRenderedImageVisible(buffer.image);
}
- private Image createImage(final Rectangle viewPort) {
- return new Image(control.getDisplay(), Math.max(1, viewPort.getWidth()), Math.max(1, viewPort.getHeight()));
+ private RenderBuffer createRenderBuffer(final Rectangle viewPort) {
+ return new RenderBuffer(control.getDisplay(), viewPort.getWidth(), viewPort.getHeight(), imageCache);
}
private void moveOriginToViewPort(final Rectangle viewPort, final Graphics graphics) {
graphics.moveOrigin(0, -viewPort.getY());
}
+ private void clearViewPort(final Rectangle viewPort, final Graphics graphics) {
+ graphics.setColor(graphics.getColor(Color.WHITE));
+ graphics.fillRect(0, 0, viewPort.getWidth(), viewPort.getHeight());
+ }
+
private void makeRenderedImageVisible(final Image newImage) {
- final Image oldImage = swapBufferImage(newImage);
- if (oldImage != null) {
- oldImage.dispose();
- }
+ swapBufferImage(newImage);
control.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
@@ -121,12 +146,27 @@
});
}
- private Image swapBufferImage(final Image newImage) {
+ private void swapBufferImage(final Image newImage) {
synchronized (bufferMonitor) {
- final Image oldImage = bufferImage;
- bufferImage = newImage;
- return oldImage;
+ visibleIndex = (visibleIndex + 1) % 2;
}
}
+ private static class RenderBuffer {
+ public final Image image;
+ public final GC gc;
+ public final SwtGraphics graphics;
+
+ public RenderBuffer(final Device device, final int width, final int height, final Map<URL, SwtImage> imageCache) {
+ image = new Image(device, Math.max(1, width), Math.max(1, height));
+ gc = new GC(image);
+ graphics = new SwtGraphics(gc, imageCache);
+ }
+
+ public void dispose() {
+ graphics.dispose();
+ gc.dispose();
+ image.dispose();
+ }
+ }
}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtGraphics.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtGraphics.java
index 5d504bc..d219441 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtGraphics.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtGraphics.java
@@ -17,6 +17,7 @@
import java.net.URL;
import java.text.MessageFormat;
import java.util.HashMap;
+import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
@@ -45,11 +46,14 @@
public class SwtGraphics implements Graphics {
private final GC gc;
+ private final Map<URL, SwtImage> imageCache;
+
private int offsetX;
private int offsetY;
private final HashMap<FontSpec, FontResource> fonts = new HashMap<FontSpec, FontResource>();
private final HashMap<Color, ColorResource> colors = new HashMap<Color, ColorResource>();
+ private final HashMap<URL, org.eclipse.swt.graphics.Image> images = new HashMap<URL, org.eclipse.swt.graphics.Image>();
private SwtFont currentFont;
private SwtFontMetrics currentFontMetrics;
@@ -59,8 +63,19 @@
* @param gc
* SWT GC to which we are drawing.
*/
+ @Deprecated
public SwtGraphics(final GC gc) {
+ this(gc, new HashMap<URL, SwtImage>());
+ }
+
+ /**
+ * @param gc
+ * SWT GC to which we are drawing.
+ */
+ public SwtGraphics(final GC gc, final Map<URL, SwtImage> imageCache) {
this.gc = gc;
+ this.imageCache = imageCache;
+
currentFont = new SwtFont(gc.getFont());
}
@@ -74,12 +89,22 @@
color.dispose();
}
colors.clear();
+ for (final org.eclipse.swt.graphics.Image image : images.values()) {
+ image.dispose();
+ }
+ images.clear();
// TODO should not dispose something that comes from outside!
gc.dispose();
}
@Override
+ public void resetOrigin() {
+ offsetX = 0;
+ offsetY = 0;
+ }
+
+ @Override
public void moveOrigin(final int offsetX, final int offsetY) {
this.offsetX += offsetX;
this.offsetY += offsetY;
@@ -155,12 +180,18 @@
@Override
public void drawImage(final Image image, final int x, final int y, final int width, final int height) {
Assert.isTrue(image instanceof SwtImage);
- final org.eclipse.swt.graphics.Image swtImage = new org.eclipse.swt.graphics.Image(gc.getDevice(), ((SwtImage) image).imageData);
- try {
- gc.drawImage(swtImage, 0, 0, image.getWidth(), image.getHeight(), x + offsetX, y + offsetY, width, height);
- } finally {
- swtImage.dispose();
+ final org.eclipse.swt.graphics.Image swtImage = toSWT((SwtImage) image);
+ gc.drawImage(swtImage, 0, 0, image.getWidth(), image.getHeight(), x + offsetX, y + offsetY, width, height);
+ }
+
+ private org.eclipse.swt.graphics.Image toSWT(final SwtImage image) {
+ final org.eclipse.swt.graphics.Image cachedImage = images.get(image.url);
+ if (cachedImage != null) {
+ return cachedImage;
}
+ final org.eclipse.swt.graphics.Image newImage = new org.eclipse.swt.graphics.Image(gc.getDevice(), image.imageData);
+ images.put(image.url, newImage);
+ return newImage;
}
/**
@@ -232,11 +263,19 @@
@Override
public Image getImage(final URL url) {
+ final SwtImage cachedImage = imageCache.get(url);
+ if (cachedImage != null) {
+ return cachedImage;
+ }
+
final ImageData[] imageData = loadImageData(url);
if (imageData != null && imageData.length > 0) {
- return new SwtImage(imageData[0]);
+ final SwtImage loadedImage = new SwtImage(url, imageData[0]);
+ imageCache.put(url, loadedImage);
+ return loadedImage;
}
- return new SwtImage(Display.getDefault().getSystemImage(SWT.ICON_ERROR).getImageData());
+
+ return new SwtImage(url, Display.getDefault().getSystemImage(SWT.ICON_ERROR).getImageData());
}
private static ImageData[] loadImageData(final URL url) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtImage.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtImage.java
index 6e9edc4..2987b04 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtImage.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtImage.java
@@ -1,36 +1,40 @@
-/*******************************************************************************
- * Copyright (c) 2010 Florian Thienel and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Florian Thienel - initial API and implementation
- *******************************************************************************/
-package org.eclipse.vex.core.internal.widget.swt;
-
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.vex.core.internal.core.Image;
-
-/**
- * @author Florian Thienel
- */
-public class SwtImage implements Image {
-
- public final ImageData imageData;
-
- public SwtImage(final ImageData imageData) {
- this.imageData = imageData;
- }
-
- @Override
- public int getHeight() {
- return imageData.height;
- }
-
- @Override
- public int getWidth() {
- return imageData.width;
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2010, 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.widget.swt;
+
+import java.net.URL;
+
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.vex.core.internal.core.Image;
+
+/**
+ * @author Florian Thienel
+ */
+public class SwtImage implements Image {
+
+ public final URL url;
+ public final ImageData imageData;
+
+ public SwtImage(final URL url, final ImageData imageData) {
+ this.url = url;
+ this.imageData = imageData;
+ }
+
+ @Override
+ public int getHeight() {
+ return imageData.height;
+ }
+
+ @Override
+ public int getWidth() {
+ return imageData.width;
+ }
+}