概要

AffineTransformを使用してPath2Dを変換、組み合わせて7セグメントデジタル時計用の数字を作成します。

スクリーンショット

Swing/SevenSegmentDigitalClock.png

サンプルコード

class DigitalNumber {
  private final double isosceles;
  private final double dx;
  private final double dy;
  private final double width;
  private final double height;
  private final Rectangle rect = new Rectangle();
  public static final Color OFF = new Color(0xCC_CC_CC);
  public static final Color ON = Color.DARK_GRAY;
  public static final Color BGC = Color.LIGHT_GRAY;
  private final List<Set<Seg>> numbers = Arrays.asList(
      EnumSet.of(Seg.A, Seg.B, Seg.C, Seg.D, Seg.E, Seg.F),
      EnumSet.of(Seg.B, Seg.C),
      EnumSet.of(Seg.A, Seg.B, Seg.D, Seg.E, Seg.G),
      EnumSet.of(Seg.A, Seg.B, Seg.C, Seg.D, Seg.G),
      EnumSet.of(Seg.B, Seg.C, Seg.F, Seg.G),
      EnumSet.of(Seg.A, Seg.C, Seg.D, Seg.F, Seg.G),
      EnumSet.of(Seg.A, Seg.C, Seg.D, Seg.E, Seg.F, Seg.G),
      EnumSet.of(Seg.A, Seg.B, Seg.C),
      EnumSet.of(Seg.A, Seg.B, Seg.C, Seg.D, Seg.E, Seg.F, Seg.G),
      EnumSet.of(Seg.A, Seg.B, Seg.C, Seg.D, Seg.F, Seg.G));
  private Set<Seg> led = EnumSet.noneOf(Seg.class);

  protected DigitalNumber(double dx, double dy, double isosceles) {
    this.isosceles = isosceles;
    this.dx = dx;
    this.dy = dy;
    this.width = 2d * isosceles;
    this.height = width + isosceles;
    rect.setLocation((int) dx, (int) dy);
    rect.setSize((int) (width + 3 * isosceles), (int) (height * 2));
  }

  public Rectangle getBounds() {
    return rect;
  }

  public void setNumber(int num) {
    led = numbers.get(num);
  }

  public void turnOffNumber() {
    led.clear();
  }

  public void drawNumber(Graphics2D g2) {
    EnumSet.allOf(Seg.class).forEach(s -> {
      g2.setColor(led.contains(s) ? ON : OFF);
      Shape seg = s.getShape(dx, dy, width, height, isosceles);
      g2.fill(seg);
      g2.setColor(BGC);
      g2.draw(seg);
    });
  }
}
View in GitHub: Java, Kotlin

解説

  • Seven-segment display - Wikipediaのセグメント配置を参考にセグメントA..Genumで作成
    • enum Seg { A, B, C, D, E, F, G }
  • EnumSetを使用して数字に対してどのセグメントが点灯しているかを定義
    • 例えば数字の2EnumSet.of(Seg.A, Seg.B, Seg.D, Seg.E, Seg.G)で表現する
  • セグメントFの下頂点(底辺と高さが2 * isoscelesとなる二等辺三角形の頂点)を原点として、Path2D.Double()で五角形の縦長セグメントを作成
    private static Path2D vert(double height, double isosceles) {
      Path2D path = new Path2D.Double();
      path.moveTo(0d, 0d);
      path.lineTo(isosceles, -isosceles);
      path.lineTo(isosceles, -isosceles - height);
      path.lineTo(-isosceles, -isosceles - height - isosceles * 2);
      path.lineTo(-isosceles, -isosceles);
      path.closePath();
      return path;
    }
    
  • セグメントEはセグメントFを原点の下頂点で上下反転して作成
    • AffineTransform at = AffineTransform.getTranslateInstance(x, y); at.scale(1, -1);で作成したAffineTransformを使用しat.createTransformedShape(shapeF)で変換
  • セグメントBは、セグメントFを原点の下頂点で左右反転してセグメントGの幅だけx軸方向に移動してを作成
    • AffineTransform at = AffineTransform.getTranslateInstance(x + widthG, y); at.scale(-1, 1);で作成したAffineTransformを使用しat.createTransformedShape(shapeF)で変換
  • セグメントCは、セグメントFを原点の下頂点で上下左右を反転してセグメントGの幅だけx軸方向に移動してを作成
    • AffineTransform at = AffineTransform.getTranslateInstance(x + widthG, y); at.scale(-1, -1);で作成したAffineTransformを使用しat.createTransformedShape(shapeF)で変換
  • セグメントGはその左頂点(底辺と高さが2 * isoscelesとなる二等辺三角形の頂点)を原点としてPath2D.Double()で六角形の横長セグメントを作成
    private static Path2D horiz1(double width, double isosceles) {
      Path2D path = new Path2D.Double();
      path.moveTo(0, 0);
      path.lineTo(isosceles, isosceles);
      path.lineTo(isosceles + width, isosceles);
      path.lineTo(isosceles + width + isosceles, 0);
      path.lineTo(isosceles + width, -isosceles);
      path.lineTo(isosceles, -isosceles);
      path.closePath();
      return path;
    }
    
  • セグメントAは上辺が下辺より3 * isosceles長くなる等脚台形をPath2D.Double()で作成し、セグメントFの高さだけy軸方向に移動、原点は左右の辺の中心に設定
    private static Path2D horiz2(double width, double isosceles) {
      Path2D path = new Path2D.Double();
      path.moveTo(isosceles, isosceles);
      path.lineTo(isosceles + width, isosceles);
      path.lineTo(3 * isosceles + width, -isosceles);
      path.lineTo(-isosceles, -isosceles);
      path.closePath();
      return path;
    }
    
  • セグメントDはセグメントAを上下反転してセグメントFの高さだけy軸方向に移動して作成
    • AffineTransform at = AffineTransform.getTranslateInstance(x, y + heightF); at.scale(1, -1);で作成したAffineTransformを使用しat.createTransformedShape(shapeA)で変換

  • DigitalNumber6個配置して24時間表記のデジタル時計を作成
  • 時間と分の区切りドットは250ミリ秒で点滅するようTimerで設定
  • 各セグメント間の隙間はBasicStrokeを設定してPath2Dを線描することで表現
  • 時計全体をg2.shear(-.1, 0d);でシャーリング変換してイタリック風に表示
  • 時計全体がJPanelに収まるよう以下のようなスケールを設定
    double sv = getWidth() / (h1.getBounds().width * 8d);
    g2.scale(sv, sv);
    

参考リンク

コメント