This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 430647fbf0abbc88767486f804c8e88e6bdf7f2e Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Jan 13 19:11:24 2021 +0100 Prepare an image slightly larger than viewport area for more continuous translations (pans). --- .../apache/sis/gui/coverage/CoverageCanvas.java | 12 +++- .../org/apache/sis/gui/coverage/RenderingData.java | 2 +- .../java/org/apache/sis/gui/map/MapCanvas.java | 4 +- .../java/org/apache/sis/gui/map/MapCanvasAWT.java | 77 +++++++++++++++++++--- 4 files changed, 81 insertions(+), 14 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java index 358ded5..8210001 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java @@ -40,6 +40,7 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.application.Platform; import javafx.concurrent.Task; +import javafx.geometry.Insets; import javax.measure.Quantity; import javax.measure.quantity.Length; import org.opengis.geometry.Envelope; @@ -528,9 +529,9 @@ public class CoverageCanvas extends MapCanvasAWT { private final LinearTransform objectiveToDisplay; /** - * Value of {@link CoverageCanvas#getDisplayBounds()} at the time this worker has been initialized. - * This is the size and location of the display device, in pixel units. - * This value is usually constant when the widget is not resized. + * Value of {@link CoverageCanvas#getDisplayBounds()} at the time this worker has been initialized, + * expanded by {@link CoverageCanvas#imageMargin}. This is the size and location of the display device + * in pixel units, plus the margin. This value is usually constant when the widget is not resized. */ private final Envelope2D displayBounds; @@ -594,6 +595,11 @@ public class CoverageCanvas extends MapCanvasAWT { if (data.validateCRS(objectiveCRS)) { resampledImage = canvas.resampledImage; } + final Insets margin = canvas.imageMargin.get(); + displayBounds.x -= margin.getLeft(); + displayBounds.width += margin.getLeft() + margin.getRight(); + displayBounds.y -= margin.getTop(); + displayBounds.height += margin.getTop() + margin.getBottom(); if (canvas.isolines != null) { isolines = canvas.isolines.prepare(); } diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java index 9eb2252..9ccd224 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/RenderingData.java @@ -461,7 +461,7 @@ final class RenderingData implements Cloneable { * * @param resampledImage the image computed by {@link #resampleAndConvert resampleAndConvert(…)}. * @param resampledToDisplay the transform computed by {@link #getTransform(LinearTransform)}. - * @param displayBounds size and location of the display device, in pixel units. + * @param displayBounds size and location of the display device (plus margin), in pixel units. * @return a temporary image with tiles intersecting the display region already computed. */ final RenderedImage prefetch(final RenderedImage resampledImage, final AffineTransform resampledToDisplay, diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java index e1e41c7..a33b22f 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java @@ -530,7 +530,7 @@ public abstract class MapCanvas extends PlanarCanvas { * the geographic location where the click occurred. This information is used for changing the projection * while preserving approximately the location, scale and rotation of pixels around the mouse cursor. */ - @SuppressWarnings("serial") // Not intended to be serialized. + @SuppressWarnings({"serial","CloneableImplementsClone"}) // Not intended to be serialized. final class MenuHandler extends DirectPosition2D implements EventHandler<MouseEvent>, ChangeListener<ReferenceSystem>, PropertyChangeListener { @@ -781,7 +781,7 @@ public abstract class MapCanvas extends PlanarCanvas { * <p>This method is invoked after {@link #createRenderer()} * and before {@link #createWorker(Renderer)}.</p> */ - final boolean initialize(final Pane view) { + private boolean initialize(final Pane view) { width = Numerics.clamp(Math.round(view.getWidth())); height = Numerics.clamp(Math.round(view.getHeight())); return width > 0 && height > 0; diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java index 3cf9003..13f73ed 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvasAWT.java @@ -27,13 +27,16 @@ import java.awt.image.RenderedImage; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import javafx.application.Platform; +import javafx.geometry.Insets; import javafx.geometry.Rectangle2D; import javafx.scene.image.ImageView; import javafx.scene.image.PixelBuffer; import javafx.scene.image.PixelFormat; import javafx.scene.image.WritableImage; +import javafx.beans.property.ObjectProperty; import javafx.concurrent.Task; import javafx.util.Callback; +import org.apache.sis.internal.gui.NonNullObjectProperty; import org.apache.sis.internal.coverage.j2d.ColorModelFactory; @@ -60,6 +63,14 @@ public abstract class MapCanvasAWT extends MapCanvas { private static final boolean NATIVE_ACCELERATION = false; /** + * Number of additional pixels to paint on each sides of the image, outside the viewing area. + * Computing a larger image reduces the black borders that user sees during translations or + * during zoom out before the new image is repainted. + * This property value can not be null but can be {@link Insets#EMPTY}. + */ + public final ObjectProperty<Insets> imageMargin; + + /** * A buffer where to draw the content of the map for the region to be displayed. * This buffer uses ARGB color model, contrarily to the {@link RenderedImage} of * {@link org.apache.sis.coverage.grid.GridCoverage} which may have any color model. @@ -118,6 +129,7 @@ public abstract class MapCanvasAWT extends MapCanvas { */ public MapCanvasAWT(final Locale locale) { super(locale); + imageMargin = new NonNullObjectProperty<>(this, "imageMargin", new Insets(128)); image = new ImageView(); image.setPreserveRatio(true); floatingPane.getChildren().add(image); @@ -170,6 +182,18 @@ public abstract class MapCanvasAWT extends MapCanvas { */ protected abstract static class Renderer extends MapCanvas.Renderer { /** + * Values of the {@link MapCanvasAWT#imageMargin} property at construction time. + * Those values are initialized by {@link #isValid(Insets, BufferedImage)}. + */ + private int left, top; + + /** + * Image width and height, taking in account the margins. + * Those values are initialized by {@link #isValid(Insets, BufferedImage)}. + */ + private int width, height; + + /** * Creates a new renderer. The {@linkplain #getWidth() width} and {@linkplain #getHeight() height} * are initially zero; they will get a non-zero values before {@link #paint(Graphics2D)} is invoked. */ @@ -177,15 +201,48 @@ public abstract class MapCanvasAWT extends MapCanvas { } /** + * Rounds and clamp the given value. The upper limit is arbitrary. + */ + private static int clamp(final double value) { + return (int) Math.max(0, Math.min(Short.MAX_VALUE, Math.round(value))); + } + + /** * Returns whether the given buffer is non-null and has the expected size. * This verification shall be done only after {@link #initialize(Pane)} has been invoked. * + * @param margin value of {@link #imageMargin}. * @param buffer value of {@link #buffer}. */ - final boolean isValid(final BufferedImage buffer) { + private boolean isValid(final Insets margin, final BufferedImage buffer) { + final int right, bottom; + top = clamp(margin.getTop()); + right = clamp(margin.getRight()); + bottom = clamp(margin.getBottom()); + left = clamp(margin.getLeft()); + width = Math.addExact(getWidth(), left + right); + height = Math.addExact(getHeight(), top + bottom); return (buffer != null) - && buffer.getWidth() == super.getWidth() - && buffer.getHeight() == super.getHeight(); + && buffer.getWidth() == width + && buffer.getHeight() == height; + } + + /** + * Applies translation on the given graphics before {@link #paint(Graphics2D)}. + */ + private void translate(final Graphics2D gr) { + gr.translate(left, top); + } + + /** + * Compensates the translation applied by {@link #translate(Graphics2D)}. + * This method is invoked only if the image painting has been successful, + * otherwise we assume that old content is still present and require the + * old translations. + */ + private void translate(final ImageView image) { + image.setTranslateX(-left); + image.setTranslateY(-top); } /** @@ -256,7 +313,7 @@ public abstract class MapCanvasAWT extends MapCanvas { final Task<?> createWorker(final MapCanvas.Renderer mc) { assert Platform.isFxApplicationThread(); final Renderer context = (Renderer) mc; - if (!context.isValid(buffer)) { + if (!context.isValid(imageMargin.get(), buffer)) { clearBuffer(); return new Creator(context); } else { @@ -309,12 +366,13 @@ public abstract class MapCanvasAWT extends MapCanvas { @Override protected WritableImage call() throws Exception { renderer.render(); - final int width = renderer.getWidth(); - final int height = renderer.getHeight(); + final int width = renderer.width; + final int height = renderer.height; drawTo = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); final Graphics2D gr = drawTo.createGraphics(); try { configuration = gr.getDeviceConfiguration(); + renderer.translate(gr); renderer.paint(gr); } finally { gr.dispose(); @@ -343,6 +401,7 @@ public abstract class MapCanvasAWT extends MapCanvas { @Override protected void succeeded() { image.setImage(getValue()); + renderer.translate(image); buffer = drawTo; bufferWrapper = wrapper; bufferConfiguration = configuration; @@ -404,8 +463,8 @@ public abstract class MapCanvasAWT extends MapCanvas { @Override protected VolatileImage call() throws Exception { renderer.render(); - final int width = renderer.getWidth(); - final int height = renderer.getHeight(); + final int width = renderer.width; + final int height = renderer.height; VolatileImage drawTo = previousBuffer; previousBuffer = null; // For letting GC do its work. if (drawTo == null) { @@ -421,6 +480,7 @@ public abstract class MapCanvasAWT extends MapCanvas { try { gr.setBackground(ColorModelFactory.TRANSPARENT); gr.clearRect(0, 0, drawTo.getWidth(), drawTo.getHeight()); + renderer.translate(gr); renderer.paint(gr); } finally { gr.dispose(); @@ -471,6 +531,7 @@ public abstract class MapCanvasAWT extends MapCanvas { } finally { drawTo.flush(); // Release native resources. } + renderer.translate(image); final boolean done = renderer.commit(MapCanvasAWT.this); renderingCompleted(this); if (!done || contentsLost || contentsChanged()) {
