1 package org.opentrafficsim.draw;
2
3 import java.awt.Color;
4 import java.awt.Font;
5 import java.awt.FontMetrics;
6 import java.awt.Graphics2D;
7 import java.awt.RenderingHints;
8 import java.awt.Shape;
9 import java.awt.geom.Rectangle2D;
10 import java.awt.geom.RoundRectangle2D;
11 import java.awt.image.ImageObserver;
12 import java.io.Serializable;
13 import java.rmi.RemoteException;
14 import java.util.function.Supplier;
15
16 import org.djutils.draw.Oriented;
17 import org.djutils.draw.bounds.Bounds2d;
18 import org.djutils.draw.line.Polygon2d;
19 import org.djutils.draw.point.OrientedPoint2d;
20 import org.djutils.draw.point.Point2d;
21 import org.opentrafficsim.base.geometry.OtsLocatable;
22
23 import nl.tudelft.simulation.dsol.animation.d2.Renderable2d;
24 import nl.tudelft.simulation.language.d2.Angle;
25 import nl.tudelft.simulation.naming.context.Contextualized;
26
27
28
29
30
31
32
33
34
35
36
37
38
39 public abstract class TextAnimation<L extends OtsLocatable, T extends TextAnimation<L, T>> implements OtsLocatable, Serializable
40 {
41
42 private static final long serialVersionUID = 20161211L;
43
44
45 private final L source;
46
47
48 private Supplier<String> text;
49
50
51 private float dx;
52
53
54 private float dy;
55
56
57 private final TextAlignment textAlignment;
58
59
60 private Color color;
61
62
63 private final float fontSize;
64
65
66 private final float minFontSize;
67
68
69 private final float maxFontSize;
70
71
72 private final AnimationImpl animationImpl;
73
74
75 private Font font;
76
77
78 private final ContrastToBackground background;
79
80
81 private final ScaleDependentRendering scaleDependentRendering;
82
83
84 private boolean dynamic = false;
85
86
87 private OrientedPoint2d location;
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 @SuppressWarnings("checkstyle:parameternumber")
105 public TextAnimation(final L source, final Supplier<String> text, final float dx, final float dy,
106 final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
107 final float maxFontSize, final Contextualized contextualized, final ContrastToBackground background,
108 final ScaleDependentRendering scaleDependentRendering)
109 {
110 this.source = source;
111 this.text = text;
112 this.dx = dx;
113 this.dy = dy;
114 this.textAlignment = textAlignment;
115 this.color = color;
116 this.fontSize = fontSize;
117 this.minFontSize = minFontSize;
118 this.maxFontSize = maxFontSize;
119 this.background = background;
120 this.scaleDependentRendering = scaleDependentRendering;
121
122 this.font = new Font("SansSerif", Font.PLAIN, 2);
123 if (this.fontSize != 2.0f)
124 {
125 this.font = this.font.deriveFont(this.fontSize);
126 }
127
128 this.animationImpl = new AnimationImpl(this, contextualized);
129 }
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 @SuppressWarnings("checkstyle:parameternumber")
146 public TextAnimation(final L source, final Supplier<String> text, final float dx, final float dy,
147 final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
148 final float maxFontSize, final Contextualized contextualized, final ScaleDependentRendering scaleDependentRendering)
149 {
150 this(source, text, dx, dy, textAlignment, color, fontSize, minFontSize, maxFontSize, contextualized, null,
151 scaleDependentRendering);
152 }
153
154
155
156
157
158
159
160
161
162
163
164 public TextAnimation(final L source, final Supplier<String> text, final float dx, final float dy,
165 final TextAlignment textAlignment, final Color color, final Contextualized contextualized,
166 final ScaleDependentRendering scaleDependentRendering)
167 {
168 this(source, text, dx, dy, textAlignment, color, 2.0f, 12.0f, 50f, contextualized, scaleDependentRendering);
169 }
170
171
172
173
174
175
176 @SuppressWarnings("unchecked")
177 public T setDynamic(final boolean dynamic)
178 {
179 this.dynamic = dynamic;
180 return (T) this;
181 }
182
183 @Override
184 public OrientedPoint2d getLocation()
185 {
186 if (this.location == null || this.dynamic)
187 {
188 Point2d p = this.source.getLocation();
189 if (p instanceof Oriented)
190 {
191
192 double a = Angle.normalizePi(((Oriented<?>) p).getDirZ());
193 if (a > Math.PI / 2.0 || a < -0.99 * Math.PI / 2.0)
194 {
195 a += Math.PI;
196 }
197 this.location = new OrientedPoint2d(p, a);
198 }
199 else
200 {
201 this.location = new OrientedPoint2d(p, 0.0);
202 }
203 }
204 return this.location;
205 }
206
207 @Override
208 public final Bounds2d getBounds()
209 {
210 return new Bounds2d(2.0, 2.0);
211 }
212
213 @Override
214 public Polygon2d getContour()
215 {
216 return new Polygon2d(new double[] {-1.0, 1.0, 1.0, -1.0}, new double[] {-1.0, -1.0, 1.0, 1.0});
217 }
218
219
220
221
222
223
224 @SuppressWarnings("checkstyle:designforextension")
225 public void paint(final Graphics2D graphics, final ImageObserver observer)
226 {
227 double scale = Math.sqrt(graphics.getTransform().getDeterminant());
228 Rectangle2D scaledFontRectangle;
229 String str = this.text.get();
230 synchronized (this.font)
231 {
232 if (!this.scaleDependentRendering.isRendered(scale))
233 {
234 return;
235 }
236 if (scale < this.minFontSize / this.fontSize)
237 {
238 graphics.setFont(this.font.deriveFont((float) (this.minFontSize / scale)));
239 FontMetrics fm = graphics.getFontMetrics();
240 scaledFontRectangle = fm.getStringBounds(str, graphics);
241 }
242 else if (scale > this.maxFontSize / this.fontSize)
243 {
244 graphics.setFont(this.font.deriveFont((float) (this.maxFontSize / scale)));
245 FontMetrics fm = graphics.getFontMetrics();
246 scaledFontRectangle = fm.getStringBounds(str, graphics);
247 }
248 else
249 {
250 graphics.setFont(this.font);
251 FontMetrics fm = graphics.getFontMetrics();
252 scaledFontRectangle = fm.getStringBounds(str, graphics);
253 }
254 Color useColor = this.color;
255 if (null != this.background && isSimilar(useColor, this.background.getBackgroundColor()))
256 {
257
258 if (Color.BLACK.equals(useColor))
259 {
260 useColor = Color.WHITE;
261 }
262 else
263 {
264 useColor = Color.BLACK;
265 }
266 }
267
268 float dxText =
269 this.textAlignment.equals(TextAlignment.LEFT) ? 0.0f : this.textAlignment.equals(TextAlignment.CENTER)
270 ? (float) -scaledFontRectangle.getWidth() / 2.0f : (float) -scaledFontRectangle.getWidth();
271 Object antialias = graphics.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
272 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
273 if (null != this.background)
274 {
275
276
277
278 double r = scaledFontRectangle.getHeight() / 2.0;
279 double dh = scaledFontRectangle.getHeight() / 5.0;
280 Shape s = new RoundRectangle2D.Double(this.dx - scaledFontRectangle.getWidth() - dxText,
281 this.dy + dh - scaledFontRectangle.getHeight(), scaledFontRectangle.getWidth(),
282 scaledFontRectangle.getHeight(), r, r);
283 Color bg = this.background.getBackgroundColor();
284 graphics.setColor(new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 92));
285 graphics.fill(s);
286 }
287 graphics.setColor(useColor);
288 graphics.drawString(str, dxText + this.dx, -this.dy);
289
290 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias);
291 }
292 }
293
294
295
296
297
298
299
300 private boolean isSimilar(final Color color1, final Color color2)
301 {
302 int r = color1.getRed() - color2.getRed();
303 int g = color1.getGreen() - color2.getGreen();
304 int b = color1.getBlue() - color2.getBlue();
305 return r * r + g * g + b * b < 2000;
306
307 }
308
309
310
311
312
313 public final void destroy(final Contextualized contextProvider)
314 {
315 this.animationImpl.destroy(contextProvider);
316 }
317
318
319
320
321
322 protected final L getSource()
323 {
324 return this.source;
325 }
326
327
328
329
330
331 protected final float getDx()
332 {
333 return this.dx;
334 }
335
336
337
338
339
340 protected final float getDy()
341 {
342 return this.dy;
343 }
344
345
346
347
348
349
350 protected final void setXY(final float x, final float y)
351 {
352 this.dx = x;
353 this.dy = y;
354 }
355
356 @Override
357 public double getZ() throws RemoteException
358 {
359 return DrawLevel.LABEL.getZ();
360 }
361
362
363
364
365
366 protected final TextAlignment getTextAlignment()
367 {
368 return this.textAlignment;
369 }
370
371
372
373
374
375 protected final float getFontSize()
376 {
377 return this.fontSize;
378 }
379
380
381
382
383
384 protected final Font getFont()
385 {
386 return this.font;
387 }
388
389
390
391
392
393 protected final String getText()
394 {
395 return this.text.get();
396 }
397
398
399
400
401
402 public final void setText(final Supplier<String> text)
403 {
404 this.text = text;
405 }
406
407
408
409
410
411 protected final Color getColor()
412 {
413 return this.color;
414 }
415
416
417
418
419
420 protected final void setColor(final Color color)
421 {
422 this.color = color;
423 }
424
425
426
427
428
429 public final boolean isFlip()
430 {
431 return this.animationImpl.isFlip();
432 }
433
434
435
436
437
438 public final void setFlip(final boolean flip)
439 {
440 this.animationImpl.setFlip(flip);
441 }
442
443
444
445
446
447 public final boolean isRotate()
448 {
449 return this.animationImpl.isRotate();
450 }
451
452
453
454
455
456 public final void setRotate(final boolean rotate)
457 {
458 this.animationImpl.setRotate(rotate);
459
460 }
461
462
463
464
465
466 public final boolean isScale()
467 {
468 return this.animationImpl.isScale();
469 }
470
471
472
473
474
475 public final void setScale(final boolean scale)
476 {
477 this.animationImpl.setScale(scale);
478 }
479
480
481
482
483
484 public final boolean isTranslate()
485 {
486 return this.animationImpl.isTranslate();
487 }
488
489
490
491
492
493 public final void setTranslate(final boolean translate)
494 {
495 this.animationImpl.setTranslate(translate);
496 }
497
498
499
500
501
502
503
504
505
506
507
508
509 private static class AnimationImpl extends Renderable2d<TextAnimation<?, ?>>
510 {
511
512 private static final long serialVersionUID = 20170400L;
513
514
515
516
517
518
519 AnimationImpl(final TextAnimation<?, ?> source, final Contextualized contextualized)
520 {
521 super(source, contextualized);
522 }
523
524 @Override
525 public final void paint(final Graphics2D graphics, final ImageObserver observer)
526 {
527 getSource().paint(graphics, observer);
528 }
529
530 @Override
531 public boolean contains(final Point2d pointWorldCoordinates, final Bounds2d extent)
532 {
533 return false;
534 }
535
536 @Override
537 public final String toString()
538 {
539 return "TextAnimation.AnimationImpl []";
540 }
541 }
542
543
544
545
546
547 protected ScaleDependentRendering getScaleDependentRendering()
548 {
549 return this.scaleDependentRendering;
550 }
551
552
553
554
555 public interface ContrastToBackground
556 {
557
558
559
560
561 Color getBackgroundColor();
562 }
563
564
565
566
567 public interface ScaleDependentRendering
568 {
569
570
571
572
573
574 boolean isRendered(double scale);
575 }
576
577
578 public static final ScaleDependentRendering RENDERALWAYS = new ScaleDependentRendering()
579 {
580 @Override
581 public boolean isRendered(final double scale)
582 {
583 return true;
584 }
585 };
586
587
588 public static final ScaleDependentRendering RENDERWHEN1 = new ScaleDependentRendering()
589 {
590 @Override
591 public boolean isRendered(final double scale)
592 {
593 return scale >= 1.0;
594 }
595 };
596
597
598 public static final ScaleDependentRendering RENDERWHEN10 = new ScaleDependentRendering()
599 {
600 @Override
601 public boolean isRendered(final double scale)
602 {
603 return scale >= 0.1;
604 }
605 };
606
607
608 public static final ScaleDependentRendering RENDERWHEN100 = new ScaleDependentRendering()
609 {
610 @Override
611 public boolean isRendered(final double scale)
612 {
613 return scale >= 0.01;
614 }
615 };
616
617 }