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