blob: cc71053fced687a288decacccbed1da75345a64d [file] [log] [blame]
Jerome Gaillardf666c0e2016-11-24 21:51:22 +00001/*
2 * Copyright (C) 2016 The Android Open Source Project
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
17package android.graphics;
18
19import com.android.ide.common.rendering.api.LayoutLog;
20import com.android.layoutlib.bridge.Bridge;
21import com.android.layoutlib.bridge.impl.DelegateManager;
22import com.android.layoutlib.bridge.impl.GcSnapshot;
23import com.android.layoutlib.bridge.impl.PorterDuffUtility;
24import com.android.ninepatch.NinePatchChunk;
25import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
26
Diego Perezf4291b02017-03-15 12:45:39 +000027import android.annotation.Nullable;
Jerome Gaillardf666c0e2016-11-24 21:51:22 +000028import android.text.TextUtils;
29
30import java.awt.*;
31import java.awt.geom.AffineTransform;
32import java.awt.geom.Arc2D;
33import java.awt.geom.Rectangle2D;
34import java.awt.image.BufferedImage;
Diego Perezf4291b02017-03-15 12:45:39 +000035import java.awt.image.ColorModel;
36import java.awt.image.DataBuffer;
Jerome Gaillardf666c0e2016-11-24 21:51:22 +000037
38public class BaseCanvas_Delegate {
39 // ---- delegate manager ----
40 protected static DelegateManager<BaseCanvas_Delegate> sManager =
41 new DelegateManager<>(BaseCanvas_Delegate.class);
42
43 // ---- delegate helper data ----
44 private final static boolean[] sBoolOut = new boolean[1];
45
46
47 // ---- delegate data ----
48 protected Bitmap_Delegate mBitmap;
49 protected GcSnapshot mSnapshot;
50
51 // ---- Public Helper methods ----
52
53 protected BaseCanvas_Delegate(Bitmap_Delegate bitmap) {
54 mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
55 }
56
57 protected BaseCanvas_Delegate() {
58 mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
59 }
60
61 /**
62 * Disposes of the {@link Graphics2D} stack.
63 */
64 protected void dispose() {
65 mSnapshot.dispose();
66 }
67
68 /**
69 * Returns the current {@link Graphics2D} used to draw.
70 */
71 public GcSnapshot getSnapshot() {
72 return mSnapshot;
73 }
74
75 // ---- native methods ----
76
77 @LayoutlibDelegate
78 /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
79 long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) {
80 // get the delegate from the native int.
81 Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
82 if (bitmapDelegate == null) {
83 return;
84 }
85
86 BufferedImage image = bitmapDelegate.getImage();
87 float right = left + image.getWidth();
88 float bottom = top + image.getHeight();
89
90 drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
91 0, 0, image.getWidth(), image.getHeight(),
92 (int)left, (int)top, (int)right, (int)bottom);
93 }
94
95 @LayoutlibDelegate
96 /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop,
97 float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
98 float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity) {
99 // get the delegate from the native int.
100 Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
101 if (bitmapDelegate == null) {
102 return;
103 }
104
105 drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, (int) srcLeft, (int) srcTop,
106 (int) srcRight, (int) srcBottom, (int) dstLeft, (int) dstTop, (int) dstRight,
107 (int) dstBottom);
108 }
109
110 @LayoutlibDelegate
111 /*package*/ static void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
112 final float x, final float y, int width, int height, boolean hasAlpha,
113 long nativePaintOrZero) {
114 // create a temp BufferedImage containing the content.
115 final BufferedImage image = new BufferedImage(width, height,
116 hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
117 image.setRGB(0, 0, width, height, colors, offset, stride);
118
119 draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
120 (graphics, paint) -> {
121 if (paint != null && paint.isFilterBitmap()) {
122 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
123 RenderingHints.VALUE_INTERPOLATION_BILINEAR);
124 }
125
126 graphics.drawImage(image, (int) x, (int) y, null);
127 });
128 }
129
130 @LayoutlibDelegate
131 /*package*/ static void nDrawColor(long nativeCanvas, final int color, final int mode) {
132 // get the delegate from the native int.
133 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
134 if (canvasDelegate == null) {
135 return;
136 }
137
138 final int w = canvasDelegate.mBitmap.getImage().getWidth();
139 final int h = canvasDelegate.mBitmap.getImage().getHeight();
140 draw(nativeCanvas, (graphics, paint) -> {
141 // reset its transform just in case
142 graphics.setTransform(new AffineTransform());
143
144 // set the color
145 graphics.setColor(new java.awt.Color(color, true /*alpha*/));
146
147 Composite composite = PorterDuffUtility.getComposite(
148 PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
149 if (composite != null) {
150 graphics.setComposite(composite);
151 }
152
153 graphics.fillRect(0, 0, w, h);
154 });
155 }
156
157 @LayoutlibDelegate
158 /*package*/ static void nDrawPaint(long nativeCanvas, long paint) {
159 // FIXME
160 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
161 "Canvas.drawPaint is not supported.", null, null /*data*/);
162 }
163
164 @LayoutlibDelegate
165 /*package*/ static void nDrawPoint(long nativeCanvas, float x, float y,
166 long nativePaint) {
Charlie Tsaidea48d62017-03-02 18:09:56 +0000167 // TODO: need to support the attribute (e.g. stroke width) of paint
168 draw(nativeCanvas, nativePaint, false /*compositeOnly*/, false /*forceSrcMode*/,
169 (graphics, paintDelegate) -> graphics.fillRect((int)x, (int)y, 1, 1));
Jerome Gaillardf666c0e2016-11-24 21:51:22 +0000170 }
171
172 @LayoutlibDelegate
173 /*package*/ static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count,
174 long nativePaint) {
Charlie Tsaidea48d62017-03-02 18:09:56 +0000175 if (offset < 0 || count < 0 || offset + count > pts.length) {
176 throw new IllegalArgumentException("Invalid argument set");
177 }
178 // ignore the last point if the count is odd (It means it is not paired).
179 count = (count >> 1) << 1;
180 for (int i = offset; i < offset + count; i += 2) {
181 nDrawPoint(nativeCanvas, pts[i], pts[i + 1], nativePaint);
182 }
Jerome Gaillardf666c0e2016-11-24 21:51:22 +0000183 }
184
185 @LayoutlibDelegate
186 /*package*/ static void nDrawLine(long nativeCanvas,
187 final float startX, final float startY, final float stopX, final float stopY,
188 long paint) {
189 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
190 (graphics, paintDelegate) -> graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY));
191 }
192
193 @LayoutlibDelegate
194 /*package*/ static void nDrawLines(long nativeCanvas,
195 final float[] pts, final int offset, final int count,
196 long nativePaint) {
197 draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
198 false /*forceSrcMode*/, (graphics, paintDelegate) -> {
199 for (int i = 0; i < count; i += 4) {
200 graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1],
201 (int) pts[i + offset + 2], (int) pts[i + offset + 3]);
202 }
203 });
204 }
205
206 @LayoutlibDelegate
207 /*package*/ static void nDrawRect(long nativeCanvas,
208 final float left, final float top, final float right, final float bottom, long paint) {
209
210 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
211 (graphics, paintDelegate) -> {
212 int style = paintDelegate.getStyle();
213
214 // draw
215 if (style == Paint.Style.FILL.nativeInt ||
216 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
217 graphics.fillRect((int)left, (int)top,
218 (int)(right-left), (int)(bottom-top));
219 }
220
221 if (style == Paint.Style.STROKE.nativeInt ||
222 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
223 graphics.drawRect((int)left, (int)top,
224 (int)(right-left), (int)(bottom-top));
225 }
226 });
227 }
228
229 @LayoutlibDelegate
230 /*package*/ static void nDrawOval(long nativeCanvas, final float left,
231 final float top, final float right, final float bottom, long paint) {
232 if (right > left && bottom > top) {
233 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
234 (graphics, paintDelegate) -> {
235 int style = paintDelegate.getStyle();
236
237 // draw
238 if (style == Paint.Style.FILL.nativeInt ||
239 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
240 graphics.fillOval((int)left, (int)top,
241 (int)(right - left), (int)(bottom - top));
242 }
243
244 if (style == Paint.Style.STROKE.nativeInt ||
245 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
246 graphics.drawOval((int)left, (int)top,
247 (int)(right - left), (int)(bottom - top));
248 }
249 });
250 }
251 }
252
253 @LayoutlibDelegate
254 /*package*/ static void nDrawCircle(long nativeCanvas,
255 float cx, float cy, float radius, long paint) {
256 nDrawOval(nativeCanvas,
257 cx - radius, cy - radius, cx + radius, cy + radius,
258 paint);
259 }
260
261 @LayoutlibDelegate
262 /*package*/ static void nDrawArc(long nativeCanvas,
263 final float left, final float top, final float right, final float bottom,
264 final float startAngle, final float sweep,
265 final boolean useCenter, long paint) {
266 if (right > left && bottom > top) {
267 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
268 (graphics, paintDelegate) -> {
269 int style = paintDelegate.getStyle();
270
271 Arc2D.Float arc = new Arc2D.Float(
272 left, top, right - left, bottom - top,
273 -startAngle, -sweep,
274 useCenter ? Arc2D.PIE : Arc2D.OPEN);
275
276 // draw
277 if (style == Paint.Style.FILL.nativeInt ||
278 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
279 graphics.fill(arc);
280 }
281
282 if (style == Paint.Style.STROKE.nativeInt ||
283 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
284 graphics.draw(arc);
285 }
286 });
287 }
288 }
289
290 @LayoutlibDelegate
291 /*package*/ static void nDrawRoundRect(long nativeCanvas,
292 final float left, final float top, final float right, final float bottom,
293 final float rx, final float ry, long paint) {
294 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
295 (graphics, paintDelegate) -> {
296 int style = paintDelegate.getStyle();
297
298 // draw
299 if (style == Paint.Style.FILL.nativeInt ||
300 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
301 graphics.fillRoundRect(
302 (int)left, (int)top,
303 (int)(right - left), (int)(bottom - top),
304 2 * (int)rx, 2 * (int)ry);
305 }
306
307 if (style == Paint.Style.STROKE.nativeInt ||
308 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
309 graphics.drawRoundRect(
310 (int)left, (int)top,
311 (int)(right - left), (int)(bottom - top),
312 2 * (int)rx, 2 * (int)ry);
313 }
314 });
315 }
316
317 @LayoutlibDelegate
318 public static void nDrawPath(long nativeCanvas, long path, long paint) {
319 final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
320 if (pathDelegate == null) {
321 return;
322 }
323
324 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
325 (graphics, paintDelegate) -> {
326 Shape shape = pathDelegate.getJavaShape();
327 Rectangle2D bounds = shape.getBounds2D();
328 if (bounds.isEmpty()) {
329 // Apple JRE 1.6 doesn't like drawing empty shapes.
330 // http://b.android.com/178278
331
332 if (pathDelegate.isEmpty()) {
333 // This means that the path doesn't have any lines or curves so
334 // nothing to draw.
335 return;
336 }
337
338 // The stroke width is not consider for the size of the bounds so,
339 // for example, a horizontal line, would be considered as an empty
340 // rectangle.
341 // If the strokeWidth is not 0, we use it to consider the size of the
342 // path as well.
343 float strokeWidth = paintDelegate.getStrokeWidth();
344 if (strokeWidth <= 0.0f) {
345 return;
346 }
347 bounds.setRect(bounds.getX(), bounds.getY(),
348 Math.max(strokeWidth, bounds.getWidth()),
349 Math.max(strokeWidth, bounds.getHeight()));
350 }
351
352 int style = paintDelegate.getStyle();
353
354 if (style == Paint.Style.FILL.nativeInt ||
355 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
356 graphics.fill(shape);
357 }
358
359 if (style == Paint.Style.STROKE.nativeInt ||
360 style == Paint.Style.FILL_AND_STROKE.nativeInt) {
361 graphics.draw(shape);
362 }
363 });
364 }
365
366 @LayoutlibDelegate
367 /*package*/ static void nDrawRegion(long nativeCanvas, long nativeRegion,
368 long nativePaint) {
369 // FIXME
370 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
371 "Some canvas paths may not be drawn", null, null);
372 }
373
374 @LayoutlibDelegate
375 /*package*/ static void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch,
376 final float dstLeft, final float dstTop, final float dstRight, final float dstBottom,
377 long nativePaintOrZero, final int screenDensity, final int bitmapDensity) {
378
379 // get the delegate from the native int.
380 final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
381 if (bitmapDelegate == null) {
382 return;
383 }
384
385 byte[] c = NinePatch_Delegate.getChunk(ninePatch);
386 if (c == null) {
387 // not a 9-patch?
388 BufferedImage image = bitmapDelegate.getImage();
389 drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
390 image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
391 (int) dstBottom);
392 return;
393 }
394
395 final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
396 assert chunkObject != null;
397 if (chunkObject == null) {
398 return;
399 }
400
401 Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
402 if (canvasDelegate == null) {
403 return;
404 }
405
406 // this one can be null
407 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
408
409 canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
410 @Override
411 public void draw(Graphics2D graphics, Paint_Delegate paint) {
412 chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
413 (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
414 bitmapDensity);
415 }
416 }, paintDelegate, true, false);
417
418 }
419
420 @LayoutlibDelegate
421 /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
422 long nMatrix, long nPaint) {
423 // get the delegate from the native int.
424 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
425 if (canvasDelegate == null) {
426 return;
427 }
428
429 // get the delegate from the native int, which can be null
430 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
431
432 // get the delegate from the native int.
433 Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
434 if (bitmapDelegate == null) {
435 return;
436 }
437
438 final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
439
440 Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
441 if (matrixDelegate == null) {
442 return;
443 }
444
445 final AffineTransform mtx = matrixDelegate.getAffineTransform();
446
447 canvasDelegate.getSnapshot().draw((graphics, paint) -> {
448 if (paint != null && paint.isFilterBitmap()) {
449 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
450 RenderingHints.VALUE_INTERPOLATION_BILINEAR);
451 }
452
453 //FIXME add support for canvas, screen and bitmap densities.
454 graphics.drawImage(image, mtx, null);
455 }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
456 }
457
458 @LayoutlibDelegate
459 /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap,
460 int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
461 int colorOffset, long nPaint) {
462 // FIXME
463 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
464 "Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
465 }
466
467 @LayoutlibDelegate
468 /*package*/ static void nDrawVertices(long nCanvas, int mode, int n,
469 float[] verts, int vertOffset,
470 float[] texs, int texOffset,
471 int[] colors, int colorOffset,
472 short[] indices, int indexOffset,
473 int indexCount, long nPaint) {
474 // FIXME
475 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
476 "Canvas.drawVertices is not supported.", null, null /*data*/);
477 }
478
479 @LayoutlibDelegate
480 /*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count,
481 float startX, float startY, int flags, long paint, long typeface) {
482 drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0,
483 paint, typeface);
484 }
485
486 @LayoutlibDelegate
487 /*package*/ static void nDrawText(long nativeCanvas, String text,
488 int start, int end, float x, float y, final int flags, long paint,
489 long typeface) {
490 int count = end - start;
491 char[] buffer = TemporaryBuffer.obtain(count);
492 TextUtils.getChars(text, start, end, buffer, 0);
493
494 nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
495 }
496
497 @LayoutlibDelegate
498 /*package*/ static void nDrawTextRun(long nativeCanvas, String text,
499 int start, int end, int contextStart, int contextEnd,
500 float x, float y, boolean isRtl, long paint, long typeface) {
501 int count = end - start;
502 char[] buffer = TemporaryBuffer.obtain(count);
503 TextUtils.getChars(text, start, end, buffer, 0);
504
505 drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface);
506 }
507
508 @LayoutlibDelegate
509 /*package*/ static void nDrawTextRun(long nativeCanvas, char[] text,
510 int start, int count, int contextStart, int contextCount,
511 float x, float y, boolean isRtl, long paint, long typeface) {
512 drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface);
513 }
514
515 @LayoutlibDelegate
516 /*package*/ static void nDrawTextOnPath(long nativeCanvas,
517 char[] text, int index,
518 int count, long path,
519 float hOffset,
520 float vOffset, int bidiFlags,
521 long paint, long typeface) {
522 // FIXME
523 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
524 "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
525 }
526
527 @LayoutlibDelegate
528 /*package*/ static void nDrawTextOnPath(long nativeCanvas,
529 String text, long path,
530 float hOffset,
531 float vOffset,
532 int bidiFlags, long paint,
533 long typeface) {
534 // FIXME
535 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
536 "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
537 }
538
539 // ---- Private delegate/helper methods ----
540
541 /**
542 * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
543 * <p>Note that the drawable may actually be executed several times if there are
544 * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
545 */
546 private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode,
547 GcSnapshot.Drawable drawable) {
548 // get the delegate from the native int.
549 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
550 if (canvasDelegate == null) {
551 return;
552 }
553
554 // get the paint which can be null if nPaint is 0;
555 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
556
557 canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
558 }
559
560 /**
561 * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
562 * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
563 * <p>Note that the drawable may actually be executed several times if there are
564 * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
565 */
566 private static void draw(long nCanvas, GcSnapshot.Drawable drawable) {
567 // get the delegate from the native int.
568 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
569 if (canvasDelegate == null) {
570 return;
571 }
572
573 canvasDelegate.mSnapshot.draw(drawable);
574 }
575
576 private static void drawText(long nativeCanvas, final char[] text, final int index,
577 final int count, final float startX, final float startY, final boolean isRtl,
578 long paint, final long typeface) {
579
580 draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
581 (graphics, paintDelegate) -> {
582 // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
583 // Any change to this method should be reflected in Paint.measureText
584
585 // assert that the typeface passed is actually the one stored in paint.
586 assert (typeface == paintDelegate.mNativeTypeface);
587
588 // Paint.TextAlign indicates how the text is positioned relative to X.
589 // LEFT is the default and there's nothing to do.
590 float x = startX;
591 int limit = index + count;
592 if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
593 RectF bounds =
594 paintDelegate.measureText(text, index, count, null, 0, isRtl);
595 float m = bounds.right - bounds.left;
596 if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
597 x -= m / 2;
598 } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
599 x -= m;
600 }
601 }
602
603 new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x,
604 startY).renderText(index, limit, isRtl, null, 0, true);
605 });
606 }
607
608 private static void drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap,
609 long nativePaintOrZero, final int sleft, final int stop, final int sright,
610 final int sbottom, final int dleft, final int dtop, final int dright,
611 final int dbottom) {
612 // get the delegate from the native int.
613 BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
614 if (canvasDelegate == null) {
615 return;
616 }
617
618 // get the paint, which could be null if the int is 0
619 Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
620
621 final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
622
623 draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
624 (graphics, paint) -> {
625 if (paint != null && paint.isFilterBitmap()) {
626 graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
627 RenderingHints.VALUE_INTERPOLATION_BILINEAR);
628 }
629
630 //FIXME add support for canvas, screen and bitmap densities.
631 graphics.drawImage(image, dleft, dtop, dright, dbottom, sleft, stop, sright,
632 sbottom, null);
633 });
634 }
635
636 /**
637 * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
638 * The image returns, through a 1-size boolean array, whether the drawing code should
639 * use a SRC composite no matter what the paint says.
640 *
641 * @param bitmap the bitmap
642 * @param paint the paint that will be used to draw
643 * @param forceSrcMode whether the composite will have to be SRC
644 * @return the image to draw
645 */
646 private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
647 boolean[] forceSrcMode) {
648 BufferedImage image = bitmap.getImage();
649 forceSrcMode[0] = false;
650
651 // if the bitmap config is alpha_8, then we erase all color value from it
Diego Perezf4291b02017-03-15 12:45:39 +0000652 // before drawing it or apply the texture from the shader if present.
Jerome Gaillardf666c0e2016-11-24 21:51:22 +0000653 if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
Diego Perezf4291b02017-03-15 12:45:39 +0000654 Shader_Delegate shader = paint.getShader();
655 java.awt.Paint javaPaint = null;
656 if (shader instanceof BitmapShader_Delegate) {
657 javaPaint = shader.getJavaPaint();
658 }
659
660 fixAlpha8Bitmap(image, javaPaint);
Jerome Gaillardf666c0e2016-11-24 21:51:22 +0000661 } else if (!bitmap.hasAlpha()) {
662 // hasAlpha is merely a rendering hint. There can in fact be alpha values
663 // in the bitmap but it should be ignored at drawing time.
664 // There is two ways to do this:
665 // - override the composite to be SRC. This can only be used if the composite
666 // was going to be SRC or SRC_OVER in the first place
667 // - Create a different bitmap to draw in which all the alpha channel values is set
668 // to 0xFF.
669 if (paint != null) {
670 PorterDuff.Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
671
672 forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || mode == PorterDuff.Mode.SRC;
673 }
674
675 // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
676 if (!forceSrcMode[0]) {
677 image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
678 }
679 }
680
681 return image;
682 }
683
Diego Perezf4291b02017-03-15 12:45:39 +0000684 /**
685 * This method will apply the correct color to the passed "only alpha" image. Colors on the
686 * passed image will be destroyed.
687 * If the passed javaPaint is null, the color will be set to 0. If a paint is passed, it will
688 * be used to obtain the color that will be applied.
689 * <p/>
690 * This will destroy the passed image color channel.
691 */
692 private static void fixAlpha8Bitmap(final BufferedImage image,
693 @Nullable java.awt.Paint javaPaint) {
Jerome Gaillardf666c0e2016-11-24 21:51:22 +0000694 int w = image.getWidth();
695 int h = image.getHeight();
Diego Perezf4291b02017-03-15 12:45:39 +0000696
697 DataBuffer texture = null;
698 if (javaPaint != null) {
699 PaintContext context = javaPaint.createContext(ColorModel.getRGBdefault(), null, null,
700 new AffineTransform(), null);
701 texture = context.getRaster(0, 0, w, h).getDataBuffer();
702 }
703
Jerome Gaillardf666c0e2016-11-24 21:51:22 +0000704 int[] argb = new int[w * h];
705 image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
706
707 final int length = argb.length;
Diego Perezf4291b02017-03-15 12:45:39 +0000708 for (int i = 0; i < length; i++) {
Jerome Gaillardf666c0e2016-11-24 21:51:22 +0000709 argb[i] &= 0xFF000000;
Diego Perezf4291b02017-03-15 12:45:39 +0000710 if (texture != null) {
711 argb[i] |= texture.getElem(i) & 0x00FFFFFF;
712 }
Jerome Gaillardf666c0e2016-11-24 21:51:22 +0000713 }
Diego Perezf4291b02017-03-15 12:45:39 +0000714
Jerome Gaillardf666c0e2016-11-24 21:51:22 +0000715 image.setRGB(0, 0, w, h, argb, 0, w);
716 }
717
718 protected int save(int saveFlags) {
719 // get the current save count
720 int count = mSnapshot.size();
721
722 mSnapshot = mSnapshot.save(saveFlags);
723
724 // return the old save count
725 return count;
726 }
727
728 protected int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
729 Paint_Delegate paint = new Paint_Delegate();
730 paint.setAlpha(alpha);
731 return saveLayer(rect, paint, saveFlags);
732 }
733
734 protected int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
735 // get the current save count
736 int count = mSnapshot.size();
737
738 mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
739
740 // return the old save count
741 return count;
742 }
743
744 /**
745 * Restores the {@link GcSnapshot} to <var>saveCount</var>
746 * @param saveCount the saveCount
747 */
748 protected void restoreTo(int saveCount) {
749 mSnapshot = mSnapshot.restoreTo(saveCount);
750 }
751
752 /**
753 * Restores the top {@link GcSnapshot}
754 */
755 protected void restore() {
756 mSnapshot = mSnapshot.restore();
757 }
758
759 protected boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
760 return mSnapshot.clipRect(left, top, right, bottom, regionOp);
761 }
762}