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