• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
ImageOverlay(LayoutCanvas canvas, CanvasTransform hScale, CanvasTransform vScale)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
create(Device device)84     public void create(Device device) {
85         super.create(device);
86     }
87 
88     @Override
dispose()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      */
setImage(BufferedImage awtImage, boolean isAlphaChannelImage)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      */
getImage()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
getAwtImage()159     BufferedImage getAwtImage() {
160         return mAwtImage;
161     }
162 
163     @Override
paint(GC gc)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      */
gc_setAlpha(GC gc, int alpha)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      */
gc_setAntialias(GC gc, int alias)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          */
SwtReadyBufferedImage(int width, int height, ImageData imageData, Device device)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          */
getSwtImage()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          */
createImage(int w, int h, Device device)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
getImage(int w, int h)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      */
getImageBounds()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