/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.graphics;
import java.nio.Buffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import com.badlogic.gdx.backends.gwt.GwtApplication;
import com.badlogic.gdx.backends.gwt.GwtFileHandle;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.google.gwt.aria.client.ImgRole;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.CanvasPixelArray;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.Context2d.Composite;
import com.google.gwt.canvas.dom.client.ImageData;
import com.google.gwt.dom.client.CanvasElement;
import com.google.gwt.dom.client.ImageElement;
public class Pixmap implements Disposable {
public static Map<Integer, Pixmap> pixmaps = new HashMap<Integer, Pixmap>();
static int nextId = 0;
/** Different pixel formats.
*
* @author mzechner */
public enum Format {
Alpha, Intensity, LuminanceAlpha, RGB565, RGBA4444, RGB888, RGBA8888;
public static int toGlFormat (Format format) {
if (format == Alpha) return GL20.GL_ALPHA;
if (format == Intensity) return GL20.GL_ALPHA;
if (format == LuminanceAlpha) return GL20.GL_LUMINANCE_ALPHA;
if (format == RGB565) return GL20.GL_RGB;
if (format == RGB888) return GL20.GL_RGB;
if (format == RGBA4444) return GL20.GL_RGBA;
if (format == RGBA8888) return GL20.GL_RGBA;
throw new GdxRuntimeException("unknown format: " + format);
}
public static int toGlType (Format format) {
if (format == Alpha) return GL20.GL_UNSIGNED_BYTE;
if (format == Intensity) return GL20.GL_UNSIGNED_BYTE;
if (format == LuminanceAlpha) return GL20.GL_UNSIGNED_BYTE;
if (format == RGB565) return GL20.GL_UNSIGNED_SHORT_5_6_5;
if (format == RGB888) return GL20.GL_UNSIGNED_BYTE;
if (format == RGBA4444) return GL20.GL_UNSIGNED_SHORT_4_4_4_4;
if (format == RGBA8888) return GL20.GL_UNSIGNED_BYTE;
throw new GdxRuntimeException("unknown format: " + format);
}
}
/** Blending functions to be set with {@link Pixmap#setBlending}.
* @author mzechner */
public enum Blending {
None, SourceOver
}
/** Filters to be used with {@link Pixmap#drawPixmap(Pixmap, int, int, int, int, int, int, int, int)}.
*
* @author mzechner */
public enum Filter {
NearestNeighbour, BiLinear
}
int width;
int height;
Format format;
Canvas canvas;
Context2d context;
int id;
IntBuffer buffer;
int r = 255, g = 255, b = 255;
float a;
String color = make(r, g, b, a);
static String clearColor = make(255, 255, 255, 1.0f);
Blending blending;
CanvasPixelArray pixels;
private ImageElement imageElement;
public Pixmap (FileHandle file) {
this(((GwtFileHandle)file).preloader.images.get(file.path()));
if (imageElement == null) throw new GdxRuntimeException("Couldn't load image '" + file.path() + "', file does not exist");
}
public Context2d getContext() {
ensureCanvasExists();
return context;
}
private static Composite getComposite () {
return Composite.SOURCE_OVER;
}
public Pixmap (ImageElement img) {
this(-1, -1, img);
}
public Pixmap (int width, int height, Format format) {
this(width, height, (ImageElement)null);
}
private Pixmap(int width, int height, ImageElement imageElement) {
this.imageElement = imageElement;
this.width = imageElement != null ? imageElement.getWidth() : width;
this.height = imageElement != null ? imageElement.getHeight() : height;
this.format = Format.RGBA8888;
buffer = BufferUtils.newIntBuffer(1);
id = nextId++;
buffer.put(0, id);
pixmaps.put(id, this);
}
private void create () {
canvas = Canvas.createIfSupported();
canvas.getCanvasElement().setWidth(width);
canvas.getCanvasElement().setHeight(height);
context = canvas.getContext2d();
context.setGlobalCompositeOperation(getComposite());
}
public static String make (int r2, int g2, int b2, float a2) {
return "rgba(" + r2 + "," + g2 + "," + b2 + "," + a2 + ")";
}
/** Sets the type of {@link Blending} to be used for all operations. Default is {@link Blending#SourceOver}.
* @param blending the blending type */
public void setBlending (Blending blending) {
this.blending = blending;
this.ensureCanvasExists();
this.context.setGlobalCompositeOperation(getComposite());
}
/** @return the currently set {@link Blending} */
public Blending getBlending () {
return blending;
}
/** Sets the type of interpolation {@link Filter} to be used in conjunction with
* {@link Pixmap#drawPixmap(Pixmap, int, int, int, int, int, int, int, int)}.
* @param filter the filter. */
public void setFilter (Filter filter) {
}
public Format getFormat () {
return format;
}
public int getGLInternalFormat () {
return GL20.GL_RGBA;
}
public int getGLFormat () {
return GL20.GL_RGBA;
}
public int getGLType () {
return GL20.GL_UNSIGNED_BYTE;
}
public int getWidth () {
return width;
}
public int getHeight () {
return height;
}
public Buffer getPixels () {
return buffer;
}
@Override
public void dispose () {
pixmaps.remove(id);
}
public CanvasElement getCanvasElement () {
ensureCanvasExists();
return canvas.getCanvasElement();
}
private void ensureCanvasExists () {
if (canvas == null) {
create();
if (imageElement != null) {
context.setGlobalCompositeOperation(Composite.COPY);
context.drawImage(imageElement, 0, 0);
context.setGlobalCompositeOperation(getComposite());
}
}
}
public boolean canUseImageElement () {
return canvas == null && imageElement != null;
}
public ImageElement getImageElement () {
return imageElement;
}
/** Sets the color for the following drawing operations
* @param color the color, encoded as RGBA8888 */
public void setColor (int color) {
ensureCanvasExists();
r = (color >>> 24) & 0xff;
g = (color >>> 16) & 0xff;
b = (color >>> 8) & 0xff;
a = (color & 0xff) / 255f;
this.color = make(r, g, b, a);
context.setFillStyle(this.color);
context.setStrokeStyle(this.color);
}
/** Sets the color for the following drawing operations.
*
* @param r The red component.
* @param g The green component.
* @param b The blue component.
* @param a The alpha component. */
public void setColor (float r, float g, float b, float a) {
ensureCanvasExists();
this.r = (int)(r * 255);
this.g = (int)(g * 255);
this.b = (int)(b * 255);
this.a = a;
color = make(this.r, this.g, this.b, this.a);
context.setFillStyle(color);
context.setStrokeStyle(this.color);
}
/** Sets the color for the following drawing operations.
* @param color The color. */
public void setColor (Color color) {
setColor(color.r, color.g, color.b, color.a);
}
/** Fills the complete bitmap with the currently set color. */
public void fill () {
ensureCanvasExists();
context.clearRect(0, 0, getWidth(), getHeight());
rectangle(0, 0, getWidth(), getHeight(), DrawType.FILL);
}
// /**
// * Sets the width in pixels of strokes.
// *
// * @param width The stroke width in pixels.
// */
// public void setStrokeWidth (int width);
/** Draws a line between the given coordinates using the currently set color.
*
* @param x The x-coodinate of the first point
* @param y The y-coordinate of the first point
* @param x2 The x-coordinate of the first point
* @param y2 The y-coordinate of the first point */
public void drawLine (int x, int y, int x2, int y2) {
line(x, y, x2, y2, DrawType.STROKE);
}
/** Draws a rectangle outline starting at x, y extending by width to the right and by height downwards (y-axis points downwards)
* using the current color.
*
* @param x The x coordinate
* @param y The y coordinate
* @param width The width in pixels
* @param height The height in pixels */
public void drawRectangle (int x, int y, int width, int height) {
rectangle(x, y, width, height, DrawType.STROKE);
}
/** Draws an area form another Pixmap to this Pixmap.
*
* @param pixmap The other Pixmap
* @param x The target x-coordinate (top left corner)
* @param y The target y-coordinate (top left corner) */
public void drawPixmap (Pixmap pixmap, int x, int y) {
CanvasElement image = pixmap.getCanvasElement();
image(image, 0, 0, image.getWidth(), image.getHeight(), x, y, image.getWidth(), image.getHeight());
}
/** Draws an area form another Pixmap to this Pixmap.
*
* @param pixmap The other Pixmap
* @param x The target x-coordinate (top left corner)
* @param y The target y-coordinate (top left corner)
* @param srcx The source x-coordinate (top left corner)
* @param srcy The source y-coordinate (top left corner);
* @param srcWidth The width of the area form the other Pixmap in pixels
* @param srcHeight The height of the area form the other Pixmap in pixles */
public void drawPixmap (Pixmap pixmap, int x, int y, int srcx, int srcy, int srcWidth, int srcHeight) {
CanvasElement image = pixmap.getCanvasElement();
image(image, srcx, srcy, srcWidth, srcHeight, x, y, srcWidth, srcHeight);
}
/** Draws an area form another Pixmap to this Pixmap. This will automatically scale and stretch the source image to the
* specified target rectangle. Use {@link Pixmap#setFilter(Filter)} to specify the type of filtering to be used (nearest
* neighbour or bilinear).
*
* @param pixmap The other Pixmap
* @param srcx The source x-coordinate (top left corner)
* @param srcy The source y-coordinate (top left corner);
* @param srcWidth The width of the area form the other Pixmap in pixels
* @param srcHeight The height of the area form the other Pixmap in pixles
* @param dstx The target x-coordinate (top left corner)
* @param dsty The target y-coordinate (top left corner)
* @param dstWidth The target width
* @param dstHeight the target height */
public void drawPixmap (Pixmap pixmap, int srcx, int srcy, int srcWidth, int srcHeight, int dstx, int dsty, int dstWidth,
int dstHeight) {
image(pixmap.getCanvasElement(), srcx, srcy, srcWidth, srcHeight, dstx, dsty, dstWidth, dstHeight);
}
/** Fills a rectangle starting at x, y extending by width to the right and by height downwards (y-axis points downwards) using
* the current color.
*
* @param x The x coordinate
* @param y The y coordinate
* @param width The width in pixels
* @param height The height in pixels */
public void fillRectangle (int x, int y, int width, int height) {
rectangle(x, y, width, height, DrawType.FILL);
}
/** Draws a circle outline with the center at x,y and a radius using the current color and stroke width.
*
* @param x The x-coordinate of the center
* @param y The y-coordinate of the center
* @param radius The radius in pixels */
public void drawCircle (int x, int y, int radius) {
circle(x, y, radius, DrawType.STROKE);
}
/** Fills a circle with the center at x,y and a radius using the current color.
*
* @param x The x-coordinate of the center
* @param y The y-coordinate of the center
* @param radius The radius in pixels */
public void fillCircle (int x, int y, int radius) {
circle(x, y, radius, DrawType.FILL);
}
/** Fills a triangle with vertices at x1,y1 and x2,y2 and x3,y3 using the current color.
*
* @param x1 The x-coordinate of vertex 1
* @param y1 The y-coordinate of vertex 1
* @param x2 The x-coordinate of vertex 2
* @param y2 The y-coordinate of vertex 2
* @param x3 The x-coordinate of vertex 3
* @param y3 The y-coordinate of vertex 3 */
public void fillTriangle (int x1, int y1, int x2, int y2, int x3, int y3) {
triangle(x1, y1, x2, y2, x3, y3, DrawType.FILL);
}
/** Returns the 32-bit RGBA8888 value of the pixel at x, y. For Alpha formats the RGB components will be one.
*
* @param x The x-coordinate
* @param y The y-coordinate
* @return The pixel color in RGBA8888 format. */
public int getPixel (int x, int y) {
ensureCanvasExists();
if (pixels == null) pixels = context.getImageData(0, 0, width, height).getData();
int i = x * 4 + y * width * 4;
int r = pixels.get(i + 0) & 0xff;
int g = pixels.get(i + 1) & 0xff;
int b = pixels.get(i + 2) & 0xff;
int a = pixels.get(i + 3) & 0xff;
return (r << 24) | (g << 16) | (b << 8) | (a);
}
/** Draws a pixel at the given location with the current color.
*
* @param x the x-coordinate
* @param y the y-coordinate */
public void drawPixel (int x, int y) {
rectangle(x, y, 1, 1, DrawType.FILL);
}
/** Draws a pixel at the given location with the given color.
*
* @param x the x-coordinate
* @param y the y-coordinate
* @param color the color in RGBA8888 format. */
public void drawPixel (int x, int y, int color) {
setColor(color);
drawPixel(x, y);
}
private void circle (int x, int y, int radius, DrawType drawType) {
ensureCanvasExists();
if (blending == Blending.None) {
context.setFillStyle(clearColor);
context.setStrokeStyle(clearColor);
context.setGlobalCompositeOperation("destination-out");
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
fillOrStrokePath(drawType);
context.closePath();
context.setFillStyle(color);
context.setStrokeStyle(color);
context.setGlobalCompositeOperation(Composite.SOURCE_OVER);
}
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
fillOrStrokePath(drawType);
context.closePath();
pixels = null;
}
private void line(int x, int y, int x2, int y2, DrawType drawType) {
ensureCanvasExists();
if (blending == Blending.None) {
context.setFillStyle(clearColor);
context.setStrokeStyle(clearColor);
context.setGlobalCompositeOperation("destination-out");
context.beginPath();
context.moveTo(x, y);
context.lineTo(x2, y2);
fillOrStrokePath(drawType);
context.closePath();
context.setFillStyle(color);
context.setStrokeStyle(color);
context.setGlobalCompositeOperation(Composite.SOURCE_OVER);
}
context.beginPath();
context.moveTo(x, y);
context.lineTo(x2, y2);
fillOrStrokePath(drawType);
context.closePath();
pixels = null;
}
private void rectangle(int x, int y, int width, int height, DrawType drawType) {
ensureCanvasExists();
if (blending == Blending.None) {
context.setFillStyle(clearColor);
context.setStrokeStyle(clearColor);
context.setGlobalCompositeOperation("destination-out");
context.beginPath();
context.rect(x, y, width, height);
fillOrStrokePath(drawType);
context.closePath();
context.setFillStyle(color);
context.setStrokeStyle(color);
context.setGlobalCompositeOperation(Composite.SOURCE_OVER);
}
context.beginPath();
context.rect(x, y, width, height);
fillOrStrokePath(drawType);
context.closePath();
pixels = null;
}
private void triangle(int x1, int y1, int x2, int y2, int x3, int y3, DrawType drawType) {
ensureCanvasExists();
if (blending == Blending.None) {
context.setFillStyle(clearColor);
context.setStrokeStyle(clearColor);
context.setGlobalCompositeOperation("destination-out");
context.beginPath();
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.lineTo(x3,y3);
context.lineTo(x1,y1);
fillOrStrokePath(drawType);
context.closePath();
context.setFillStyle(color);
context.setStrokeStyle(color);
context.setGlobalCompositeOperation(Composite.SOURCE_OVER);
}
context.beginPath();
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.lineTo(x3,y3);
context.lineTo(x1,y1);
fillOrStrokePath(drawType);
context.closePath();
pixels = null;
}
private void image (CanvasElement image, int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight) {
ensureCanvasExists();
if (blending == Blending.None) {
context.setFillStyle(clearColor);
context.setStrokeStyle(clearColor);
context.setGlobalCompositeOperation("destination-out");
context.beginPath();
context.rect(dstX, dstY, dstWidth, dstHeight);
fillOrStrokePath(DrawType.FILL);
context.closePath();
context.setFillStyle(color);
context.setStrokeStyle(color);
context.setGlobalCompositeOperation(Composite.SOURCE_OVER);
}
context.drawImage(image, srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight);
pixels = null;
}
private void fillOrStrokePath(DrawType drawType) {
ensureCanvasExists();
switch (drawType) {
case FILL:
context.fill();
break;
case STROKE:
context.stroke();
break;
}
}
private enum DrawType {
FILL, STROKE
}
}