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