1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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.badlogic.gdx.graphics; 18 19 import java.nio.Buffer; 20 import java.nio.IntBuffer; 21 import java.util.HashMap; 22 import java.util.Map; 23 24 import com.badlogic.gdx.backends.gwt.GwtFileHandle; 25 import com.badlogic.gdx.files.FileHandle; 26 import com.badlogic.gdx.utils.BufferUtils; 27 import com.badlogic.gdx.utils.Disposable; 28 import com.badlogic.gdx.utils.GdxRuntimeException; 29 import com.google.gwt.canvas.client.Canvas; 30 import com.google.gwt.canvas.dom.client.CanvasPixelArray; 31 import com.google.gwt.canvas.dom.client.Context2d; 32 import com.google.gwt.canvas.dom.client.Context2d.Composite; 33 import com.google.gwt.dom.client.CanvasElement; 34 import com.google.gwt.dom.client.ImageElement; 35 36 public class Pixmap implements Disposable { 37 public static Map<Integer, Pixmap> pixmaps = new HashMap<Integer, Pixmap>(); 38 static int nextId = 0; 39 40 /** Different pixel formats. 41 * 42 * @author mzechner */ 43 public enum Format { 44 Alpha, Intensity, LuminanceAlpha, RGB565, RGBA4444, RGB888, RGBA8888; 45 toGlFormat(Format format)46 public static int toGlFormat (Format format) { 47 if (format == Alpha) return GL20.GL_ALPHA; 48 if (format == Intensity) return GL20.GL_ALPHA; 49 if (format == LuminanceAlpha) return GL20.GL_LUMINANCE_ALPHA; 50 if (format == RGB565) return GL20.GL_RGB; 51 if (format == RGB888) return GL20.GL_RGB; 52 if (format == RGBA4444) return GL20.GL_RGBA; 53 if (format == RGBA8888) return GL20.GL_RGBA; 54 throw new GdxRuntimeException("unknown format: " + format); 55 } 56 toGlType(Format format)57 public static int toGlType (Format format) { 58 if (format == Alpha) return GL20.GL_UNSIGNED_BYTE; 59 if (format == Intensity) return GL20.GL_UNSIGNED_BYTE; 60 if (format == LuminanceAlpha) return GL20.GL_UNSIGNED_BYTE; 61 if (format == RGB565) return GL20.GL_UNSIGNED_SHORT_5_6_5; 62 if (format == RGB888) return GL20.GL_UNSIGNED_BYTE; 63 if (format == RGBA4444) return GL20.GL_UNSIGNED_SHORT_4_4_4_4; 64 if (format == RGBA8888) return GL20.GL_UNSIGNED_BYTE; 65 throw new GdxRuntimeException("unknown format: " + format); 66 } 67 } 68 69 /** Blending functions to be set with {@link Pixmap#setBlending}. 70 * @author mzechner */ 71 public enum Blending { 72 None, SourceOver 73 } 74 75 /** Filters to be used with {@link Pixmap#drawPixmap(Pixmap, int, int, int, int, int, int, int, int)}. 76 * 77 * @author mzechner */ 78 public enum Filter { 79 NearestNeighbour, BiLinear 80 } 81 82 int width; 83 int height; 84 Format format; 85 Canvas canvas; 86 Context2d context; 87 int id; 88 IntBuffer buffer; 89 int r = 255, g = 255, b = 255; 90 float a; 91 String color = make(r, g, b, a); 92 static String clearColor = make(255, 255, 255, 1.0f); 93 static Blending blending; 94 CanvasPixelArray pixels; 95 Pixmap(FileHandle file)96 public Pixmap (FileHandle file) { 97 GwtFileHandle gwtFile = (GwtFileHandle)file; 98 ImageElement img = gwtFile.preloader.images.get(file.path()); 99 if (img == null) throw new GdxRuntimeException("Couldn't load image '" + file.path() + "', file does not exist"); 100 create(img.getWidth(), img.getHeight(), Format.RGBA8888); 101 context.setGlobalCompositeOperation(Composite.COPY); 102 context.drawImage(img, 0, 0); 103 context.setGlobalCompositeOperation(getComposite()); 104 } 105 getContext()106 public Context2d getContext() { 107 return context; 108 } 109 getComposite()110 private static Composite getComposite () { 111 return Composite.SOURCE_OVER; 112 } 113 Pixmap(ImageElement img)114 public Pixmap (ImageElement img) { 115 create(img.getWidth(), img.getHeight(), Format.RGBA8888); 116 context.drawImage(img, 0, 0); 117 } 118 Pixmap(int width, int height, Format format)119 public Pixmap (int width, int height, Format format) { 120 create(width, height, format); 121 } 122 create(int width, int height, Format format2)123 private void create (int width, int height, Format format2) { 124 this.width = width; 125 this.height = height; 126 this.format = Format.RGBA8888; 127 canvas = Canvas.createIfSupported(); 128 canvas.getCanvasElement().setWidth(width); 129 canvas.getCanvasElement().setHeight(height); 130 context = canvas.getContext2d(); 131 context.setGlobalCompositeOperation(getComposite()); 132 buffer = BufferUtils.newIntBuffer(1); 133 id = nextId++; 134 buffer.put(0, id); 135 pixmaps.put(id, this); 136 } 137 make(int r2, int g2, int b2, float a2)138 public static String make (int r2, int g2, int b2, float a2) { 139 return "rgba(" + r2 + "," + g2 + "," + b2 + "," + a2 + ")"; 140 } 141 142 /** Sets the type of {@link Blending} to be used for all operations. Default is {@link Blending#SourceOver}. 143 * @param blending the blending type */ setBlending(Blending blending)144 public static void setBlending (Blending blending) { 145 Pixmap.blending = blending; 146 Composite composite = getComposite(); 147 for (Pixmap pixmap : pixmaps.values()) { 148 pixmap.context.setGlobalCompositeOperation(composite); 149 } 150 } 151 152 /** @return the currently set {@link Blending} */ getBlending()153 public static Blending getBlending () { 154 return blending; 155 } 156 157 /** Sets the type of interpolation {@link Filter} to be used in conjunction with 158 * {@link Pixmap#drawPixmap(Pixmap, int, int, int, int, int, int, int, int)}. 159 * @param filter the filter. */ setFilter(Filter filter)160 public static void setFilter (Filter filter) { 161 } 162 getFormat()163 public Format getFormat () { 164 return format; 165 } 166 getGLInternalFormat()167 public int getGLInternalFormat () { 168 return GL20.GL_RGBA; 169 } 170 getGLFormat()171 public int getGLFormat () { 172 return GL20.GL_RGBA; 173 } 174 getGLType()175 public int getGLType () { 176 return GL20.GL_UNSIGNED_BYTE; 177 } 178 getWidth()179 public int getWidth () { 180 return width; 181 } 182 getHeight()183 public int getHeight () { 184 return height; 185 } 186 getPixels()187 public Buffer getPixels () { 188 return buffer; 189 } 190 191 @Override dispose()192 public void dispose () { 193 pixmaps.remove(id); 194 } 195 getCanvasElement()196 public CanvasElement getCanvasElement () { 197 return canvas.getCanvasElement(); 198 } 199 200 /** Sets the color for the following drawing operations 201 * @param color the color, encoded as RGBA8888 */ setColor(int color)202 public void setColor (int color) { 203 r = (color >>> 24) & 0xff; 204 g = (color >>> 16) & 0xff; 205 b = (color >>> 8) & 0xff; 206 a = (color & 0xff) / 255f; 207 this.color = make(r, g, b, a); 208 context.setFillStyle(this.color); 209 context.setStrokeStyle(this.color); 210 } 211 212 /** Sets the color for the following drawing operations. 213 * 214 * @param r The red component. 215 * @param g The green component. 216 * @param b The blue component. 217 * @param a The alpha component. */ setColor(float r, float g, float b, float a)218 public void setColor (float r, float g, float b, float a) { 219 this.r = (int)(r * 255); 220 this.g = (int)(g * 255); 221 this.b = (int)(b * 255); 222 this.a = a; 223 color = make(this.r, this.g, this.b, this.a); 224 context.setFillStyle(color); 225 context.setStrokeStyle(this.color); 226 } 227 228 /** Sets the color for the following drawing operations. 229 * @param color The color. */ setColor(Color color)230 public void setColor (Color color) { 231 setColor(color.r, color.g, color.b, color.a); 232 } 233 234 /** Fills the complete bitmap with the currently set color. */ fill()235 public void fill () { 236 context.clearRect(0, 0, getWidth(), getHeight()); 237 rectangle(0, 0, getWidth(), getHeight(), DrawType.FILL); 238 } 239 240 // /** 241 // * Sets the width in pixels of strokes. 242 // * 243 // * @param width The stroke width in pixels. 244 // */ 245 // public void setStrokeWidth (int width); 246 247 /** Draws a line between the given coordinates using the currently set color. 248 * 249 * @param x The x-coodinate of the first point 250 * @param y The y-coordinate of the first point 251 * @param x2 The x-coordinate of the first point 252 * @param y2 The y-coordinate of the first point */ drawLine(int x, int y, int x2, int y2)253 public void drawLine (int x, int y, int x2, int y2) { 254 line(x, y, x2, y2, DrawType.STROKE); 255 } 256 257 /** Draws a rectangle outline starting at x, y extending by width to the right and by height downwards (y-axis points downwards) 258 * using the current color. 259 * 260 * @param x The x coordinate 261 * @param y The y coordinate 262 * @param width The width in pixels 263 * @param height The height in pixels */ drawRectangle(int x, int y, int width, int height)264 public void drawRectangle (int x, int y, int width, int height) { 265 rectangle(x, y, width, height, DrawType.STROKE); 266 } 267 268 /** Draws an area form another Pixmap to this Pixmap. 269 * 270 * @param pixmap The other Pixmap 271 * @param x The target x-coordinate (top left corner) 272 * @param y The target y-coordinate (top left corner) */ drawPixmap(Pixmap pixmap, int x, int y)273 public void drawPixmap (Pixmap pixmap, int x, int y) { 274 CanvasElement image = pixmap.getCanvasElement(); 275 image(image, 0, 0, image.getWidth(), image.getHeight(), x, y, image.getWidth(), image.getHeight()); 276 } 277 278 /** Draws an area form another Pixmap to this Pixmap. 279 * 280 * @param pixmap The other Pixmap 281 * @param x The target x-coordinate (top left corner) 282 * @param y The target y-coordinate (top left corner) 283 * @param srcx The source x-coordinate (top left corner) 284 * @param srcy The source y-coordinate (top left corner); 285 * @param srcWidth The width of the area form the other Pixmap in pixels 286 * @param srcHeight The height of the area form the other Pixmap in pixles */ drawPixmap(Pixmap pixmap, int x, int y, int srcx, int srcy, int srcWidth, int srcHeight)287 public void drawPixmap (Pixmap pixmap, int x, int y, int srcx, int srcy, int srcWidth, int srcHeight) { 288 CanvasElement image = pixmap.getCanvasElement(); 289 image(image, srcx, srcy, srcWidth, srcHeight, x, y, srcWidth, srcHeight); 290 } 291 292 /** Draws an area form another Pixmap to this Pixmap. This will automatically scale and stretch the source image to the 293 * specified target rectangle. Use {@link Pixmap#setFilter(Filter)} to specify the type of filtering to be used (nearest 294 * neighbour or bilinear). 295 * 296 * @param pixmap The other Pixmap 297 * @param srcx The source x-coordinate (top left corner) 298 * @param srcy The source y-coordinate (top left corner); 299 * @param srcWidth The width of the area form the other Pixmap in pixels 300 * @param srcHeight The height of the area form the other Pixmap in pixles 301 * @param dstx The target x-coordinate (top left corner) 302 * @param dsty The target y-coordinate (top left corner) 303 * @param dstWidth The target width 304 * @param dstHeight the target height */ drawPixmap(Pixmap pixmap, int srcx, int srcy, int srcWidth, int srcHeight, int dstx, int dsty, int dstWidth, int dstHeight)305 public void drawPixmap (Pixmap pixmap, int srcx, int srcy, int srcWidth, int srcHeight, int dstx, int dsty, int dstWidth, 306 int dstHeight) { 307 image(pixmap.getCanvasElement(), srcx, srcy, srcWidth, srcHeight, dstx, dsty, dstWidth, dstHeight); 308 } 309 310 /** Fills a rectangle starting at x, y extending by width to the right and by height downwards (y-axis points downwards) using 311 * the current color. 312 * 313 * @param x The x coordinate 314 * @param y The y coordinate 315 * @param width The width in pixels 316 * @param height The height in pixels */ fillRectangle(int x, int y, int width, int height)317 public void fillRectangle (int x, int y, int width, int height) { 318 rectangle(x, y, width, height, DrawType.FILL); 319 } 320 321 /** Draws a circle outline with the center at x,y and a radius using the current color and stroke width. 322 * 323 * @param x The x-coordinate of the center 324 * @param y The y-coordinate of the center 325 * @param radius The radius in pixels */ drawCircle(int x, int y, int radius)326 public void drawCircle (int x, int y, int radius) { 327 circle(x, y, radius, DrawType.STROKE); 328 } 329 330 /** Fills a circle with the center at x,y and a radius using the current color. 331 * 332 * @param x The x-coordinate of the center 333 * @param y The y-coordinate of the center 334 * @param radius The radius in pixels */ fillCircle(int x, int y, int radius)335 public void fillCircle (int x, int y, int radius) { 336 circle(x, y, radius, DrawType.FILL); 337 } 338 339 /** Fills a triangle with vertices at x1,y1 and x2,y2 and x3,y3 using the current color. 340 * 341 * @param x1 The x-coordinate of vertex 1 342 * @param y1 The y-coordinate of vertex 1 343 * @param x2 The x-coordinate of vertex 2 344 * @param y2 The y-coordinate of vertex 2 345 * @param x3 The x-coordinate of vertex 3 346 * @param y3 The y-coordinate of vertex 3 */ fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3)347 public void fillTriangle (int x1, int y1, int x2, int y2, int x3, int y3) { 348 triangle(x1, y1, x2, y2, x3, y3, DrawType.FILL); 349 } 350 351 /** Returns the 32-bit RGBA8888 value of the pixel at x, y. For Alpha formats the RGB components will be one. 352 * 353 * @param x The x-coordinate 354 * @param y The y-coordinate 355 * @return The pixel color in RGBA8888 format. */ getPixel(int x, int y)356 public int getPixel (int x, int y) { 357 if (pixels == null) pixels = context.getImageData(0, 0, width, height).getData(); 358 int i = x * 4 + y * width * 4; 359 int r = pixels.get(i + 0) & 0xff; 360 int g = pixels.get(i + 1) & 0xff; 361 int b = pixels.get(i + 2) & 0xff; 362 int a = pixels.get(i + 3) & 0xff; 363 return (r << 24) | (g << 16) | (b << 8) | (a); 364 } 365 366 /** Draws a pixel at the given location with the current color. 367 * 368 * @param x the x-coordinate 369 * @param y the y-coordinate */ drawPixel(int x, int y)370 public void drawPixel (int x, int y) { 371 rectangle(x, y, 1, 1, DrawType.FILL); 372 } 373 374 /** Draws a pixel at the given location with the given color. 375 * 376 * @param x the x-coordinate 377 * @param y the y-coordinate 378 * @param color the color in RGBA8888 format. */ drawPixel(int x, int y, int color)379 public void drawPixel (int x, int y, int color) { 380 setColor(color); 381 drawPixel(x, y); 382 } 383 circle(int x, int y, int radius, DrawType drawType)384 private void circle (int x, int y, int radius, DrawType drawType) { 385 if (blending == Blending.None) { 386 context.setFillStyle(clearColor); 387 context.setStrokeStyle(clearColor); 388 context.setGlobalCompositeOperation("destination-out"); 389 context.beginPath(); 390 context.arc(x, y, radius, 0, 2 * Math.PI, false); 391 fillOrStrokePath(drawType); 392 context.closePath(); 393 context.setFillStyle(color); 394 context.setStrokeStyle(color); 395 context.setGlobalCompositeOperation(Composite.SOURCE_OVER); 396 } 397 context.beginPath(); 398 context.arc(x, y, radius, 0, 2 * Math.PI, false); 399 fillOrStrokePath(drawType); 400 context.closePath(); 401 pixels = null; 402 } 403 line(int x, int y, int x2, int y2, DrawType drawType)404 private void line(int x, int y, int x2, int y2, DrawType drawType) { 405 if (blending == Blending.None) { 406 context.setFillStyle(clearColor); 407 context.setStrokeStyle(clearColor); 408 context.setGlobalCompositeOperation("destination-out"); 409 context.beginPath(); 410 context.moveTo(x, y); 411 context.lineTo(x2, y2); 412 fillOrStrokePath(drawType); 413 context.closePath(); 414 context.setFillStyle(color); 415 context.setStrokeStyle(color); 416 context.setGlobalCompositeOperation(Composite.SOURCE_OVER); 417 } 418 context.beginPath(); 419 context.moveTo(x, y); 420 context.lineTo(x2, y2); 421 fillOrStrokePath(drawType); 422 context.closePath(); 423 pixels = null; 424 } 425 rectangle(int x, int y, int width, int height, DrawType drawType)426 private void rectangle(int x, int y, int width, int height, DrawType drawType) { 427 if (blending == Blending.None) { 428 context.setFillStyle(clearColor); 429 context.setStrokeStyle(clearColor); 430 context.setGlobalCompositeOperation("destination-out"); 431 context.beginPath(); 432 context.rect(x, y, width, height); 433 fillOrStrokePath(drawType); 434 context.closePath(); 435 context.setFillStyle(color); 436 context.setStrokeStyle(color); 437 context.setGlobalCompositeOperation(Composite.SOURCE_OVER); 438 } 439 context.beginPath(); 440 context.rect(x, y, width, height); 441 fillOrStrokePath(drawType); 442 context.closePath(); 443 pixels = null; 444 } 445 triangle(int x1, int y1, int x2, int y2, int x3, int y3, DrawType drawType)446 private void triangle(int x1, int y1, int x2, int y2, int x3, int y3, DrawType drawType) { 447 if (blending == Blending.None) { 448 context.setFillStyle(clearColor); 449 context.setStrokeStyle(clearColor); 450 context.setGlobalCompositeOperation("destination-out"); 451 context.beginPath(); 452 context.moveTo(x1,y1); 453 context.lineTo(x2,y2); 454 context.lineTo(x3,y3); 455 context.lineTo(x1,y1); 456 fillOrStrokePath(drawType); 457 context.closePath(); 458 context.setFillStyle(color); 459 context.setStrokeStyle(color); 460 context.setGlobalCompositeOperation(Composite.SOURCE_OVER); 461 } 462 context.beginPath(); 463 context.moveTo(x1,y1); 464 context.lineTo(x2,y2); 465 context.lineTo(x3,y3); 466 context.lineTo(x1,y1); 467 fillOrStrokePath(drawType); 468 context.closePath(); 469 pixels = null; 470 } 471 image(CanvasElement image, int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight)472 private void image (CanvasElement image, int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight) { 473 if (blending == Blending.None) { 474 context.setFillStyle(clearColor); 475 context.setStrokeStyle(clearColor); 476 context.setGlobalCompositeOperation("destination-out"); 477 context.beginPath(); 478 context.rect(dstX, dstY, dstWidth, dstHeight); 479 fillOrStrokePath(DrawType.FILL); 480 context.closePath(); 481 context.setFillStyle(color); 482 context.setStrokeStyle(color); 483 context.setGlobalCompositeOperation(Composite.SOURCE_OVER); 484 } 485 context.drawImage(image, srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight); 486 pixels = null; 487 } 488 fillOrStrokePath(DrawType drawType)489 private void fillOrStrokePath(DrawType drawType) { 490 switch (drawType) { 491 case FILL: 492 context.fill(); 493 break; 494 case STROKE: 495 context.stroke(); 496 break; 497 } 498 } 499 500 private enum DrawType { 501 FILL, STROKE 502 } 503 504 } 505