aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java447
1 files changed, 447 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
new file mode 100644
index 000000000..a1363ecb1
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE;
+
+import com.android.SdkConstants;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.Rect;
+import com.android.ide.common.rendering.api.IImageFactory;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.PaletteData;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.awt.image.WritableRaster;
+import java.lang.ref.SoftReference;
+
+/**
+ * The {@link ImageOverlay} class renders an image as an overlay.
+ */
+public class ImageOverlay extends Overlay implements IImageFactory {
+ /**
+ * Whether the image should be pre-scaled (scaled to the zoom level) once
+ * instead of dynamically during each paint; this is necessary on some
+ * platforms (see issue #19447)
+ */
+ private static final boolean PRESCALE =
+ // Currently this is necessary on Linux because the "Cairo" library
+ // seems to be a bottleneck
+ SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX
+ && !(Boolean.getBoolean("adt.noprescale")); //$NON-NLS-1$
+
+ /** Current background image. Null when there's no image. */
+ private Image mImage;
+
+ /** A pre-scaled version of the image */
+ private Image mPreScaledImage;
+
+ /** Whether the rendered image should have a drop shadow */
+ private boolean mShowDropShadow;
+
+ /** Current background AWT image. This is created by {@link #getImage()}, which is called
+ * by the LayoutLib. */
+ private SoftReference<BufferedImage> mAwtImage = new SoftReference<BufferedImage>(null);
+
+ /**
+ * Strong reference to the image in the above soft reference, to prevent
+ * garbage collection when {@link PRESCALE} is set, until the scaled image
+ * is created (lazily as part of the next paint call, where this strong
+ * reference is nulled out and the above soft reference becomes eligible to
+ * be reclaimed when memory is low.)
+ */
+ @SuppressWarnings("unused") // Used by the garbage collector to keep mAwtImage non-soft
+ private BufferedImage mAwtImageStrongRef;
+
+ /** The associated {@link LayoutCanvas}. */
+ private LayoutCanvas mCanvas;
+
+ /** Vertical scaling & scrollbar information. */
+ private CanvasTransform mVScale;
+
+ /** Horizontal scaling & scrollbar information. */
+ private CanvasTransform mHScale;
+
+ /**
+ * Constructs an {@link ImageOverlay} tied to the given canvas.
+ *
+ * @param canvas The {@link LayoutCanvas} to paint the overlay over.
+ * @param hScale The horizontal scale information.
+ * @param vScale The vertical scale information.
+ */
+ public ImageOverlay(LayoutCanvas canvas, CanvasTransform hScale, CanvasTransform vScale) {
+ mCanvas = canvas;
+ mHScale = hScale;
+ mVScale = vScale;
+ }
+
+ @Override
+ public void create(Device device) {
+ super.create(device);
+ }
+
+ @Override
+ public void dispose() {
+ if (mImage != null) {
+ mImage.dispose();
+ mImage = null;
+ }
+ if (mPreScaledImage != null) {
+ mPreScaledImage.dispose();
+ mPreScaledImage = null;
+ }
+ }
+
+ /**
+ * Sets the image to be drawn as an overlay from the passed in AWT
+ * {@link BufferedImage} (which will be converted to an SWT image).
+ * <p/>
+ * The image <b>can</b> be null, which is the case when we are dealing with
+ * an empty document.
+ *
+ * @param awtImage The AWT image to be rendered as an SWT image.
+ * @param isAlphaChannelImage whether the alpha channel of the image is relevant
+ * @return The corresponding SWT image, or null.
+ */
+ public synchronized Image setImage(BufferedImage awtImage, boolean isAlphaChannelImage) {
+ mShowDropShadow = !isAlphaChannelImage;
+
+ BufferedImage oldAwtImage = mAwtImage.get();
+ if (awtImage != oldAwtImage || awtImage == null) {
+ mAwtImage.clear();
+ mAwtImageStrongRef = null;
+
+ if (mImage != null) {
+ mImage.dispose();
+ }
+
+ if (awtImage == null) {
+ mImage = null;
+ } else {
+ mImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), awtImage,
+ isAlphaChannelImage, -1);
+ }
+ } else {
+ assert awtImage instanceof SwtReadyBufferedImage;
+
+ if (isAlphaChannelImage) {
+ if (mImage != null) {
+ mImage.dispose();
+ }
+
+ mImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), awtImage, true, -1);
+ } else {
+ Image prev = mImage;
+ mImage = ((SwtReadyBufferedImage)awtImage).getSwtImage();
+ if (prev != mImage && prev != null) {
+ prev.dispose();
+ }
+ }
+ }
+
+ if (mPreScaledImage != null) {
+ // Force refresh on next paint
+ mPreScaledImage.dispose();
+ mPreScaledImage = null;
+ }
+
+ return mImage;
+ }
+
+ /**
+ * Returns the currently painted image, or null if none has been set
+ *
+ * @return the currently painted image or null
+ */
+ public Image getImage() {
+ return mImage;
+ }
+
+ /**
+ * Returns the currently rendered image, or null if none has been set
+ *
+ * @return the currently rendered image or null
+ */
+ @Nullable
+ BufferedImage getAwtImage() {
+ BufferedImage awtImage = mAwtImage.get();
+ if (awtImage == null && mImage != null) {
+ awtImage = SwtUtils.convertToAwt(mImage);
+ }
+
+ return awtImage;
+ }
+
+ /**
+ * Returns whether this image overlay should be painted with a drop shadow.
+ * This is usually the case, but not for transparent themes like the dialog
+ * theme (Theme.*Dialog), which already provides its own shadow.
+ *
+ * @return true if the image overlay should be shown with a drop shadow.
+ */
+ public boolean getShowDropShadow() {
+ return mShowDropShadow;
+ }
+
+ @Override
+ public synchronized void paint(GC gc) {
+ if (mImage != null) {
+ boolean valid = mCanvas.getViewHierarchy().isValid();
+ mCanvas.ensureZoomed();
+ if (!valid) {
+ gc_setAlpha(gc, 128); // half-transparent
+ }
+
+ CanvasTransform hi = mHScale;
+ CanvasTransform vi = mVScale;
+
+ // On some platforms, dynamic image scaling is very slow (see issue #19447) so
+ // compute a pre-scaled version of the image once and render that instead.
+ // This is done lazily in paint rather than when the image changes because
+ // the image must be rescaled each time the zoom level changes, which varies
+ // independently from when the image changes.
+ BufferedImage awtImage = mAwtImage.get();
+ if (PRESCALE && awtImage != null) {
+ int imageWidth = (mPreScaledImage == null) ? 0
+ : mPreScaledImage.getImageData().width
+ - (mShowDropShadow ? SHADOW_SIZE : 0);
+ if (mPreScaledImage == null || imageWidth != hi.getScaledImgSize()) {
+ double xScale = hi.getScaledImgSize() / (double) awtImage.getWidth();
+ double yScale = vi.getScaledImgSize() / (double) awtImage.getHeight();
+ BufferedImage scaledAwtImage;
+
+ // NOTE: == comparison on floating point numbers is okay
+ // here because we normalize the scaling factor
+ // to an exact 1.0 in the zooming code when the value gets
+ // near 1.0 to make painting more efficient in the presence
+ // of rounding errors.
+ if (xScale == 1.0 && yScale == 1.0) {
+ // Scaling to 100% is easy!
+ scaledAwtImage = awtImage;
+
+ if (mShowDropShadow) {
+ // Just need to draw drop shadows
+ scaledAwtImage = ImageUtils.createRectangularDropShadow(awtImage);
+ }
+ } else {
+ if (mShowDropShadow) {
+ scaledAwtImage = ImageUtils.scale(awtImage, xScale, yScale,
+ SHADOW_SIZE, SHADOW_SIZE);
+ ImageUtils.drawRectangleShadow(scaledAwtImage, 0, 0,
+ scaledAwtImage.getWidth() - SHADOW_SIZE,
+ scaledAwtImage.getHeight() - SHADOW_SIZE);
+ } else {
+ scaledAwtImage = ImageUtils.scale(awtImage, xScale, yScale);
+ }
+ }
+
+ if (mPreScaledImage != null && !mPreScaledImage.isDisposed()) {
+ mPreScaledImage.dispose();
+ }
+ mPreScaledImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), scaledAwtImage,
+ true /*transferAlpha*/, -1);
+ // We can't just clear the mAwtImageStrongRef here, because if the
+ // zooming factor changes, we may need to use it again
+ }
+
+ if (mPreScaledImage != null) {
+ gc.drawImage(mPreScaledImage, hi.translate(0), vi.translate(0));
+ }
+ return;
+ }
+
+ // we only anti-alias when reducing the image size.
+ int oldAlias = -2;
+ if (hi.getScale() < 1.0) {
+ oldAlias = gc_setAntialias(gc, SWT.ON);
+ }
+
+ int srcX = 0;
+ int srcY = 0;
+ int srcWidth = hi.getImgSize();
+ int srcHeight = vi.getImgSize();
+ int destX = hi.translate(0);
+ int destY = vi.translate(0);
+ int destWidth = hi.getScaledImgSize();
+ int destHeight = vi.getScaledImgSize();
+
+ gc.drawImage(mImage,
+ srcX, srcY, srcWidth, srcHeight,
+ destX, destY, destWidth, destHeight);
+
+ if (mShowDropShadow) {
+ SwtUtils.drawRectangleShadow(gc, destX, destY, destWidth, destHeight);
+ }
+
+ if (oldAlias != -2) {
+ gc_setAntialias(gc, oldAlias);
+ }
+
+ if (!valid) {
+ gc_setAlpha(gc, 255); // opaque
+ }
+ }
+ }
+
+ /**
+ * Sets the alpha for the given GC.
+ * <p/>
+ * Alpha may not work on all platforms and may fail with an exception, which
+ * is hidden here (false is returned in that case).
+ *
+ * @param gc the GC to change
+ * @param alpha the new alpha, 0 for transparent, 255 for opaque.
+ * @return True if the operation worked, false if it failed with an
+ * exception.
+ * @see GC#setAlpha(int)
+ */
+ private boolean gc_setAlpha(GC gc, int alpha) {
+ try {
+ gc.setAlpha(alpha);
+ return true;
+ } catch (SWTException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Sets the non-text antialias flag for the given GC.
+ * <p/>
+ * Antialias may not work on all platforms and may fail with an exception,
+ * which is hidden here (-2 is returned in that case).
+ *
+ * @param gc the GC to change
+ * @param alias One of {@link SWT#DEFAULT}, {@link SWT#ON}, {@link SWT#OFF}.
+ * @return The previous aliasing mode if the operation worked, or -2 if it
+ * failed with an exception.
+ * @see GC#setAntialias(int)
+ */
+ private int gc_setAntialias(GC gc, int alias) {
+ try {
+ int old = gc.getAntialias();
+ gc.setAntialias(alias);
+ return old;
+ } catch (SWTException e) {
+ return -2;
+ }
+ }
+
+ /**
+ * Custom {@link BufferedImage} class able to convert itself into an SWT {@link Image}
+ * efficiently.
+ *
+ * The BufferedImage also contains an instance of {@link ImageData} that's kept around
+ * and used to create new SWT {@link Image} objects in {@link #getSwtImage()}.
+ *
+ */
+ private static final class SwtReadyBufferedImage extends BufferedImage {
+
+ private final ImageData mImageData;
+ private final Device mDevice;
+
+ /**
+ * Creates the image with a given model, raster and SWT {@link ImageData}
+ * @param model the color model
+ * @param raster the image raster
+ * @param imageData the SWT image data.
+ * @param device the {@link Device} in which the SWT image will be painted.
+ */
+ private SwtReadyBufferedImage(int width, int height, ImageData imageData, Device device) {
+ super(width, height, BufferedImage.TYPE_INT_ARGB);
+ mImageData = imageData;
+ mDevice = device;
+ }
+
+ /**
+ * Returns a new {@link Image} object initialized with the content of the BufferedImage.
+ * @return the image object.
+ */
+ private Image getSwtImage() {
+ // transfer the content of the bufferedImage into the image data.
+ WritableRaster raster = getRaster();
+ int[] imageDataBuffer = ((DataBufferInt) raster.getDataBuffer()).getData();
+
+ mImageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
+
+ return new Image(mDevice, mImageData);
+ }
+
+ /**
+ * Creates a new {@link SwtReadyBufferedImage}.
+ * @param w the width of the image
+ * @param h the height of the image
+ * @param device the device in which the SWT image will be painted
+ * @return a new {@link SwtReadyBufferedImage} object
+ */
+ private static SwtReadyBufferedImage createImage(int w, int h, Device device) {
+ // NOTE: We can't make this image bigger to accommodate the drop shadow directly
+ // (such that we could paint one into the image after a layoutlib render)
+ // since this image is in the full resolution of the device, and gets scaled
+ // to fit in the layout editor. This would have the net effect of causing
+ // the drop shadow to get zoomed/scaled along with the scene, making a tiny
+ // drop shadow for tablet layouts, a huge drop shadow for tiny QVGA screens, etc.
+
+ ImageData imageData = new ImageData(w, h, 32,
+ new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF));
+
+ SwtReadyBufferedImage swtReadyImage = new SwtReadyBufferedImage(w, h,
+ imageData, device);
+
+ return swtReadyImage;
+ }
+ }
+
+ /**
+ * Implementation of {@link IImageFactory#getImage(int, int)}.
+ */
+ @Override
+ public BufferedImage getImage(int w, int h) {
+ BufferedImage awtImage = mAwtImage.get();
+ if (awtImage == null ||
+ awtImage.getWidth() != w ||
+ awtImage.getHeight() != h) {
+ mAwtImage.clear();
+ awtImage = SwtReadyBufferedImage.createImage(w, h, getDevice());
+ mAwtImage = new SoftReference<BufferedImage>(awtImage);
+ if (PRESCALE) {
+ mAwtImageStrongRef = awtImage;
+ }
+ }
+
+ return awtImage;
+ }
+
+ /**
+ * Returns the bounds of the current image, or null
+ *
+ * @return the bounds of the current image, or null
+ */
+ public Rect getImageBounds() {
+ if (mImage == null) {
+ return null;
+ }
+
+ return new Rect(0, 0, mImage.getImageData().width, mImage.getImageData().height);
+ }
+}