1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.platform.canvas.rendering;
16
17 import java.awt.AlphaComposite;
18 import java.awt.BasicStroke;
19 import java.awt.Color;
20 import java.awt.Composite;
21 import java.awt.Font;
22 import java.awt.Graphics2D;
23 import java.awt.RenderingHints;
24 import java.awt.Shape;
25 import java.awt.geom.AffineTransform;
26 import java.awt.geom.Arc2D;
27 import java.awt.geom.Path2D;
28 import java.awt.geom.Point2D;
29 import java.awt.geom.Rectangle2D;
30 import java.awt.image.BufferedImage;
31 import java.awt.image.ImageObserver;
32 import java.io.ByteArrayOutputStream;
33 import java.io.IOException;
34 import java.nio.charset.StandardCharsets;
35 import java.util.ArrayDeque;
36 import java.util.ArrayList;
37 import java.util.Base64;
38 import java.util.Deque;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Locale;
42 import java.util.Map;
43
44 import javax.imageio.ImageIO;
45 import javax.imageio.ImageReader;
46
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
49 import org.htmlunit.platform.image.ImageIOImageData;
50 import org.htmlunit.util.StringUtils;
51
52
53
54
55
56
57 public class AwtRenderingBackend implements RenderingBackend {
58
59 private static final Log LOG = LogFactory.getLog(AwtRenderingBackend.class);
60 private static int ID_GENERATOR_;
61
62 private static final Map<String, Color> KNOWN_COLORS = new HashMap<>();
63
64 private final int id_;
65 private final BufferedImage image_;
66 private final Graphics2D graphics2D_;
67
68 private AffineTransform transformation_;
69 private float globalAlpha_;
70 private int lineWidth_;
71 private Color fillColor_;
72 private Color strokeColor_;
73
74 private final List<Path2D> subPaths_;
75 private final Deque<SaveState> savedStates_;
76
77 static {
78
79
80
81 KNOWN_COLORS.put("black", Color.decode("#000000"));
82 KNOWN_COLORS.put("silver", Color.decode("#c0c0c0"));
83 KNOWN_COLORS.put("gray", Color.decode("#808080"));
84 KNOWN_COLORS.put("white", Color.decode("#ffffff"));
85 KNOWN_COLORS.put("maroon", Color.decode("#800000"));
86 KNOWN_COLORS.put("red", Color.decode("#ff0000"));
87 KNOWN_COLORS.put("purple", Color.decode("#800080"));
88 KNOWN_COLORS.put("fuchsia", Color.decode("#ff00ff"));
89 KNOWN_COLORS.put("green", Color.decode("#008000"));
90 KNOWN_COLORS.put("lime", Color.decode("#00ff00"));
91 KNOWN_COLORS.put("olive", Color.decode("#808000"));
92 KNOWN_COLORS.put("yellow", Color.decode("#ffff00"));
93 KNOWN_COLORS.put("navy", Color.decode("#000080"));
94 KNOWN_COLORS.put("blue", Color.decode("#0000ff"));
95 KNOWN_COLORS.put("teal", Color.decode("#008080"));
96 KNOWN_COLORS.put("aqua", Color.decode("#00ffff"));
97
98
99 KNOWN_COLORS.put("orange", Color.decode("#ffa500"));
100
101
102 KNOWN_COLORS.put("aliceblue", Color.decode("#f0f8ff"));
103 KNOWN_COLORS.put("antiquewhite", Color.decode("#faebd7"));
104 KNOWN_COLORS.put("aquamarine", Color.decode("#7fffd4"));
105 KNOWN_COLORS.put("azure", Color.decode("#f0ffff"));
106 KNOWN_COLORS.put("beige", Color.decode("#f5f5dc"));
107 KNOWN_COLORS.put("bisque", Color.decode("#ffe4c4"));
108 KNOWN_COLORS.put("blanchedalmond", Color.decode("#ffebcd"));
109 KNOWN_COLORS.put("blueviolet", Color.decode("#8a2be2"));
110 KNOWN_COLORS.put("brown", Color.decode("#a52a2a"));
111 KNOWN_COLORS.put("burlywood", Color.decode("#deb887"));
112 KNOWN_COLORS.put("cadetblue", Color.decode("#5f9ea0"));
113 KNOWN_COLORS.put("chartreuse", Color.decode("#7fff00"));
114 KNOWN_COLORS.put("chocolate", Color.decode("#d2691e"));
115 KNOWN_COLORS.put("coral", Color.decode("#ff7f50"));
116 KNOWN_COLORS.put("cornflowerblue", Color.decode("#6495ed"));
117 KNOWN_COLORS.put("cornsilk", Color.decode("#fff8dc"));
118 KNOWN_COLORS.put("crimson", Color.decode("#dc143c"));
119 KNOWN_COLORS.put("cyan", Color.decode("#00ffff"));
120 KNOWN_COLORS.put("darkblue", Color.decode("#00008b"));
121 KNOWN_COLORS.put("darkcyan", Color.decode("#008b8b"));
122 KNOWN_COLORS.put("darkgoldenrod", Color.decode("#b8860b"));
123 KNOWN_COLORS.put("darkgray", Color.decode("#a9a9a9"));
124 KNOWN_COLORS.put("darkgreen", Color.decode("#006400"));
125 KNOWN_COLORS.put("darkgrey", Color.decode("#a9a9a9"));
126 KNOWN_COLORS.put("darkkhaki", Color.decode("#bdb76b"));
127 KNOWN_COLORS.put("darkmagenta", Color.decode("#8b008b"));
128 KNOWN_COLORS.put("darkolivegreen", Color.decode("#556b2f"));
129 KNOWN_COLORS.put("darkorange", Color.decode("#ff8c00"));
130 KNOWN_COLORS.put("darkorchid", Color.decode("#9932cc"));
131 KNOWN_COLORS.put("darkred", Color.decode("#8b0000"));
132 KNOWN_COLORS.put("darksalmon", Color.decode("#e9967a"));
133 KNOWN_COLORS.put("darkseagreen", Color.decode("#8fbc8f"));
134 KNOWN_COLORS.put("darkslateblue", Color.decode("#483d8b"));
135 KNOWN_COLORS.put("darkslategray", Color.decode("#2f4f4f"));
136 KNOWN_COLORS.put("darkslategrey", Color.decode("#2f4f4f"));
137 KNOWN_COLORS.put("darkturquoise", Color.decode("#00ced1"));
138 KNOWN_COLORS.put("darkviolet", Color.decode("#9400d3"));
139 KNOWN_COLORS.put("deeppink", Color.decode("#ff1493"));
140 KNOWN_COLORS.put("deepskyblue", Color.decode("#00bfff"));
141 KNOWN_COLORS.put("dimgray", Color.decode("#696969"));
142 KNOWN_COLORS.put("dimgrey", Color.decode("#696969"));
143 KNOWN_COLORS.put("dodgerblue", Color.decode("#1e90ff"));
144 KNOWN_COLORS.put("firebrick", Color.decode("#b22222"));
145 KNOWN_COLORS.put("floralwhite", Color.decode("#fffaf0"));
146 KNOWN_COLORS.put("forestgreen", Color.decode("#228b22"));
147 KNOWN_COLORS.put("gainsboro", Color.decode("#dcdcdc"));
148 KNOWN_COLORS.put("ghostwhite", Color.decode("#f8f8ff"));
149 KNOWN_COLORS.put("gold", Color.decode("#ffd700"));
150 KNOWN_COLORS.put("goldenrod", Color.decode("#daa520"));
151 KNOWN_COLORS.put("greenyellow", Color.decode("#adff2f"));
152 KNOWN_COLORS.put("grey", Color.decode("#808080"));
153 KNOWN_COLORS.put("honeydew", Color.decode("#f0fff0"));
154 KNOWN_COLORS.put("hotpink", Color.decode("#ff69b4"));
155 KNOWN_COLORS.put("indianred", Color.decode("#cd5c5c"));
156 KNOWN_COLORS.put("indigo", Color.decode("#4b0082"));
157 KNOWN_COLORS.put("ivory", Color.decode("#fffff0"));
158 KNOWN_COLORS.put("khaki", Color.decode("#f0e68c"));
159 KNOWN_COLORS.put("lavender", Color.decode("#e6e6fa"));
160 KNOWN_COLORS.put("lavenderblush", Color.decode("#fff0f5"));
161 KNOWN_COLORS.put("lawngreen", Color.decode("#7cfc00"));
162 KNOWN_COLORS.put("lemonchiffon", Color.decode("#fffacd"));
163 KNOWN_COLORS.put("lightblue", Color.decode("#add8e6"));
164 KNOWN_COLORS.put("lightcoral", Color.decode("#f08080"));
165 KNOWN_COLORS.put("lightcyan", Color.decode("#e0ffff"));
166 KNOWN_COLORS.put("lightgoldenrodyellow", Color.decode("#fafad2"));
167 KNOWN_COLORS.put("lightgray", Color.decode("#d3d3d3"));
168 KNOWN_COLORS.put("lightgreen", Color.decode("#90ee90"));
169 KNOWN_COLORS.put("lightgrey", Color.decode("#d3d3d3"));
170 KNOWN_COLORS.put("lightpink", Color.decode("#ffb6c1"));
171 KNOWN_COLORS.put("lightsalmon", Color.decode("#ffa07a"));
172 KNOWN_COLORS.put("lightseagreen", Color.decode("#20b2aa"));
173 KNOWN_COLORS.put("lightskyblue", Color.decode("#87cefa"));
174 KNOWN_COLORS.put("lightslategray", Color.decode("#778899"));
175 KNOWN_COLORS.put("lightslategrey", Color.decode("#778899"));
176 KNOWN_COLORS.put("lightsteelblue", Color.decode("#b0c4de"));
177 KNOWN_COLORS.put("lightyellow", Color.decode("#ffffe0"));
178 KNOWN_COLORS.put("limegreen", Color.decode("#32cd32"));
179 KNOWN_COLORS.put("linen", Color.decode("#faf0e6"));
180 KNOWN_COLORS.put("magenta", Color.decode("#ff00ff"));
181 KNOWN_COLORS.put("mediumaquamarine", Color.decode("#66cdaa"));
182 KNOWN_COLORS.put("mediumblue", Color.decode("#0000cd"));
183 KNOWN_COLORS.put("mediumorchid", Color.decode("#ba55d3"));
184 KNOWN_COLORS.put("mediumpurple", Color.decode("#9370db"));
185 KNOWN_COLORS.put("mediumseagreen", Color.decode("#3cb371"));
186 KNOWN_COLORS.put("mediumslateblue", Color.decode("#7b68ee"));
187 KNOWN_COLORS.put("mediumspringgreen", Color.decode("#00fa9a"));
188 KNOWN_COLORS.put("mediumturquoise", Color.decode("#48d1cc"));
189 KNOWN_COLORS.put("mediumvioletred", Color.decode("#c71585"));
190 KNOWN_COLORS.put("midnightblue", Color.decode("#191970"));
191 KNOWN_COLORS.put("mintcream", Color.decode("#f5fffa"));
192 KNOWN_COLORS.put("mistyrose", Color.decode("#ffe4e1"));
193 KNOWN_COLORS.put("moccasin", Color.decode("#ffe4b5"));
194 KNOWN_COLORS.put("navajowhite", Color.decode("#ffdead"));
195 KNOWN_COLORS.put("oldlace", Color.decode("#fdf5e6"));
196 KNOWN_COLORS.put("olivedrab", Color.decode("#6b8e23"));
197 KNOWN_COLORS.put("orangered", Color.decode("#ff4500"));
198 KNOWN_COLORS.put("orchid", Color.decode("#da70d6"));
199 KNOWN_COLORS.put("palegoldenrod", Color.decode("#eee8aa"));
200 KNOWN_COLORS.put("palegreen", Color.decode("#98fb98"));
201 KNOWN_COLORS.put("paleturquoise", Color.decode("#afeeee"));
202 KNOWN_COLORS.put("palevioletred", Color.decode("#db7093"));
203 KNOWN_COLORS.put("papayawhip", Color.decode("#ffefd5"));
204 KNOWN_COLORS.put("peachpuff", Color.decode("#ffdab9"));
205 KNOWN_COLORS.put("peru", Color.decode("#cd853f"));
206 KNOWN_COLORS.put("pink", Color.decode("#ffc0cb"));
207 KNOWN_COLORS.put("plum", Color.decode("#dda0dd"));
208 KNOWN_COLORS.put("powderblue", Color.decode("#b0e0e6"));
209 KNOWN_COLORS.put("rosybrown", Color.decode("#bc8f8f"));
210 KNOWN_COLORS.put("royalblue", Color.decode("#4169e1"));
211 KNOWN_COLORS.put("saddlebrown", Color.decode("#8b4513"));
212 KNOWN_COLORS.put("salmon", Color.decode("#fa8072"));
213 KNOWN_COLORS.put("sandybrown", Color.decode("#f4a460"));
214 KNOWN_COLORS.put("seagreen", Color.decode("#2e8b57"));
215 KNOWN_COLORS.put("seashell", Color.decode("#fff5ee"));
216 KNOWN_COLORS.put("sienna", Color.decode("#a0522d"));
217 KNOWN_COLORS.put("skyblue", Color.decode("#87ceeb"));
218 KNOWN_COLORS.put("slateblue", Color.decode("#6a5acd"));
219 KNOWN_COLORS.put("slategray", Color.decode("#708090"));
220 KNOWN_COLORS.put("slategrey", Color.decode("#708090"));
221 KNOWN_COLORS.put("snow", Color.decode("#fffafa"));
222 KNOWN_COLORS.put("springgreen", Color.decode("#00ff7f"));
223 KNOWN_COLORS.put("steelblue", Color.decode("#4682b4"));
224 KNOWN_COLORS.put("tan", Color.decode("#d2b48c"));
225 KNOWN_COLORS.put("thistle", Color.decode("#d8bfd8"));
226 KNOWN_COLORS.put("tomato", Color.decode("#ff6347"));
227 KNOWN_COLORS.put("turquoise", Color.decode("#40e0d0"));
228 KNOWN_COLORS.put("violet", Color.decode("#ee82ee"));
229 KNOWN_COLORS.put("wheat", Color.decode("#f5deb3"));
230 KNOWN_COLORS.put("whitesmoke", Color.decode("#f5f5f5"));
231 KNOWN_COLORS.put("yellowgreen", Color.decode("#9acd32"));
232
233 KNOWN_COLORS.put("rebeccapurple", Color.decode("#663399"));
234 }
235
236 private static synchronized int nextId() {
237 return ID_GENERATOR_++;
238 }
239
240
241
242
243
244
245 public AwtRenderingBackend(final int imageWidth, final int imageHeight) {
246 id_ = nextId();
247 if (LOG.isDebugEnabled()) {
248 LOG.debug("[" + id_ + "] AwtRenderingBackend(" + imageWidth + ", " + imageHeight + ")");
249 }
250
251 image_ = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
252 graphics2D_ = image_.createGraphics();
253
254 graphics2D_.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
255 graphics2D_.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
256 graphics2D_.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
257
258
259 fillColor_ = Color.black;
260 strokeColor_ = Color.black;
261 lineWidth_ = 1;
262 transformation_ = new AffineTransform();
263 updateGlobalAlpha(1f);
264 graphics2D_.setClip(null);
265
266 final Font font = new Font("SansSerif", Font.PLAIN, 10);
267 graphics2D_.setFont(font);
268
269 graphics2D_.setBackground(new Color(0f, 0f, 0f, 0f));
270 graphics2D_.setColor(Color.black);
271 graphics2D_.clearRect(0, 0, imageWidth, imageHeight);
272
273 subPaths_ = new ArrayList<>();
274 savedStates_ = new ArrayDeque<>();
275 }
276
277
278
279
280 @Override
281 public double getGlobalAlpha() {
282 return globalAlpha_;
283 }
284
285
286
287
288 @Override
289 public void setGlobalAlpha(final double globalAlpha) {
290 if (LOG.isDebugEnabled()) {
291 LOG.debug("[" + id_ + "] setGlobalAlpha(" + globalAlpha + ")");
292 }
293
294 if (globalAlpha >= 0 && globalAlpha <= 1) {
295 updateGlobalAlpha((float) globalAlpha);
296 }
297 }
298
299 private void updateGlobalAlpha(final float globalAlpha) {
300 globalAlpha_ = globalAlpha;
301 final AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, globalAlpha_);
302 graphics2D_.setComposite(composite);
303 }
304
305
306
307
308 @Override
309 public void beginPath() {
310 if (LOG.isDebugEnabled()) {
311 LOG.debug("[" + id_ + "] beginPath()");
312 }
313 subPaths_.clear();
314 }
315
316
317
318
319 @Override
320 public void ellipse(final double x, final double y,
321 final double radiusX, final double radiusY,
322 final double rotation, final double startAngle, final double endAngle,
323 final boolean anticlockwise) {
324 if (LOG.isDebugEnabled()) {
325 LOG.debug("[" + id_ + "] ellipse()");
326 }
327
328 final Path2D subPath = getCurrentSubPath();
329 if (subPath != null) {
330 final Point2D p = transformation_.transform(new Point2D.Double(x, y), null);
331 final double startAngleDegree = 360 - (startAngle * 180 / Math.PI);
332 final double endAngleDegree = 360 - (endAngle * 180 / Math.PI);
333
334 double extendAngle = startAngleDegree - endAngleDegree;
335 extendAngle = Math.min(360, Math.abs(extendAngle));
336 if (anticlockwise && extendAngle < 360) {
337 extendAngle = extendAngle - 360;
338 }
339
340 final AffineTransform transformation = new AffineTransform();
341 transformation.rotate(rotation, p.getX(), p.getY());
342 final Arc2D arc = new Arc2D.Double(p.getX() - radiusX, p.getY() - radiusY, radiusX * 2, radiusY * 2,
343 startAngleDegree, extendAngle * -1, Arc2D.OPEN);
344 subPath.append(transformation.createTransformedShape(arc), false);
345 }
346 }
347
348
349
350
351 @Override
352 public void bezierCurveTo(final double cp1x, final double cp1y,
353 final double cp2x, final double cp2y, final double x, final double y) {
354 if (LOG.isDebugEnabled()) {
355 LOG.debug("[" + id_ + "] bezierCurveTo()");
356 }
357
358 final Path2D subPath = getCurrentSubPath();
359 if (subPath != null) {
360 final Point2D cp1 = transformation_.transform(new Point2D.Double(cp1x, cp1y), null);
361 final Point2D cp2 = transformation_.transform(new Point2D.Double(cp2x, cp2y), null);
362 final Point2D p = transformation_.transform(new Point2D.Double(x, y), null);
363 subPath.curveTo(cp1.getX(), cp1.getY(), cp2.getX(), cp2.getY(), p.getX(), p.getY());
364 }
365 }
366
367
368
369
370 @Override
371 public void arc(final double x, final double y, final double radius, final double startAngle,
372 final double endAngle, final boolean anticlockwise) {
373 if (LOG.isDebugEnabled()) {
374 LOG.debug("[" + id_ + "] arc()");
375 }
376
377 final Path2D subPath = getCurrentSubPath();
378 if (subPath != null) {
379 final Point2D p = transformation_.transform(new Point2D.Double(x, y), null);
380 final double startAngleDegree = 360 - (startAngle * 180 / Math.PI);
381 final double endAngleDegree = 360 - (endAngle * 180 / Math.PI);
382
383 double extendAngle = startAngleDegree - endAngleDegree;
384 extendAngle = Math.min(360, Math.abs(extendAngle));
385 if (anticlockwise && extendAngle < 360) {
386 extendAngle = extendAngle - 360;
387 }
388 final Arc2D arc = new Arc2D.Double(p.getX() - radius, p.getY() - radius, radius * 2, radius * 2,
389 startAngleDegree, extendAngle * -1, Arc2D.OPEN);
390 subPath.append(arc, false);
391 }
392 }
393
394
395
396
397 @Override
398 public void clearRect(final double x, final double y, final double w, final double h) {
399 if (LOG.isDebugEnabled()) {
400 LOG.debug("[" + id_ + "] clearRect(" + x + ", " + y + ", " + w + ", " + h + ")");
401 }
402
403 final Composite saved = graphics2D_.getComposite();
404
405 graphics2D_.setColor(Color.BLACK);
406 graphics2D_.setComposite(AlphaComposite.Clear);
407 final Rectangle2D rect = new Rectangle2D.Double(x, y, w, h);
408 graphics2D_.fill(transformation_.createTransformedShape(rect));
409
410 graphics2D_.setComposite(saved);
411 }
412
413
414
415
416 @Override
417 public void drawImage(final org.htmlunit.platform.image.ImageData imageData,
418 final int sx, final int sy, final Integer sWidth, final Integer sHeight,
419 final int dx, final int dy, final Integer dWidth, final Integer dHeight) throws IOException {
420 if (LOG.isDebugEnabled()) {
421 LOG.debug("[" + id_ + "] drawImage(" + sx + ", " + sy + ", " + sWidth + ", " + sHeight
422 + "," + dx + ", " + dy + ", " + dWidth + ", " + dHeight + ")");
423 }
424
425 try {
426 final ImageReader imageReader = ((ImageIOImageData) imageData).getImageReader();
427
428 if (imageReader.getNumImages(true) != 0) {
429 final BufferedImage img = imageReader.read(0);
430
431 final AffineTransform savedTransform = graphics2D_.getTransform();
432 try {
433 graphics2D_.setTransform(transformation_);
434 graphics2D_.setColor(fillColor_);
435
436 final int sx2;
437 if (sWidth == null) {
438 sx2 = sx + img.getWidth();
439 }
440 else {
441 sx2 = sx + sWidth;
442 }
443
444 final int sy2;
445 if (sHeight == null) {
446 sy2 = sy + img.getHeight();
447 }
448 else {
449 sy2 = sy + sHeight;
450 }
451
452 int dx1 = dx;
453 final int dx2;
454 if (dWidth == null) {
455 dx2 = dx + img.getWidth();
456 }
457 else {
458 if (dWidth < 0) {
459 dx1 = dx1 + dWidth;
460 dx2 = dx1 - dWidth;
461 }
462 else {
463 dx2 = dx1 + dWidth;
464 }
465 }
466
467 int dy1 = dy;
468 final int dy2;
469 if (dHeight == null) {
470 dy2 = dy + img.getHeight();
471 }
472 else {
473 if (dHeight < 0) {
474 dy1 = dy1 + dHeight;
475 dy2 = dy1 - dHeight;
476 }
477 else {
478 dy2 = dy1 + dHeight;
479 }
480 }
481
482 final Object done = new Object();
483 final ImageObserver imageObserver = (img1, flags, x, y, width, height) -> {
484
485 if ((flags & ImageObserver.ALLBITS) == ImageObserver.ALLBITS) {
486 return true;
487 }
488
489 if ((flags & ImageObserver.ABORT) == ImageObserver.ABORT
490 || (flags & ImageObserver.ERROR) == ImageObserver.ERROR) {
491 return true;
492 }
493
494 synchronized (done) {
495 done.notify();
496 }
497
498 return false;
499 };
500
501 synchronized (done) {
502 final boolean completelyLoaded =
503 graphics2D_.drawImage(img, dx1, dy1, dx2, dy2, sx, sy, sx2, sy2, imageObserver);
504 if (!completelyLoaded) {
505 while (true) {
506 try {
507 done.wait(4 * 1000);
508 break;
509 }
510 catch (final InterruptedException e) {
511 LOG.error("[" + id_ + "] AwtRenderingBackend interrupted "
512 + "while waiting for drawImage to finish.", e);
513
514
515 Thread.currentThread().interrupt();
516 }
517 }
518 }
519 }
520 }
521 finally {
522 graphics2D_.setTransform(savedTransform);
523 }
524 }
525 }
526 catch (final ClassCastException e) {
527 LOG.error("[" + id_ + "] drawImage(..) failed", e);
528 }
529 }
530
531
532
533
534 @Override
535 public String encodeToString(final String type) throws IOException {
536 String imageType = type;
537 if (imageType != null && imageType.startsWith("image/")) {
538 imageType = imageType.substring(6);
539 }
540 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
541 ImageIO.write(image_, imageType, bos);
542
543 final byte[] imageBytes = bos.toByteArray();
544 return new String(Base64.getEncoder().encode(imageBytes), StandardCharsets.US_ASCII);
545 }
546 }
547
548
549
550
551 @Override
552 public void fill() {
553 if (LOG.isDebugEnabled()) {
554 LOG.debug("[" + id_ + "] fill()");
555 }
556
557 graphics2D_.setStroke(new BasicStroke(getLineWidth()));
558 graphics2D_.setColor(fillColor_);
559 for (final Path2D path2d : subPaths_) {
560 graphics2D_.fill(path2d);
561 }
562 }
563
564
565
566
567 @Override
568 public void fillRect(final int x, final int y, final int w, final int h) {
569 if (LOG.isDebugEnabled()) {
570 LOG.debug("[" + id_ + "] fillRect(" + x + ", " + y + ", " + w + ", " + h + ")");
571 }
572
573 graphics2D_.setColor(fillColor_);
574 final Rectangle2D rect = new Rectangle2D.Double(x, y, w, h);
575 graphics2D_.fill(transformation_.createTransformedShape(rect));
576 }
577
578
579
580
581 @Override
582 public void fillText(final String text, final double x, final double y) {
583 if (LOG.isDebugEnabled()) {
584 LOG.debug("[" + id_ + "] fillText('" + text + "', " + x + ", " + y + ")");
585 }
586
587 final AffineTransform savedTransform = graphics2D_.getTransform();
588 try {
589 graphics2D_.setTransform(transformation_);
590 graphics2D_.setColor(fillColor_);
591 graphics2D_.drawString(text, (int) x, (int) y);
592 }
593 finally {
594 graphics2D_.setTransform(savedTransform);
595 }
596 }
597
598
599
600
601 @Override
602 public byte[] getBytes(final int width, final int height, final int sx, final int sy) {
603 if (LOG.isDebugEnabled()) {
604 LOG.debug("[" + id_ + "] getBytes(" + width + ", " + height + ", " + sx + ", " + sy + ")");
605 }
606
607 final byte[] array = new byte[width * height * 4];
608 int index = 0;
609 for (int x = sx; x < sx + width; x++) {
610 if (x < 0 || x >= image_.getWidth()) {
611 array[index++] = (byte) 0;
612 array[index++] = (byte) 0;
613 array[index++] = (byte) 0;
614 array[index++] = (byte) 0;
615 }
616 else {
617 for (int y = sy; y < sy + height; y++) {
618 if (y < 0 || y >= image_.getHeight()) {
619 array[index++] = (byte) 0;
620 array[index++] = (byte) 0;
621 array[index++] = (byte) 0;
622 array[index++] = (byte) 0;
623 }
624 else {
625 final int color = image_.getRGB(x, y);
626 array[index++] = (byte) ((color & 0xff0000) >> 16);
627 array[index++] = (byte) ((color & 0xff00) >> 8);
628 array[index++] = (byte) (color & 0xff);
629 array[index++] = (byte) ((color & 0xff000000) >>> 24);
630 }
631 }
632 }
633 }
634 return array;
635 }
636
637
638
639
640 @Override
641 public void lineTo(final double x, final double y) {
642 if (LOG.isDebugEnabled()) {
643 LOG.debug("[" + id_ + "] lineTo(" + x + ", " + y + ")");
644 }
645
646 final Path2D subPath = getCurrentSubPath();
647 if (subPath != null) {
648 final Point2D p = transformation_.transform(new Point2D.Double(x, y), null);
649 subPath.lineTo(p.getX(), p.getY());
650 }
651 }
652
653
654
655
656 @Override
657 public void moveTo(final double x, final double y) {
658 if (LOG.isDebugEnabled()) {
659 LOG.debug("[" + id_ + "] moveTo(" + x + ", " + y + ")");
660 }
661
662 final Path2D subPath = new Path2D.Double();
663 final Point2D p = transformation_.transform(new Point2D.Double(x, y), null);
664 subPath.moveTo(p.getX(), p.getY());
665 subPaths_.add(subPath);
666 }
667
668
669
670
671 @Override
672 public void putImageData(final byte[] imageDataBytes,
673 final int imageDataWidth, final int imageDataHeight,
674 final int dx, final int dy, final int dirtyX, final int dirtyY,
675 final int dirtyWidth, final int dirtyHeight) {
676
677 if (LOG.isDebugEnabled()) {
678 LOG.debug("[" + id_ + "] putImageData()");
679 }
680
681 final Color orgColor = graphics2D_.getColor();
682
683 final int width = dx + imageDataWidth;
684 final int height = dy + imageDataHeight;
685 final int imageWidth = dirtyX + dirtyWidth;
686 final int imageHeight = dirtyY + dirtyHeight;
687
688 int byteIdx = 0;
689 int imageX = 0;
690 int imageY = 0;
691 for (int insertY = dy; insertY < height; insertY++) {
692 for (int insertX = dx; insertX < width; insertX++) {
693 if (0 <= insertX && insertX < image_.getWidth()
694 && 0 <= insertY && insertY < image_.getHeight()
695 && dirtyX <= imageX && imageX < imageWidth
696 && dirtyY <= imageY && imageY < imageHeight) {
697 final int r = imageDataBytes[byteIdx++] & 0xFF;
698 final int g = imageDataBytes[byteIdx++] & 0xFF;
699 final int b = imageDataBytes[byteIdx++] & 0xFF;
700 final int a = imageDataBytes[byteIdx++] & 0xFF;
701 final Color color = new Color(r, g, b, a);
702 graphics2D_.setColor(color);
703 graphics2D_.drawLine(insertX, insertY, insertX, insertY);
704 }
705 else {
706 byteIdx += 4;
707 }
708
709 imageX++;
710 if (imageX == imageDataWidth) {
711 imageX = 0;
712 imageY++;
713 }
714 }
715 }
716
717 graphics2D_.setColor(orgColor);
718 }
719
720
721
722
723 @Override
724 public void quadraticCurveTo(final double cpx, final double cpy,
725 final double x, final double y) {
726 if (LOG.isDebugEnabled()) {
727 LOG.debug("[" + id_ + "] quadraticCurveTo()");
728 }
729
730 final Path2D subPath = getCurrentSubPath();
731 if (subPath != null) {
732 final Point2D cp = transformation_.transform(new Point2D.Double(cpx, cpy), null);
733 final Point2D p = transformation_.transform(new Point2D.Double(x, y), null);
734 subPath.quadTo(cp.getX(), cp.getY(), p.getX(), p.getY());
735 }
736 }
737
738
739
740
741 @Override
742 public void rect(final double x, final double y, final double w, final double h) {
743 if (LOG.isDebugEnabled()) {
744 LOG.debug("[" + id_ + "] rect()");
745 }
746
747 final Path2D subPath = getCurrentSubPath();
748 if (subPath != null) {
749 final Point2D p = transformation_.transform(new Point2D.Double(x, y), null);
750 final Rectangle2D rect = new Rectangle2D.Double(p.getX(), p.getY(), w, h);
751 subPath.append(rect, false);
752 }
753 }
754
755
756
757
758 @Override
759 public void setFillStyle(final String fillStyle) {
760 if (LOG.isDebugEnabled()) {
761 LOG.debug("[" + id_ + "] setFillStyle(" + fillStyle + ")");
762 }
763
764 final Color color = extractColor(fillStyle);
765 if (color != null) {
766 fillColor_ = color;
767 }
768 }
769
770
771
772
773 @Override
774 public void setStrokeStyle(final String strokeStyle) {
775 if (LOG.isDebugEnabled()) {
776 LOG.debug("[" + id_ + "] setStrokeStyle(" + strokeStyle + ")");
777 }
778
779 final Color color = extractColor(strokeStyle);
780 if (color != null) {
781 strokeColor_ = color;
782 }
783 }
784
785 private static Color extractColor(final String style) {
786 final String tmpStyle = style.replaceAll("\\s", "");
787
788 Color color = toAwtColor(StringUtils.findColorRGB(tmpStyle));
789 if (color == null) {
790 color = toAwtColor(StringUtils.findColorRGBA(tmpStyle));
791 }
792 if (color == null) {
793 color = toAwtColor(StringUtils.findColorHSL(tmpStyle));
794 }
795
796 if (color == null) {
797 if (tmpStyle.length() > 0 && tmpStyle.charAt(0) == '#') {
798 color = toAwtColor(StringUtils.asColorHexadecimal(tmpStyle));
799 }
800 else {
801 color = KNOWN_COLORS.get(tmpStyle.toLowerCase(Locale.ROOT));
802 }
803 }
804 return color;
805 }
806
807
808
809
810 @Override
811 public int getLineWidth() {
812 return lineWidth_;
813 }
814
815
816
817
818 @Override
819 public void restore() {
820 if (LOG.isDebugEnabled()) {
821 LOG.debug("[" + id_ + "] restore()");
822 }
823
824 if (savedStates_.isEmpty()) {
825 return;
826 }
827
828 savedStates_.pop().applyOn(this);
829 }
830
831
832
833
834 @Override
835 public void rotate(final double angle) {
836 if (LOG.isDebugEnabled()) {
837 LOG.debug("[" + id_ + "] rotate()");
838 }
839
840 transformation_.rotate(angle);
841 }
842
843
844
845
846 @Override
847 public void save() {
848 if (LOG.isDebugEnabled()) {
849 LOG.debug("[" + id_ + "] save()");
850 }
851
852 savedStates_.push(new SaveState(this));
853 }
854
855
856
857
858 @Override
859 public void setLineWidth(final int lineWidth) {
860 if (LOG.isDebugEnabled()) {
861 LOG.debug("[" + id_ + "] setLineWidth(" + lineWidth + ")");
862 }
863
864 lineWidth_ = lineWidth;
865 }
866
867
868
869
870 @Override
871 public void setTransform(final double m11, final double m12,
872 final double m21, final double m22, final double dx, final double dy) {
873 if (LOG.isDebugEnabled()) {
874 LOG.debug("[" + id_ + "] setTransform("
875 + m11 + ", " + m12 + ", " + m21 + ", " + m22 + ", " + dx + ", " + dy + ")");
876 }
877
878 transformation_ = new AffineTransform(m11, m12, m21, m22, dx, dy);
879 }
880
881
882
883
884 @Override
885 public void stroke() {
886 if (LOG.isDebugEnabled()) {
887 LOG.debug("[" + id_ + "] stroke()");
888 }
889
890 graphics2D_.setStroke(new BasicStroke(getLineWidth()));
891 graphics2D_.setColor(strokeColor_);
892 for (final Path2D path2d : subPaths_) {
893 graphics2D_.draw(path2d);
894 }
895 }
896
897
898
899
900 @Override
901 public void strokeRect(final int x, final int y, final int w, final int h) {
902 if (LOG.isDebugEnabled()) {
903 LOG.debug("[" + id_ + "] strokeRect(" + x + ", " + y + ", " + w + ", " + h + ")");
904 }
905
906 graphics2D_.setColor(strokeColor_);
907 final Rectangle2D rect = new Rectangle2D.Double(x, y, w, h);
908 graphics2D_.draw(transformation_.createTransformedShape(rect));
909 }
910
911
912
913
914 @Override
915 public void transform(final double m11, final double m12,
916 final double m21, final double m22, final double dx, final double dy) {
917 if (LOG.isDebugEnabled()) {
918 LOG.debug("[" + id_ + "] transform()");
919 }
920
921 transformation_.concatenate(new AffineTransform(m11, m12, m21, m22, dx, dy));
922 }
923
924
925
926
927 @Override
928 public void translate(final int x, final int y) {
929 if (LOG.isDebugEnabled()) {
930 LOG.debug("[" + id_ + "] translate()");
931 }
932
933 transformation_.translate(x, y);
934 }
935
936
937
938
939 @Override
940 public void clip(final RenderingBackend.WindingRule windingRule,
941 final org.htmlunit.javascript.host.canvas.Path2D path) {
942 if (LOG.isDebugEnabled()) {
943 LOG.debug("[" + id_ + "] clip(" + windingRule + ", " + path + ")");
944 }
945
946 if (path == null && subPaths_.isEmpty()) {
947 graphics2D_.setClip(null);
948 return;
949 }
950
951 final Path2D currentPath;
952 if (path == null) {
953 currentPath = subPaths_.get(subPaths_.size() - 1);
954 }
955 else {
956
957 currentPath = null;
958 }
959 currentPath.closePath();
960
961 if (windingRule == WindingRule.NON_ZERO) {
962 currentPath.setWindingRule(Path2D.WIND_NON_ZERO);
963 }
964 else {
965 currentPath.setWindingRule(Path2D.WIND_EVEN_ODD);
966 }
967
968 graphics2D_.clip(currentPath);
969 }
970
971
972
973
974 @Override
975 public void closePath() {
976 if (LOG.isDebugEnabled()) {
977 LOG.debug("[" + id_ + "] closePath()");
978 }
979
980 if (subPaths_.isEmpty()) {
981 return;
982 }
983 subPaths_.get(subPaths_.size() - 1).closePath();
984 }
985
986 private Path2D getCurrentSubPath() {
987 if (subPaths_.isEmpty()) {
988 final Path2D subPath = new Path2D.Double();
989 subPaths_.add(subPath);
990 return subPath;
991 }
992 return subPaths_.get(subPaths_.size() - 1);
993 }
994
995 private static final class SaveState {
996 private final AffineTransform transformation_;
997 private final float globalAlpha_;
998 private final int lineWidth_;
999 private final Color fillColor_;
1000 private final Color strokeColor_;
1001 private final Shape clip_;
1002
1003 SaveState(final AwtRenderingBackend backend) {
1004 transformation_ = backend.transformation_;
1005 globalAlpha_ = backend.globalAlpha_;
1006 lineWidth_ = backend.lineWidth_;
1007 fillColor_ = backend.fillColor_;
1008 strokeColor_ = backend.strokeColor_;
1009
1010 clip_ = backend.graphics2D_.getClip();
1011 }
1012
1013 void applyOn(final AwtRenderingBackend backend) {
1014 backend.transformation_ = transformation_;
1015 backend.globalAlpha_ = globalAlpha_;
1016 backend.lineWidth_ = lineWidth_;
1017 backend.fillColor_ = fillColor_;
1018 backend.strokeColor_ = strokeColor_;
1019
1020 backend.graphics2D_.setClip(clip_);
1021 }
1022 }
1023
1024 private static Color toAwtColor(final org.htmlunit.html.impl.Color color) {
1025 if (color == null) {
1026 return null;
1027 }
1028 return new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
1029 }
1030 }