Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
     18 
     19 import com.android.annotations.Nullable;
     20 import com.android.ide.common.api.Rect;
     21 import com.android.ide.common.rendering.api.IImageFactory;
     22 import com.android.sdklib.SdkConstants;
     23 
     24 import org.eclipse.swt.SWT;
     25 import org.eclipse.swt.SWTException;
     26 import org.eclipse.swt.graphics.Device;
     27 import org.eclipse.swt.graphics.GC;
     28 import org.eclipse.swt.graphics.Image;
     29 import org.eclipse.swt.graphics.ImageData;
     30 import org.eclipse.swt.graphics.PaletteData;
     31 
     32 import java.awt.image.BufferedImage;
     33 import java.awt.image.DataBufferInt;
     34 import java.awt.image.WritableRaster;
     35 
     36 /**
     37  * The {@link ImageOverlay} class renders an image as an overlay.
     38  */
     39 public class ImageOverlay extends Overlay implements IImageFactory {
     40     /**
     41      * Whether the image should be pre-scaled (scaled to the zoom level) once
     42      * instead of dynamically during each paint; this is necessary on some
     43      * platforms (see issue #19447)
     44      */
     45     private static final boolean PRESCALE =
     46             // Currently this is necessary on Linux because the "Cairo" library
     47             // seems to be a bottleneck
     48             SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX
     49                     && !(Boolean.getBoolean("adt.noprescale")); //$NON-NLS-1$
     50 
     51     /** Current background image. Null when there's no image. */
     52     private Image mImage;
     53 
     54     /** A pre-scaled version of the image */
     55     private Image mPreScaledImage;
     56 
     57     /** Current background AWT image. This is created by {@link #getImage()}, which is called
     58      * by the LayoutLib. */
     59     private BufferedImage mAwtImage;
     60 
     61     /** The associated {@link LayoutCanvas}. */
     62     private LayoutCanvas mCanvas;
     63 
     64     /** Vertical scaling & scrollbar information. */
     65     private CanvasTransform mVScale;
     66 
     67     /** Horizontal scaling & scrollbar information. */
     68     private CanvasTransform mHScale;
     69 
     70     /**
     71      * Constructs an {@link ImageOverlay} tied to the given canvas.
     72      *
     73      * @param canvas The {@link LayoutCanvas} to paint the overlay over.
     74      * @param hScale The horizontal scale information.
     75      * @param vScale The vertical scale information.
     76      */
     77     public ImageOverlay(LayoutCanvas canvas, CanvasTransform hScale, CanvasTransform vScale) {
     78         this.mCanvas = canvas;
     79         this.mHScale = hScale;
     80         this.mVScale = vScale;
     81     }
     82 
     83     @Override
     84     public void create(Device device) {
     85         super.create(device);
     86     }
     87 
     88     @Override
     89     public void dispose() {
     90         if (mImage != null) {
     91             mImage.dispose();
     92             mImage = null;
     93         }
     94         if (mPreScaledImage != null) {
     95             mPreScaledImage.dispose();
     96             mPreScaledImage = null;
     97         }
     98     }
     99 
    100     /**
    101      * Sets the image to be drawn as an overlay from the passed in AWT
    102      * {@link BufferedImage} (which will be converted to an SWT image).
    103      * <p/>
    104      * The image <b>can</b> be null, which is the case when we are dealing with
    105      * an empty document.
    106      *
    107      * @param awtImage The AWT image to be rendered as an SWT image.
    108      * @param isAlphaChannelImage whether the alpha channel of the image is relevant
    109      * @return The corresponding SWT image, or null.
    110      */
    111     public synchronized Image setImage(BufferedImage awtImage, boolean isAlphaChannelImage) {
    112         if (awtImage != mAwtImage || awtImage == null) {
    113             mAwtImage = null;
    114 
    115             if (mImage != null) {
    116                 mImage.dispose();
    117             }
    118 
    119             if (awtImage == null) {
    120                 mImage = null;
    121             } else {
    122                 mImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), awtImage,
    123                         isAlphaChannelImage, -1);
    124             }
    125         } else {
    126             assert awtImage instanceof SwtReadyBufferedImage;
    127 
    128             if (isAlphaChannelImage) {
    129                 mImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), awtImage, true, -1);
    130             } else {
    131                 mImage = ((SwtReadyBufferedImage)awtImage).getSwtImage();
    132             }
    133         }
    134 
    135         if (mPreScaledImage != null) {
    136             // Force refresh on next paint
    137             mPreScaledImage.dispose();
    138             mPreScaledImage = null;
    139         }
    140 
    141         return mImage;
    142     }
    143 
    144     /**
    145      * Returns the currently painted image, or null if none has been set
    146      *
    147      * @return the currently painted image or null
    148      */
    149     public Image getImage() {
    150         return mImage;
    151     }
    152 
    153     /**
    154      * Returns the currently rendered image, or null if none has been set
    155      *
    156      * @return the currently rendered image or null
    157      */
    158     @Nullable
    159     BufferedImage getAwtImage() {
    160         return mAwtImage;
    161     }
    162 
    163     @Override
    164     public synchronized void paint(GC gc) {
    165         if (mImage != null) {
    166             boolean valid = mCanvas.getViewHierarchy().isValid();
    167             if (!valid) {
    168                 gc_setAlpha(gc, 128); // half-transparent
    169             }
    170 
    171             CanvasTransform hi = mHScale;
    172             CanvasTransform vi = mVScale;
    173 
    174             // On some platforms, dynamic image scaling is very slow (see issue #19447) so
    175             // compute a pre-scaled version of the image once and render that instead.
    176             // This is done lazily in paint rather than when the image changes because
    177             // the image must be rescaled each time the zoom level changes, which varies
    178             // independently from when the image changes.
    179             if (PRESCALE && mAwtImage != null) {
    180                 if (mPreScaledImage == null ||
    181                         mPreScaledImage.getImageData().width != hi.getScalledImgSize()) {
    182                     double xScale = hi.getScalledImgSize() / (double) mAwtImage.getWidth();
    183                     double yScale = vi.getScalledImgSize() / (double) mAwtImage.getHeight();
    184                     BufferedImage scaledAwtImage;
    185 
    186                     // NOTE: == comparison on floating point numbers is okay
    187                     // here because we normalize the scaling factor
    188                     // to an exact 1.0 in the zooming code when the value gets
    189                     // near 1.0 to make painting more efficient in the presence
    190                     // of rounding errors.
    191                     if (xScale == 1.0 && yScale == 1.0) {
    192                         // Scaling to 100% is easy!
    193                         scaledAwtImage = mAwtImage;
    194                     } else {
    195                         scaledAwtImage = ImageUtils.scale(mAwtImage, xScale, yScale);
    196                     }
    197                     assert scaledAwtImage.getWidth() == hi.getScalledImgSize();
    198                     mPreScaledImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), scaledAwtImage,
    199                             true /*transferAlpha*/, -1);
    200                 }
    201 
    202                 if (mPreScaledImage != null) {
    203                     gc.drawImage(mPreScaledImage, hi.translate(0), vi.translate(0));
    204                 }
    205                 return;
    206             }
    207 
    208             // we only anti-alias when reducing the image size.
    209             int oldAlias = -2;
    210             if (hi.getScale() < 1.0) {
    211                 oldAlias = gc_setAntialias(gc, SWT.ON);
    212             }
    213 
    214             gc.drawImage(
    215                     mImage,
    216                     0,                      // srcX
    217                     0,                      // srcY
    218                     hi.getImgSize(),        // srcWidth
    219                     vi.getImgSize(),        // srcHeight
    220                     hi.translate(0),        // destX
    221                     vi.translate(0),        // destY
    222                     hi.getScalledImgSize(), // destWidth
    223                     vi.getScalledImgSize());  // destHeight
    224 
    225             if (oldAlias != -2) {
    226                 gc_setAntialias(gc, oldAlias);
    227             }
    228 
    229             if (!valid) {
    230                 gc_setAlpha(gc, 255); // opaque
    231             }
    232         }
    233     }
    234 
    235     /**
    236      * Sets the alpha for the given GC.
    237      * <p/>
    238      * Alpha may not work on all platforms and may fail with an exception, which
    239      * is hidden here (false is returned in that case).
    240      *
    241      * @param gc the GC to change
    242      * @param alpha the new alpha, 0 for transparent, 255 for opaque.
    243      * @return True if the operation worked, false if it failed with an
    244      *         exception.
    245      * @see GC#setAlpha(int)
    246      */
    247     private boolean gc_setAlpha(GC gc, int alpha) {
    248         try {
    249             gc.setAlpha(alpha);
    250             return true;
    251         } catch (SWTException e) {
    252             return false;
    253         }
    254     }
    255 
    256     /**
    257      * Sets the non-text antialias flag for the given GC.
    258      * <p/>
    259      * Antialias may not work on all platforms and may fail with an exception,
    260      * which is hidden here (-2 is returned in that case).
    261      *
    262      * @param gc the GC to change
    263      * @param alias One of {@link SWT#DEFAULT}, {@link SWT#ON}, {@link SWT#OFF}.
    264      * @return The previous aliasing mode if the operation worked, or -2 if it
    265      *         failed with an exception.
    266      * @see GC#setAntialias(int)
    267      */
    268     private int gc_setAntialias(GC gc, int alias) {
    269         try {
    270             int old = gc.getAntialias();
    271             gc.setAntialias(alias);
    272             return old;
    273         } catch (SWTException e) {
    274             return -2;
    275         }
    276     }
    277 
    278     /**
    279      * Custom {@link BufferedImage} class able to convert itself into an SWT {@link Image}
    280      * efficiently.
    281      *
    282      * The BufferedImage also contains an instance of {@link ImageData} that's kept around
    283      * and used to create new SWT {@link Image} objects in {@link #getSwtImage()}.
    284      *
    285      */
    286     private static final class SwtReadyBufferedImage extends BufferedImage {
    287 
    288         private final ImageData mImageData;
    289         private final Device mDevice;
    290 
    291         /**
    292          * Creates the image with a given model, raster and SWT {@link ImageData}
    293          * @param model the color model
    294          * @param raster the image raster
    295          * @param imageData the SWT image data.
    296          * @param device the {@link Device} in which the SWT image will be painted.
    297          */
    298         private SwtReadyBufferedImage(int width, int height, ImageData imageData, Device device) {
    299             super(width, height, BufferedImage.TYPE_INT_ARGB);
    300             mImageData = imageData;
    301             mDevice = device;
    302         }
    303 
    304         /**
    305          * Returns a new {@link Image} object initialized with the content of the BufferedImage.
    306          * @return the image object.
    307          */
    308         private Image getSwtImage() {
    309             // transfer the content of the bufferedImage into the image data.
    310             WritableRaster raster = getRaster();
    311             int[] imageDataBuffer = ((DataBufferInt) raster.getDataBuffer()).getData();
    312 
    313             mImageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
    314 
    315             return new Image(mDevice, mImageData);
    316         }
    317 
    318         /**
    319          * Creates a new {@link SwtReadyBufferedImage}.
    320          * @param w the width of the image
    321          * @param h the height of the image
    322          * @param device the device in which the SWT image will be painted
    323          * @return a new {@link SwtReadyBufferedImage} object
    324          */
    325         private static SwtReadyBufferedImage createImage(int w, int h, Device device) {
    326             ImageData imageData = new ImageData(w, h, 32,
    327                     new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF));
    328 
    329             SwtReadyBufferedImage swtReadyImage = new SwtReadyBufferedImage(w, h,
    330                     imageData, device);
    331 
    332             return swtReadyImage;
    333         }
    334     }
    335 
    336     /**
    337      * Implementation of {@link IImageFactory#getImage(int, int)}.
    338      */
    339     @Override
    340     public BufferedImage getImage(int w, int h) {
    341         if (mAwtImage == null ||
    342                 mAwtImage.getWidth() != w ||
    343                 mAwtImage.getHeight() != h) {
    344 
    345             mAwtImage = SwtReadyBufferedImage.createImage(w, h, getDevice());
    346         }
    347 
    348         return mAwtImage;
    349     }
    350 
    351     /**
    352      * Returns the bounds of the current image, or null
    353      *
    354      * @return the bounds of the current image, or null
    355      */
    356     public Rect getImageBounds() {
    357         if (mImage == null) {
    358             return null;
    359         }
    360 
    361         return new Rect(0, 0, mImage.getImageData().width, mImage.getImageData().height);
    362     }
    363 }
    364