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 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 
     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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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