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 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
17 
18 import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE;
19 
20 import com.android.ide.common.api.Rect;
21 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
22 
23 import org.eclipse.swt.SWTException;
24 import org.eclipse.swt.graphics.Device;
25 import org.eclipse.swt.graphics.Font;
26 import org.eclipse.swt.graphics.FontMetrics;
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 import org.eclipse.swt.graphics.Rectangle;
32 import org.eclipse.swt.widgets.Control;
33 import org.eclipse.swt.widgets.Display;
34 
35 import java.awt.Graphics;
36 import java.awt.image.BufferedImage;
37 import java.awt.image.DataBuffer;
38 import java.awt.image.DataBufferByte;
39 import java.awt.image.DataBufferInt;
40 import java.awt.image.WritableRaster;
41 import java.util.List;
42 
43 /**
44  * Various generic SWT utilities such as image conversion.
45  */
46 public class SwtUtils {
47 
SwtUtils()48     private SwtUtils() {
49     }
50 
51     /**
52      * Returns the {@link PaletteData} describing the ARGB ordering expected from integers
53      * representing pixels for AWT {@link BufferedImage}.
54      *
55      * @param imageType the {@link BufferedImage#getType()} type
56      * @return A new {@link PaletteData} suitable for AWT images.
57      */
getAwtPaletteData(int imageType)58     public static PaletteData getAwtPaletteData(int imageType) {
59         switch (imageType) {
60             case BufferedImage.TYPE_INT_RGB:
61             case BufferedImage.TYPE_INT_ARGB:
62             case BufferedImage.TYPE_INT_ARGB_PRE:
63                 return new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF);
64 
65             case BufferedImage.TYPE_3BYTE_BGR:
66             case BufferedImage.TYPE_4BYTE_ABGR:
67             case BufferedImage.TYPE_4BYTE_ABGR_PRE:
68                 return new PaletteData(0x000000FF, 0x0000FF00, 0x00FF0000);
69 
70             default:
71                 throw new UnsupportedOperationException("RGB type not supported yet.");
72         }
73     }
74 
75     /**
76      * Returns true if the given type of {@link BufferedImage} is supported for
77      * conversion. For unsupported formats, use
78      * {@link #convertToCompatibleFormat(BufferedImage)} first.
79      *
80      * @param imageType the {@link BufferedImage#getType()}
81      * @return true if we can convert the given buffered image format
82      */
isSupportedPaletteType(int imageType)83     private static boolean isSupportedPaletteType(int imageType) {
84         switch (imageType) {
85             case BufferedImage.TYPE_INT_RGB:
86             case BufferedImage.TYPE_INT_ARGB:
87             case BufferedImage.TYPE_INT_ARGB_PRE:
88             case BufferedImage.TYPE_3BYTE_BGR:
89             case BufferedImage.TYPE_4BYTE_ABGR:
90             case BufferedImage.TYPE_4BYTE_ABGR_PRE:
91                 return true;
92             default:
93                 return false;
94         }
95     }
96 
97     /** Converts the given arbitrary {@link BufferedImage} to another {@link BufferedImage}
98      * in a format that is supported (see {@link #isSupportedPaletteType(int)})
99      *
100      * @param image the image to be converted
101      * @return a new image that is in a guaranteed compatible format
102      */
convertToCompatibleFormat(BufferedImage image)103     private static BufferedImage convertToCompatibleFormat(BufferedImage image) {
104         BufferedImage converted = new BufferedImage(image.getWidth(), image.getHeight(),
105                 BufferedImage.TYPE_INT_ARGB);
106         Graphics graphics = converted.getGraphics();
107         graphics.drawImage(image, 0, 0, null);
108         graphics.dispose();
109 
110         return converted;
111     }
112 
113     /**
114      * Converts an AWT {@link BufferedImage} into an equivalent SWT {@link Image}. Whether
115      * the transparency data is transferred is optional, and this method can also apply an
116      * alpha adjustment during the conversion.
117      * <p/>
118      * Implementation details: on Windows, the returned {@link Image} will have an ordering
119      * matching the Windows DIB (e.g. RGBA, not ARGB). Callers must make sure to use
120      * <code>Image.getImageData().paletteData</code> to get the right pixels out of the image.
121      *
122      * @param display The display where the SWT image will be shown
123      * @param awtImage The AWT {@link BufferedImage}
124      * @param transferAlpha If true, copy alpha data out of the source image
125      * @param globalAlpha If -1, do nothing, otherwise adjust the alpha of the final image
126      *            by the given amount in the range [0,255]
127      * @return A new SWT {@link Image} with the same contents as the source
128      *         {@link BufferedImage}
129      */
convertToSwt(Device display, BufferedImage awtImage, boolean transferAlpha, int globalAlpha)130     public static Image convertToSwt(Device display, BufferedImage awtImage,
131             boolean transferAlpha, int globalAlpha) {
132         if (!isSupportedPaletteType(awtImage.getType())) {
133             awtImage = convertToCompatibleFormat(awtImage);
134         }
135 
136         int width = awtImage.getWidth();
137         int height = awtImage.getHeight();
138 
139         WritableRaster raster = awtImage.getRaster();
140         DataBuffer dataBuffer = raster.getDataBuffer();
141         ImageData imageData =
142             new ImageData(width, height, 32, getAwtPaletteData(awtImage.getType()));
143 
144         if (dataBuffer instanceof DataBufferInt) {
145             int[] imageDataBuffer = ((DataBufferInt) dataBuffer).getData();
146             imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
147         } else if (dataBuffer instanceof DataBufferByte) {
148             byte[] imageDataBuffer = ((DataBufferByte) dataBuffer).getData();
149             try {
150                 imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0);
151             } catch (SWTException se) {
152                 // Unsupported depth
153                 return convertToSwt(display, convertToCompatibleFormat(awtImage),
154                         transferAlpha, globalAlpha);
155             }
156         }
157 
158         if (transferAlpha) {
159             byte[] alphaData = new byte[height * width];
160             for (int y = 0; y < height; y++) {
161                 byte[] alphaRow = new byte[width];
162                 for (int x = 0; x < width; x++) {
163                     int alpha = awtImage.getRGB(x, y) >>> 24;
164 
165                     // We have to multiply in the alpha now since if we
166                     // set ImageData.alpha, it will ignore the alphaData.
167                     if (globalAlpha != -1) {
168                         alpha = alpha * globalAlpha >> 8;
169                     }
170 
171                     alphaRow[x] = (byte) alpha;
172                 }
173                 System.arraycopy(alphaRow, 0, alphaData, y * width, width);
174             }
175 
176             imageData.alphaData = alphaData;
177         } else if (globalAlpha != -1) {
178             imageData.alpha = globalAlpha;
179         }
180 
181         return new Image(display, imageData);
182     }
183 
184     /**
185      * Converts a direct-color model SWT image to an equivalent AWT image. If the image
186      * does not have a supported color model, returns null. This method does <b>NOT</b>
187      * preserve alpha in the source image.
188      *
189      * @param swtImage the SWT image to be converted to AWT
190      * @return an AWT image representing the source SWT image
191      */
convertToAwt(Image swtImage)192     public static BufferedImage convertToAwt(Image swtImage) {
193         ImageData swtData = swtImage.getImageData();
194         BufferedImage awtImage =
195             new BufferedImage(swtData.width, swtData.height, BufferedImage.TYPE_INT_ARGB);
196         PaletteData swtPalette = swtData.palette;
197         if (swtPalette.isDirect) {
198             PaletteData awtPalette = getAwtPaletteData(awtImage.getType());
199 
200             if (swtPalette.equals(awtPalette)) {
201                 // No color conversion needed.
202                 for (int y = 0; y < swtData.height; y++) {
203                     for (int x = 0; x < swtData.width; x++) {
204                       int pixel = swtData.getPixel(x, y);
205                       awtImage.setRGB(x, y, 0xFF000000 | pixel);
206                     }
207                 }
208             } else {
209                 // We need to remap the colors
210                 int sr = -awtPalette.redShift   + swtPalette.redShift;
211                 int sg = -awtPalette.greenShift + swtPalette.greenShift;
212                 int sb = -awtPalette.blueShift  + swtPalette.blueShift;
213 
214                 for (int y = 0; y < swtData.height; y++) {
215                     for (int x = 0; x < swtData.width; x++) {
216                       int pixel = swtData.getPixel(x, y);
217 
218                       int r = pixel & swtPalette.redMask;
219                       int g = pixel & swtPalette.greenMask;
220                       int b = pixel & swtPalette.blueMask;
221                       r = (sr < 0) ? r >>> -sr : r << sr;
222                       g = (sg < 0) ? g >>> -sg : g << sg;
223                       b = (sb < 0) ? b >>> -sb : b << sb;
224 
225                       pixel = 0xFF000000 | r | g | b;
226                       awtImage.setRGB(x, y, pixel);
227                     }
228                 }
229             }
230         } else {
231             return null;
232         }
233 
234         return awtImage;
235     }
236 
237     /**
238      * Creates a new image from a source image where the contents from a given set of
239      * bounding boxes are copied into the new image and the rest is left transparent. A
240      * scale can be applied to make the resulting image larger or smaller than the source
241      * image. Note that the alpha channel in the original image is ignored, and the alpha
242      * values for the painted rectangles will be set to a specific value passed into this
243      * function.
244      *
245      * @param image the source image
246      * @param rectangles the set of rectangles (bounding boxes) to copy from the source
247      *            image
248      * @param boundingBox the bounding rectangle of the rectangle list, which can be
249      *            computed by {@link ImageUtils#getBoundingRectangle}
250      * @param scale a scale factor to apply to the result, e.g. 0.5 to shrink the
251      *            destination down 50%, 1.0 to leave it alone and 2.0 to zoom in by
252      *            doubling the image size
253      * @param alpha the alpha (in the range 0-255) that painted bits should be set to
254      * @return a pair of the rendered cropped image, and the location within the source
255      *         image that the crop begins (multiplied by the scale). May return null if
256      *         there are no selected items.
257      */
drawRectangles(Image image, List<Rectangle> rectangles, Rectangle boundingBox, double scale, byte alpha)258     public static Image drawRectangles(Image image,
259             List<Rectangle> rectangles, Rectangle boundingBox, double scale, byte alpha) {
260 
261         if (rectangles.size() == 0 || boundingBox == null || boundingBox.isEmpty()) {
262             return null;
263         }
264 
265         ImageData srcData = image.getImageData();
266         int destWidth = (int) (scale * boundingBox.width);
267         int destHeight = (int) (scale * boundingBox.height);
268 
269         ImageData destData = new ImageData(destWidth, destHeight, srcData.depth, srcData.palette);
270         byte[] alphaData = new byte[destHeight * destWidth];
271         destData.alphaData = alphaData;
272 
273         for (Rectangle bounds : rectangles) {
274             int dx1 = bounds.x - boundingBox.x;
275             int dy1 = bounds.y - boundingBox.y;
276             int dx2 = dx1 + bounds.width;
277             int dy2 = dy1 + bounds.height;
278 
279             dx1 *= scale;
280             dy1 *= scale;
281             dx2 *= scale;
282             dy2 *= scale;
283 
284             int sx1 = bounds.x;
285             int sy1 = bounds.y;
286             int sx2 = sx1 + bounds.width;
287             int sy2 = sy1 + bounds.height;
288 
289             if (scale == 1.0d) {
290                 for (int dy = dy1, sy = sy1; dy < dy2; dy++, sy++) {
291                     for (int dx = dx1, sx = sx1; dx < dx2; dx++, sx++) {
292                         destData.setPixel(dx, dy, srcData.getPixel(sx, sy));
293                         alphaData[dy * destWidth + dx] = alpha;
294                     }
295                 }
296             } else {
297                 // Scaled copy.
298                 int sxDelta = sx2 - sx1;
299                 int dxDelta = dx2 - dx1;
300                 int syDelta = sy2 - sy1;
301                 int dyDelta = dy2 - dy1;
302                 for (int dy = dy1, sy = sy1; dy < dy2; dy++, sy = (dy - dy1) * syDelta / dyDelta
303                         + sy1) {
304                     for (int dx = dx1, sx = sx1; dx < dx2; dx++, sx = (dx - dx1) * sxDelta
305                             / dxDelta + sx1) {
306                         assert sx < sx2 && sy < sy2;
307                         destData.setPixel(dx, dy, srcData.getPixel(sx, sy));
308                         alphaData[dy * destWidth + dx] = alpha;
309                     }
310                 }
311             }
312         }
313 
314         return new Image(image.getDevice(), destData);
315     }
316 
317     /**
318      * Creates a new empty/blank image of the given size
319      *
320      * @param display the display to associate the image with
321      * @param width the width of the image
322      * @param height the height of the image
323      * @return a new blank image of the given size
324      */
325     public static Image createEmptyImage(Display display, int width, int height) {
326         BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
327         return SwtUtils.convertToSwt(display, image, false, 0);
328     }
329 
330     /**
331      * Converts the given SWT {@link Rectangle} into an ADT {@link Rect}
332      *
333      * @param swtRect the SWT {@link Rectangle}
334      * @return an equivalent {@link Rect}
335      */
336     public static Rect toRect(Rectangle swtRect) {
337         return new Rect(swtRect.x, swtRect.y, swtRect.width, swtRect.height);
338     }
339 
340     /**
341      * Sets the values of the given ADT {@link Rect} to the values of the given SWT
342      * {@link Rectangle}
343      *
344      * @param target the ADT {@link Rect} to modify
345      * @param source the SWT {@link Rectangle} to read values from
346      */
347     public static void set(Rect target, Rectangle source) {
348         target.set(source.x, source.y, source.width, source.height);
349     }
350 
351     /**
352      * Compares an ADT {@link Rect} with an SWT {@link Rectangle} and returns true if they
353      * are equivalent
354      *
355      * @param r1 the ADT {@link Rect}
356      * @param r2 the SWT {@link Rectangle}
357      * @return true if the two rectangles are equivalent
358      */
359     public static boolean equals(Rect r1, Rectangle r2) {
360         return r1.x == r2.x && r1.y == r2.y && r1.w == r2.width && r1.h == r2.height;
361 
362     }
363 
364     /**
365      * Get the average width of the font used by the given control
366      *
367      * @param display the display associated with the font usage
368      * @param font the font to look up the average character width for
369      * @return the average width, in pixels, of the given font
370      */
371     public static final int getAverageCharWidth(Display display, Font font) {
372         GC gc = new GC(display);
373         gc.setFont(font);
374         FontMetrics fontMetrics = gc.getFontMetrics();
375         int width = fontMetrics.getAverageCharWidth();
376         gc.dispose();
377         return width;
378     }
379 
380     /**
381      * Get the average width of the given font
382      *
383      * @param control the control to look up the default font for
384      * @return the average width, in pixels, of the current font in the control
385      */
386     public static final int getAverageCharWidth(Control control) {
387         GC gc = new GC(control.getDisplay());
388         int width = gc.getFontMetrics().getAverageCharWidth();
389         gc.dispose();
390         return width;
391     }
392 
393     /**
394      * Draws a drop shadow for the given rectangle into the given context. It
395      * will not draw anything if the rectangle is smaller than a minimum
396      * determined by the assets used to draw the shadow graphics.
397      * <p>
398      * This corresponds to {@link ImageUtils#drawRectangleShadow(Graphics, int, int, int, int)},
399      * but applied directly to an SWT graphics context instead, such that no image conversion
400      * has to be performed.
401      * <p>
402      * Make sure to keep changes in the visual appearance here in sync with the
403      * AWT version in {@link ImageUtils#drawRectangleShadow(Graphics, int, int, int, int)}.
404      *
405      * @param gc the graphics context to draw into
406      * @param x the left coordinate of the left hand side of the rectangle
407      * @param y the top coordinate of the top of the rectangle
408      * @param width the width of the rectangle
409      * @param height the height of the rectangle
410      */
411     public static final void drawRectangleShadow(GC gc, int x, int y, int width, int height) {
412         if (sShadowBottomLeft == null) {
413             IconFactory icons = IconFactory.getInstance();
414             // See ImageUtils.drawRectangleShadow for an explanation of the assets.
415             sShadowBottomLeft  = icons.getIcon("shadow-bl"); //$NON-NLS-1$
416             sShadowBottom      = icons.getIcon("shadow-b");  //$NON-NLS-1$
417             sShadowBottomRight = icons.getIcon("shadow-br"); //$NON-NLS-1$
418             sShadowRight       = icons.getIcon("shadow-r");  //$NON-NLS-1$
419             sShadowTopRight    = icons.getIcon("shadow-tr"); //$NON-NLS-1$
420             assert sShadowBottomRight.getImageData().width == SHADOW_SIZE;
421             assert sShadowBottomRight.getImageData().height == SHADOW_SIZE;
422         }
423 
424         ImageData bottomLeftData = sShadowBottomLeft.getImageData();
425         ImageData topRightData = sShadowTopRight.getImageData();
426         ImageData bottomData = sShadowBottom.getImageData();
427         ImageData rightData = sShadowRight.getImageData();
428         int blWidth = bottomLeftData.width;
429         int trHeight = topRightData.height;
430         if (width < blWidth) {
431             return;
432         }
433         if (height < trHeight) {
434             return;
435         }
436 
437         gc.drawImage(sShadowBottomLeft, x, y + height);
438         gc.drawImage(sShadowBottomRight, x + width, y + height);
439         gc.drawImage(sShadowTopRight, x + width, y);
440         gc.drawImage(sShadowBottom,
441                 0, 0,
442                 bottomData.width, bottomData.height,
443                 x + bottomLeftData.width, y + height,
444                 width - bottomLeftData.width, bottomData.height);
445         gc.drawImage(sShadowRight,
446                 0, 0,
447                 rightData.width, rightData.height,
448                 x + width, y + topRightData.height,
449                 rightData.width, height - topRightData.height);
450     }
451 
452     private static Image sShadowBottomLeft;
453     private static Image sShadowBottom;
454     private static Image sShadowBottomRight;
455     private static Image sShadowRight;
456     private static Image sShadowTopRight;
457 }
458