/** * @since Available in iOS 4.0 and later. */ public static CGBitmapContext create(long width, long height, long bitsPerComponent, long bytesPerRow, CGColorSpace space, CGBitmapInfo bitmapInfo, ReleaseDataCallback releaseCallback) { return create((IntPtr)null, width, height, bitsPerComponent, bytesPerRow, space, bitmapInfo, releaseCallback); } /**
@Override public Canvas fillText(TextLayout layout, float x, float y) { RoboGradient gradient = currentState().gradient; RoboTextLayout ilayout = (RoboTextLayout) layout; if (gradient == null) { ilayout.fill(bctx, x, y, fillColor); } else { // draw our text into a fresh context so we can use it as a mask for the gradient CGBitmapContext maskContext = RoboGraphics.createCGBitmap(texWidth, texHeight); maskContext.clearRect(new CGRect(0, 0, texWidth, texHeight)); // scale the context based on our scale factor maskContext.scaleCTM(ctx.scale.factor, ctx.scale.factor); // fill the text into this temp context in white for use as a mask setFillColor(maskContext, 0xFFFFFFFF); ilayout.fill(maskContext, 0, 0, fillColor); // now fill the gradient, using our temp context as a mask bctx.saveGState(); bctx.clipToMask(new CGRect(x, y, width, height), maskContext.toImage()); gradient.fill(bctx); bctx.restoreGState(); // finally free the temp context maskContext.dispose(); } isDirty = true; return this; }
@Override public void draw(Object ctx, float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh) { // adjust our source rect to account for the scale factor sx *= scale.factor; sy *= scale.factor; sw *= scale.factor; sh *= scale.factor; CGImage image = cgImage(); CGBitmapContext bctx = (CGBitmapContext)ctx; float iw = image.getWidth(), ih = image.getHeight(); float scaleX = dw/sw, scaleY = dh/sh; // pesky fiddling to cope with the fact that UIImages are flipped bctx.saveGState(); bctx.translateCTM(dx, dy+dh); bctx.scaleCTM(1, -1); bctx.clipToRect(new CGRect(0, 0, dw, dh)); bctx.translateCTM(-sx*scaleX, -(ih-(sy+sh))*scaleY); bctx.drawImage(new CGRect(0, 0, iw*scaleX, ih*scaleY), image); bctx.restoreGState(); }
@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; }
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(); }
@Override public Canvas fillRect(float x, float y, float width, float height) { RoboGradient 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 protected void upload (Graphics gfx, Texture tex) { int width = pixelWidth, height = pixelHeight; if (width == 0 || height == 0) { ((RoboGraphics)gfx).plat.log().info("Ignoring texture update for empty image (" + width + "x" + height + ")."); return; } CGBitmapContext bctx = RoboGraphics.createCGBitmap(width, height); CGRect rect = new CGRect(0, 0, width, height); bctx.clearRect(rect); bctx.drawImage(rect, image); upload(gfx, tex.id, width, height, bctx.getData()); bctx.dispose(); }
@Override public void getRgb(int startX, int startY, int width, int height, int[] rgbArray, int offset, int scanSize) { if (width <= 0 || height <= 0) return; int bytesPerRow = 4 * width; CGBitmapContext context = CGBitmapContext.create( width, height, 8, bytesPerRow, CGColorSpace.createDeviceRGB(), // PremultipliedFirst for ARGB, same as BufferedImage in Java. new CGBitmapInfo(CGImageAlphaInfo.PremultipliedFirst.value())); // since we're fishing for authentic RGB data, never allow interpolation. context.setInterpolationQuality(CGInterpolationQuality.None); draw(context, 0, 0, width, height, startX, startY, width, height); // TODO: extract data from context.getData() // int x = 0; // int y = height - 1; // inverted Y // for (int px = 0; px < regionBytes.length; px += 4) { // int a = (int)regionBytes[px ] & 0xFF; // int r = (int)regionBytes[px + 1] & 0xFF; // int g = (int)regionBytes[px + 2] & 0xFF; // int b = (int)regionBytes[px + 3] & 0xFF; // rgbArray[offset + y * scanSize + x] = a << 24 | r << 16 | g << 8 | b; // x++; // if (x == width) { // x = 0; // y--; // } // } }
public RoboCanvas(RoboGLContext ctx, float width, float height, boolean interpolate) { super(width, height); // if our size is invalid, we'll fail below at CGBitmapContext, so fail here more usefully if (width <= 0 || height <= 0) throw new IllegalArgumentException( "Invalid size " + width + "x" + height); states.addFirst(new RoboCanvasState()); this.ctx = ctx; // create our raw image data texWidth = ctx.scale.scaledCeil(width); texHeight = ctx.scale.scaledCeil(height); // create the bitmap context via which we'll render into it bctx = RoboGraphics.createCGBitmap(texWidth, texHeight); if (!interpolate) { bctx.setInterpolationQuality(CGInterpolationQuality.None); } // clear the canvas before we scale our bitmap context to avoid artifacts bctx.clearRect(new CGRect(0, 0, texWidth, texHeight)); // CG coordinate system is OpenGL-style (0,0 in lower left); so we flip it bctx.translateCTM(0, ctx.scale.scaled(height)); bctx.scaleCTM(ctx.scale.factor, -ctx.scale.factor); }
@Override public Canvas fillCircle(float x, float y, float radius) { RoboGradient 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 drawArc(float cx, float cy, float r, float startAngle, float arcAngle) { // Note: https://developer.apple.com/documentation/coregraphics/1455756-cgcontextaddarc // "In a flipped coordinate system (the default for UIView drawing methods // in iOS), specifying a clockwise arc results in a counterclockwise arc // after the transformation is applied." int cw = (arcAngle > 0) ? 1 : 0; bctx.beginPath(); bctx.addArc(cx, cy, r, -startAngle, -(startAngle + arcAngle), cw); bctx.strokePath(); isDirty = true; return this; }
public RoboCanvas(Graphics gfx, RoboCanvasImage image) { super(gfx, image); // if our size is invalid, we'll fail below at CGBitmapContext, so fail here more usefully if (width <= 0 || height <= 0) throw new IllegalArgumentException( "Invalid size " + width + "x" + height); states.addFirst(new RoboCanvasState()); bctx = image.bctx; // clear the canvas before we scale our bitmap context to avoid artifacts bctx.clearRect(new CGRect(0, 0, texWidth(), texHeight())); // CG coordinate system is OpenGL-style (0,0 in lower left); so we flip it Scale scale = image.scale(); bctx.translateCTM(0, scale.scaled(height)); bctx.scaleCTM(scale.factor, -scale.factor); }
@Override public Canvas strokePath(Path path) { bctx.addPath(((RoboPath) path).cgPath); bctx.strokePath(); isDirty = true; return this; }
@Override public Canvas fillRoundRect(float x, float y, float width, float height, float radius) { addRoundRectPath(x, y, width, height, radius); RoboGradient gradient = currentState().gradient; if (gradient == null) { bctx.fillPath(); } else { bctx.clip(); gradient.fill(bctx); } isDirty = true; return this; }
@Override public Canvas clear() { bctx.clearRect(new CGRect(0, 0, texWidth, texHeight)); isDirty = true; 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 Canvas fillText(TextLayout layout, float x, float y) { RoboGradient gradient = currentState().gradient; RoboTextLayout ilayout = (RoboTextLayout) layout; if (gradient == null) { ilayout.fill(bctx, x, y, fillColor); } else { // draw our text into a fresh context so we can use it as a mask for the gradient CGBitmapContext maskContext = RoboGraphics.createCGBitmap(texWidth(), texHeight()); maskContext.clearRect(new CGRect(0, 0, texWidth(), texHeight())); // scale the context based on our scale factor float scale = image.scale().factor; maskContext.scaleCTM(scale, scale); // fill the text into this temp context in white for use as a mask setFillColor(maskContext, 0xFFFFFFFF); ilayout.fill(maskContext, 0, 0, fillColor); // now fill the gradient, using our temp context as a mask bctx.saveGState(); bctx.clipToMask(new CGRect(x, y, width, height), maskContext.toImage()); gradient.fill(bctx); bctx.restoreGState(); // finally free the temp context maskContext.dispose(); } isDirty = true; return this; }
@Override public void draw(CGBitmapContext bctx, float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh) { // adjust our source rect to account for the scale factor sx *= scale.factor; sy *= scale.factor; sw *= scale.factor; sh *= scale.factor; CGImage cgImage = cgImage(); float iw = cgImage.getWidth(), ih = cgImage.getHeight(); float scaleX = dw/sw, scaleY = dh/sh; // pesky fiddling to cope with the fact that UIImages are flipped bctx.saveGState(); bctx.translateCTM(dx, dy+dh); bctx.scaleCTM(1, -1); bctx.clipToRect(new CGRect(0, 0, dw, dh)); bctx.translateCTM(-sx*scaleX, -(ih-(sy+sh))*scaleY); bctx.drawImage(new CGRect(0, 0, iw*scaleX, ih*scaleY), cgImage); bctx.restoreGState(); }