/** * Copyright 2008 - 2015 The Loon Game Engine Authors * * 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. * * @project loon * @author cping * @email:javachenpeng@yahoo.com * @version 0.5 */ package loon.robovm; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import loon.Graphics; import loon.canvas.Canvas; import loon.canvas.Gradient; import loon.canvas.Image; import loon.canvas.LColor; import loon.canvas.Path; import loon.canvas.Pattern; import loon.font.TextLayout; import loon.utils.Scale; import org.robovm.apple.coregraphics.CGAffineTransform; import org.robovm.apple.coregraphics.CGBitmapContext; import org.robovm.apple.coregraphics.CGBlendMode; import org.robovm.apple.coregraphics.CGImage; import org.robovm.apple.coregraphics.CGLineCap; import org.robovm.apple.coregraphics.CGLineJoin; import org.robovm.apple.coregraphics.CGMutablePath; import org.robovm.apple.coregraphics.CGRect; public class RoboVMCanvas extends Canvas { private float strokeWidth = 1; private int strokeColor = 0xFF000000; private int fillColor = 0xFF000000; private CGBitmapContext bctx; private LinkedList<RoboVMCanvasState> states = new LinkedList<RoboVMCanvasState>(); public RoboVMCanvas(Graphics gfx, RoboVMCanvasImage image) { super(gfx, image); if (width <= 0 || height <= 0) { throw new IllegalArgumentException( "Invalid size " + width + "x" + height); } states.addFirst(new RoboVMCanvasState()); bctx = image.bctx; bctx.clearRect(new CGRect(0, 0, texWidth(), texHeight())); Scale scale = image.scale(); bctx.translateCTM(0, scale.scaled(height)); bctx.scaleCTM(scale.factor, -scale.factor); } public int texWidth() { return image.pixelWidth(); } public int texHeight() { return image.pixelHeight(); } public CGImage cgimage() { return bctx.toImage(); } @Override public Canvas clear() { bctx.clearRect(new CGRect(0, 0, texWidth(), texHeight())); isDirty = true; return this; } @Override public Canvas clearRect(float x, float y, float width, float height) { bctx.clearRect(new CGRect(x, y, width, height)); isDirty = true; return this; } @Override public Canvas clip(Path clipPath) { bctx.addPath(((RoboVMPath) clipPath).cgPath); bctx.clip(); return this; } @Override public Canvas clipRect(float x, float y, float width, float height) { bctx.clipToRect(new CGRect(x, y, width, height)); return this; } @Override public Path createPath() { return new RoboVMPath(); } @Override public Gradient createGradient(Gradient.Config cfg) { if (cfg instanceof Gradient.Linear) return new RoboVMGradient.Linear((Gradient.Linear) cfg); else if (cfg instanceof Gradient.Radial) return new RoboVMGradient.Radial((Gradient.Radial) cfg); else throw new IllegalArgumentException("Unknown config: " + cfg); } @Override public Canvas setColor(LColor color) { int argb = color.getARGB(); this.setStrokeColor(argb); this.setFillColor(argb); this.setAlpha(color.a); return this; } @Override public Canvas setColor(int r, int g, int b, int a) { int argb = LColor.getARGB(r, g, b, a); this.setStrokeColor(argb); this.setFillColor(argb); this.setAlpha(a); return null; } @Override public Canvas setColor(int r, int g, int b) { int rgb = LColor.getRGB(r, g, b); this.setStrokeColor(rgb); this.setFillColor(rgb); return null; } @Override public int getStrokeColor() { return strokeColor; } @Override public int getFillColor() { return fillColor; } @Override public void close() { ((RoboVMCanvasImage) image).dispose(); } @Override public Canvas drawLine(float x0, float y0, float x1, float y1) { bctx.beginPath(); bctx.moveToPoint(x0, y0); bctx.addLineToPoint(x1, y1); bctx.strokePath(); isDirty = true; return this; } @Override public Canvas drawPoint(float x, float y) { save(); setStrokeWidth(0.5f); strokeRect(x + 0.25f, y + 0.25f, 0.5f, 0.5f); restore(); return this; } @Override public Canvas drawText(String text, float x, float y) { return fillText(_font.getLayoutText(text), x, y); } @Override public Canvas fillCircle(float x, float y, float radius) { RoboVMGradient gradient = currentState().gradient; if (gradient == null) { bctx.fillEllipseInRect(new CGRect(x - radius, y - radius, 2 * radius, 2 * radius)); } else { CGMutablePath cgPath = CGMutablePath.createMutable(); cgPath.addArc(null, x, y, radius, 0, 2 * Math.PI, false); bctx.addPath(cgPath); bctx.clip(); gradient.fill(bctx); } isDirty = true; return this; } @Override public Canvas fillPath(Path path) { bctx.addPath(((RoboVMPath) path).cgPath); RoboVMGradient gradient = currentState().gradient; if (gradient == null) { bctx.fillPath(); } else { bctx.clip(); gradient.fill(bctx); } isDirty = true; return this; } @Override public Canvas fillRect(float x, float y, float width, float height) { RoboVMGradient gradient = currentState().gradient; if (gradient == null) { bctx.fillRect(new CGRect(x, y, width, height)); } else { bctx.saveGState(); bctx.clipToRect(new CGRect(x, y, width, height)); gradient.fill(bctx); bctx.restoreGState(); } isDirty = true; return this; } @Override public Canvas fillRoundRect(float x, float y, float width, float height, float radius) { addRoundRectPath(x, y, width, height, radius); RoboVMGradient gradient = currentState().gradient; if (gradient == null) { bctx.fillPath(); } else { bctx.clip(); gradient.fill(bctx); } isDirty = true; return this; } @Override public Canvas fillText(TextLayout layout, float x, float y) { RoboVMGradient gradient = currentState().gradient; RoboVMTextLayout ilayout = (RoboVMTextLayout) layout; if (gradient == null) { ilayout.fill(bctx, x, y, fillColor); } else { CGBitmapContext maskContext = RoboVMGraphics.createCGBitmap( texWidth(), texHeight()); maskContext.clearRect(new CGRect(0, 0, texWidth(), texHeight())); float scale = image.scale().factor; maskContext.scaleCTM(scale, scale); setFillColor(maskContext, 0xFFFFFFFF); ilayout.fill(maskContext, 0, 0, fillColor); bctx.saveGState(); bctx.clipToMask(new CGRect(x, y, width, height), maskContext.toImage()); gradient.fill(bctx); bctx.restoreGState(); maskContext.dispose(); } isDirty = true; return this; } @Override public Canvas restore() { states.removeFirst(); bctx.restoreGState(); return this; } @Override public Canvas rotate(float radians) { bctx.rotateCTM(radians); return this; } @Override public Canvas save() { states.addFirst(new RoboVMCanvasState(currentState())); bctx.saveGState(); return this; } @Override public Canvas scale(float x, float y) { bctx.scaleCTM(x, y); return this; } @Override public Canvas setAlpha(float alpha) { bctx.setAlpha(alpha); return this; } @Override public Canvas setCompositeOperation(Composite composite) { bctx.setBlendMode(compToBlend.get(composite)); return this; } @Override public Canvas setFillColor(int color) { this.fillColor = color; currentState().gradient = null; setFillColor(bctx, color); return this; } @Override public Canvas setFillGradient(Gradient gradient) { currentState().gradient = (RoboVMGradient) gradient; return this; } @Override public Canvas setFillPattern(Pattern pattern) { currentState().gradient = null; bctx.setFillColor(((RoboVMPattern) pattern).colorWithPattern); return this; } @Override public Canvas setLineCap(LineCap cap) { bctx.setLineCap(decodeCap.get(cap)); return this; } @Override public Canvas setLineJoin(LineJoin join) { bctx.setLineJoin(decodeJoin.get(join)); return this; } @Override public Canvas setMiterLimit(float miter) { bctx.setMiterLimit(miter); return this; } @Override public Canvas setStrokeColor(int color) { this.strokeColor = color; setStrokeColor(bctx, color); return this; } @Override public Canvas setStrokeWidth(float strokeWidth) { this.strokeWidth = strokeWidth; bctx.setLineWidth(strokeWidth); return this; } @Override public Image snapshot() { return new RoboVMImage(gfx, image.scale(), ((RoboVMImage) image).cgImage(), "<canvas>"); } @Override public Canvas strokeCircle(float x, float y, float radius) { bctx.strokeEllipseInRect(new CGRect(x - radius, y - radius, 2 * radius, 2 * radius)); isDirty = true; return this; } @Override public Canvas strokePath(Path path) { bctx.addPath(((RoboVMPath) path).cgPath); bctx.strokePath(); isDirty = true; return this; } @Override public Canvas strokeRect(float x, float y, float width, float height) { bctx.strokeRect(new CGRect(x, y, width, height)); isDirty = true; return this; } @Override public Canvas strokeRoundRect(float x, float y, float width, float height, float radius) { addRoundRectPath(x, y, width, height, radius); bctx.strokePath(); isDirty = true; return this; } @Override public Canvas strokeText(TextLayout layout, float x, float y) { ((RoboVMTextLayout) layout) .stroke(bctx, x, y, strokeWidth, strokeColor); isDirty = true; return this; } @Override public Canvas transform(float m11, float m12, float m21, float m22, float dx, float dy) { bctx.concatCTM(new CGAffineTransform(m11, m12, m21, m22, dx, dy)); return this; } @Override public Canvas translate(float x, float y) { bctx.translateCTM(x, y); return this; } @Override protected CGBitmapContext gc() { return bctx; } private void addRoundRectPath(float x, float y, float width, float height, float radius) { float midx = x + width / 2, midy = y + height / 2, maxx = x + width, maxy = y + height; bctx.beginPath(); bctx.moveToPoint(x, midy); bctx.addArcToPoint(x, y, midx, y, radius); bctx.addArcToPoint(maxx, y, maxx, midy, radius); bctx.addArcToPoint(maxx, maxy, midx, maxy, radius); bctx.addArcToPoint(x, maxy, x, midy, radius); bctx.closePath(); } private RoboVMCanvasState currentState() { return states.peek(); } static void setStrokeColor(CGBitmapContext bctx, int color) { float blue = (color & 0xFF) / 255f; color >>= 8; float green = (color & 0xFF) / 255f; color >>= 8; float red = (color & 0xFF) / 255f; color >>= 8; float alpha = (color & 0xFF) / 255f; bctx.setRGBStrokeColor(red, green, blue, alpha); } static void setFillColor(CGBitmapContext bctx, int color) { float blue = (color & 0xFF) / 255f; color >>= 8; float green = (color & 0xFF) / 255f; color >>= 8; float red = (color & 0xFF) / 255f; color >>= 8; float alpha = (color & 0xFF) / 255f; bctx.setRGBFillColor(red, green, blue, alpha); } private static Map<Composite, CGBlendMode> compToBlend = new HashMap<Composite, CGBlendMode>(); static { compToBlend.put(Composite.SRC, CGBlendMode.Copy); compToBlend.put(Composite.DST_ATOP, CGBlendMode.DestinationAtop); compToBlend.put(Composite.SRC_OVER, CGBlendMode.Normal); compToBlend.put(Composite.DST_OVER, CGBlendMode.DestinationOver); compToBlend.put(Composite.SRC_IN, CGBlendMode.SourceIn); compToBlend.put(Composite.DST_IN, CGBlendMode.DestinationIn); compToBlend.put(Composite.SRC_OUT, CGBlendMode.SourceOut); compToBlend.put(Composite.DST_OUT, CGBlendMode.DestinationOut); compToBlend.put(Composite.SRC_ATOP, CGBlendMode.SourceAtop); compToBlend.put(Composite.XOR, CGBlendMode.XOR); compToBlend.put(Composite.MULTIPLY, CGBlendMode.Multiply); } private static Map<LineCap, CGLineCap> decodeCap = new HashMap<LineCap, CGLineCap>(); static { decodeCap.put(LineCap.BUTT, CGLineCap.Butt); decodeCap.put(LineCap.ROUND, CGLineCap.Round); decodeCap.put(LineCap.SQUARE, CGLineCap.Square); } private static Map<LineJoin, CGLineJoin> decodeJoin = new HashMap<LineJoin, CGLineJoin>(); static { decodeJoin.put(LineJoin.BEVEL, CGLineJoin.Bevel); decodeJoin.put(LineJoin.MITER, CGLineJoin.Miter); decodeJoin.put(LineJoin.ROUND, CGLineJoin.Round); } }