2D Painting

The painting system

JWt provides a vector graphics painting system, which depending on the browser support, uses inline SVG, inline VML, HTML5 <canvas> or a raster image to paint the graphics.

The JWt API provides different classes that provide support for vector graphics painting. To use the paint system, you need to specialize WPaintedWidget and use a WPainter to paint the contents of the widget inside its WPaintedWidget.paintEvent().

In addition, a PDF backend is included in the library, which can be used

  • to make a PDF version of a painting, or
  • to embed a painting in a PDF document.

To use inline SVG, you need to enable XHTML support in your configuration file (wt_config.xml) by enabling send-xhtml-mimetype .

Vector graphics painting class

The painter class WPainter provides a vector graphics interface for painting. It has to be used in conjunction with a WPaintDevice, onto which it paints:

WSvgImage and WVmlImage are paint devices for rendering using native vector graphics. The benefit of vector graphics is a lower bandwidth usage compared to raster images; independent of the image size. To start painting on a device, either pass the device through the constructor, or use begin().

A typical use is to instantiate a WPainter from within a specialized WPaintedWidget.paintEvent() implementation, to paint on the given paint device, but you can also use a painter to paint directly to a particular paint device of choice, for example to create SVG, PDF or PNG images (as resources). A painted widget can dynamically be updated as shown in the following example.

Example
source
  class MyPaintedWidget extends WPaintedWidget {
    private static Logger logger = LoggerFactory.getLogger(MyPaintedWidget.class);

    MyPaintedWidget(WContainerWidget parentContainer) {
      super();
      this.end_ = 100;
      this.resize(new WLength(200), new WLength(60));
      if (parentContainer != null) parentContainer.addWidget(this);
    }

    public MyPaintedWidget() {
      this((WContainerWidget) null);
    }

    void setEnd(int end) {
      this.end_ = end;
      this.update();
    }

    void paintEvent(WPaintDevice paintDevice) {
      WPainter painter = new WPainter(paintDevice);
      painter.setBrush(new WBrush(new WColor(StandardColor.Blue)));
      painter.drawRect(0, 0, this.end_, 50);
    }

    private int end_;
  }

  void PaintingEvent() {
    WContainerWidget container = new WContainerWidget();
    final MyPaintedWidget painting = new MyPaintedWidget((WContainerWidget) container);
    final WSpinBox sb = new WSpinBox((WContainerWidget) container);
    sb.setRange(10, 200);
    sb.setValue(100);
    sb.changed()
        .addListener(
            this,
            () -> {
              painting.setEnd(sb.getValue());
            });
  }

Top

The painter maintains the state such a...

  • the current pen, which you can define with setPen(),
  • the current brush, which you can define with setBrush(),
  • the current font, which you can define with setFont(),
  • the current world transformation matrix, which you can get with worldTransform(),
  • the clipping settings (See setClipping() and setClipPath()).
A particular state can be saved using save() and later restored using restore().

The painting system distinguishes between different coordinate types:

  • device coordinates,
  • logical coordinates, and
  • local coordinates.
Each coordinate type corresponds to a coordinate system:
The device coordinate system
ranges from (0, 0) in the top left corner of the device, to device->width().toPixels(), device->height().toPixels() for the bottom right corner.
The logical coordinate system
defines a coordinate system that may be chosen independent of the geometry of the device, which is convenient to make abstraction of the actual device size.
The local coordinate system
may be different from the logical coordinate system because of a transformation (which you can set with translate(), rotate(), and scale()).
Initially, the local coordinate system coincides with the logical coordinate system, which coincides with the device coordinate system.

The device coordinates are defined in terms of pixels. Even though most underlying devices are actual vector graphics formats, when used in conjunction with a WPaintedWidget, these vector graphics are rendered by the browser onto a pixel-based canvas (like the rest of the user-interface). The coordinates are defined such that integer values correspond to an imaginary raster which separates the individual pixels, as in the figure below.

The device coordinate system for a 6x5 pixel device
The device coordinate system for a 6x5 pixel device

As a consequence, to avoid anti-aliasing effects when drawing straight lines of width one pixel, you will need to use vertices that indicate the middle of a pixel to get a crisp one-pixel wide line, as shown in the above picture.

You can map logical coordinates onto device coordinates by setting a viewPort() and a window(); this defines a viewPort transformation.

You can define how the current local coordinates map onto logical coordinates by changing the world transformation using translate(), rotate(), scale() and setWorldTransform() operations.

The painter provides support for clipping using an arbitrary WPainterPath. Please note that the WVmlImage paint device only has limited support for clipping.

Example
source
  class ShapesWidget extends WPaintedWidget {
    private static Logger logger = LoggerFactory.getLogger(ShapesWidget.class);

    ShapesWidget(WContainerWidget parentContainer) {
      super();
      this.resize(new WLength(310), new WLength(400));
      if (parentContainer != null) parentContainer.addWidget(this);
    }

    public ShapesWidget() {
      this((WContainerWidget) null);
    }

    void paintEvent(WPaintDevice paintDevice) {
      WPainter painter = new WPainter(paintDevice);
      painter.setPen(new WPen(new WColor(StandardColor.Red)));
      painter.drawLine(0, 0, 200, 0);
      painter.drawLine(200, 0, 200, 30);
      painter.drawRect(0, 25, 80, 50);
      painter.setBrush(new WBrush(new WColor(StandardColor.Green)));
      painter.drawRect(100, 25, 80, 50);
      painter.fillRect(220, 25, 80, 50, new WBrush(new WColor(0, 255, 0, 64)));
      painter.drawEllipse(0, 100, 80, 50);
      painter.drawChord(100, 100, 80, 50, 0, 180 * 16);
      painter.drawArc(220, 100, 50, 50, 90 * 16, 90 * 16);
      painter.drawArc(240, 100, 50, 50, 0, 90 * 16);
      painter.drawLine(265, 100, 265, 125);
      painter.drawLine(265, 125, 290, 125);
      WPointF[] points = {
        new WPointF(120, 170),
        new WPointF(160, 170),
        new WPointF(180, 204.6),
        new WPointF(160, 239.2),
        new WPointF(120, 239.2),
        new WPointF(100, 204.6)
      };
      painter.drawPolygon(points, 6);
      WPainterPath filledEllipsePath = new WPainterPath();
      filledEllipsePath.addEllipse(0, 180, 80, 50);
      filledEllipsePath.closeSubPath();
      painter.drawPath(filledEllipsePath);
      WPainterPath filledTrianglePath = new WPainterPath();
      filledTrianglePath.moveTo(0, 270);
      filledTrianglePath.lineTo(80, 270);
      filledTrianglePath.lineTo(0, 350);
      filledTrianglePath.closeSubPath();
      painter.drawPath(filledTrianglePath);
      WPainterPath strokedTrianglePath = new WPainterPath();
      strokedTrianglePath.moveTo(100, 270);
      strokedTrianglePath.lineTo(100, 350);
      strokedTrianglePath.lineTo(20, 350);
      strokedTrianglePath.closeSubPath();
      WPen pen = new WPen();
      pen.setWidth(new WLength(3));
      painter.strokePath(strokedTrianglePath, pen);
      WPainterPath quadraticCurvePath = new WPainterPath();
      quadraticCurvePath.moveTo(250, 150);
      quadraticCurvePath.quadTo(200, 150, 200, 187.5);
      quadraticCurvePath.quadTo(200, 225, 225, 225);
      quadraticCurvePath.quadTo(225, 245, 205, 250);
      quadraticCurvePath.quadTo(235, 245, 240, 225);
      quadraticCurvePath.quadTo(300, 225, 300, 187.5);
      quadraticCurvePath.quadTo(300, 150, 250, 150);
      painter.strokePath(quadraticCurvePath, pen);
      WPainterPath bezierCurvePath = new WPainterPath();
      bezierCurvePath.moveTo(255, 285);
      bezierCurvePath.cubicTo(255, 282, 250, 270, 230, 270);
      bezierCurvePath.cubicTo(200, 270, 200, 307.5, 200, 307.5);
      bezierCurvePath.cubicTo(200, 325, 220, 357, 255, 365);
      bezierCurvePath.cubicTo(290, 347, 310, 325, 310, 307.5);
      bezierCurvePath.cubicTo(310, 307.5, 310, 270, 290, 270);
      bezierCurvePath.cubicTo(265, 270, 255, 282, 255, 285);
      painter.setBrush(new WBrush(new WColor(StandardColor.Red)));
      painter.drawPath(bezierCurvePath);
    }
  }

  void PaintingShapes() {
    WContainerWidget container = new WContainerWidget();
    new ShapesWidget((WContainerWidget) container);
  }

Top

Transformations

Introduction

Besides the different transformation methods which you can apply to a drawing (i.e. translate, rotate, and scale), there are also two other methods which are indispensable once you start generating more complex drawings:

  • WPainter.save() to save the current painter state on a stack, and
  • WPainter.restore() to restore a painter state from the stack.
The painter state that is saved is the current pen, brush, font, shadow, transformation and clipping settings (See WPainter.setClipping() and WPainter.setClipPath()).

Translate the origin

The method WPainter.translate() translates the origin of the logical coordinate system to a new location relative to the current logical coordinate system. It's a good practice to save the painter state before doing any transformations because usually, it's easier to call restore() compared to a reverse translation to return to the original state. In addition, if you're translating in a loop you might end up missing a part of your drawing because it was drawn outside the paint device's edge.

Rotate to the paint device

The method WPainter.rotate() rotates the logical coordinate system around its origin by an angle. The angle is specified in degrees, and positive values are clockwise. Use this method to rotate an object around its origin before it is drawn on the paint device.

Scale to the paint device

The method WPainter.scale() scales the logical coordinate system around its origin, by a factor in the X and Y directions. Use this method to scale what you draw on the paint device.

Example
source
  class TransformationsWidget extends WPaintedWidget {
    private static Logger logger = LoggerFactory.getLogger(TransformationsWidget.class);

    TransformationsWidget(WContainerWidget parentContainer) {
      super();
      this.resize(new WLength(300), new WLength(500));
      if (parentContainer != null) parentContainer.addWidget(this);
    }

    public TransformationsWidget() {
      this((WContainerWidget) null);
    }

    void paintEvent(WPaintDevice paintDevice) {
      WPainter painter = new WPainter(paintDevice);
      painter.setPen(new WPen(new WColor(StandardColor.Red)));
      painter.setBrush(new WBrush(new WColor(StandardColor.Black)));
      painter.save();
      painter.setPen(new WPen(new WColor(StandardColor.White)));
      painter.drawRect(0, 0, 100, 100);
      painter.save();
      painter.setBrush(new WBrush(new WColor(StandardColor.Yellow)));
      painter.drawRect(10, 10, 80, 80);
      painter.save();
      painter.setBrush(new WBrush(new WColor(StandardColor.Red)));
      painter.drawRect(20, 20, 60, 60);
      painter.restore();
      painter.drawRect(30, 30, 40, 40);
      painter.restore();
      painter.drawRect(40, 40, 20, 20);
      painter.restore();
      for (int i = 0; i < 2; i++) {
        painter.save();
        painter.translate(i * 100, 130);
        this.drawFilledPolygon(painter, new WColor(0, 255, 0, 255 - i * 200));
        painter.restore();
      }
      painter.translate(0, 300);
      painter.save();
      painter.translate(90, 0);
      for (int ring = 1; ring < 6; ring++) {
        painter.save();
        painter.setBrush(new WBrush(new WColor(51 * ring, 255 - 51 * ring, 255)));
        for (int j = 0; j < ring * 6; j++) {
          painter.rotate(360 / (ring * 6));
          painter.drawEllipse(0, ring * 12.5, 10, 10);
        }
        painter.restore();
      }
      painter.restore();
      painter.save();
      painter.translate(0, 100);
      this.drawFilledPolygon(painter, new WColor(0, 255, 0, 255));
      painter.translate(100, 0);
      painter.scale(1.2, 1);
      this.drawFilledPolygon(painter, new WColor(0, 255, 0, 55));
      painter.restore();
    }

    void drawFilledPolygon(final WPainter painter, final WColor color) {
      painter.setBrush(new WBrush(color));
      WPointF[] points = {
        new WPointF(20, 0),
        new WPointF(60, 0),
        new WPointF(80, 34.6),
        new WPointF(60, 69.2),
        new WPointF(20, 69.2),
        new WPointF(0, 34.6),
        new WPointF(20, 0)
      };
      painter.drawPolygon(points, 6);
    }
  }

  void PaintingTransformations() {
    WContainerWidget container = new WContainerWidget();
    new TransformationsWidget((WContainerWidget) container);
  }

Reset the transformations

Use the method WPainter.resetTransform() to reset the current transformation to the identity transformation matrix, so that the logical coordinate system coincides with the device coordinate system.

Top

Clipping paths

A clipping path is like a normal WPainterPath but it acts as mask to hide unwanted parts of shapes. The path is specified in local coordinates.

You can set a clipping path with the method setClipPath(). To apply clipping you still have to enable clipping using setClipping(true).

Example
source
  class ClippingWidget extends WPaintedWidget {
    private static Logger logger = LoggerFactory.getLogger(ClippingWidget.class);

    ClippingWidget(WContainerWidget parentContainer) {
      super();
      this.resize(new WLength(310), new WLength(150));
      if (parentContainer != null) parentContainer.addWidget(this);
    }

    public ClippingWidget() {
      this((WContainerWidget) null);
    }

    void paintEvent(WPaintDevice paintDevice) {
      WPainter painter = new WPainter(paintDevice);
      for (int i = 0; i < 2; i++) {
        painter.translate(i * 160, 0);
        painter.fillRect(0, 0, 150, 150, new WBrush(new WColor(StandardColor.Black)));
        WPainterPath path = new WPainterPath();
        path.addEllipse(15, 15, 120, 120);
        painter.fillPath(path, new WBrush(new WColor(StandardColor.Blue)));
        painter.setClipPath(path);
        painter.setClipping(i != 0);
        this.drawStars(painter);
      }
    }

    void drawStar(final WPainter painter, double radius) {
      painter.save();
      WPainterPath circlePath = new WPainterPath();
      circlePath.addEllipse(0, 0, radius, radius);
      circlePath.closeSubPath();
      painter.fillPath(circlePath, new WBrush(new WColor(StandardColor.White)));
      painter.restore();
    }

    void drawStars(final WPainter painter) {
      Random random = new Random();
      random.setSeed(WDate.getCurrentServerDate().getDate().getTime());
      painter.save();
      painter.translate(75, 75);
      for (int star = 1; star < 50; star++) {
        painter.save();
        painter.translate(75 - random.nextInt() % 150 + 1, 75 - random.nextInt() % 150 + 1);
        this.drawStar(painter, Math.max(0, random.nextInt() % 4) + 2);
        painter.restore();
      }
      painter.restore();
    }
  }

  void PaintingClipping() {
    WContainerWidget container = new WContainerWidget();
    new ClippingWidget((WContainerWidget) container);
  }

Top

Style

Until now we used a limited number of line and fill styles. Now we will explore other options to make a drawing more attractive by examples.

  • the difference between filling with a brush and stroking with a pen
  • transparency - drawing semi-transparent shapes using a color with lower opacity
  • painting with a gradient brush
  • rendering line ends with the line style PenCapStyle (flat, square or round)
  • rendering line joins using the style PenJoinStyle (miter, bevel or round)
  • drawing thicker lines using strokePath()
  • drawing crisp lines - even if the line thickness is an odd integer

Example
source
  class StyleWidget extends WPaintedWidget {
    private static Logger logger = LoggerFactory.getLogger(StyleWidget.class);

    StyleWidget(WContainerWidget parentContainer) {
      super();
      this.resize(new WLength(310), new WLength(1140));
      if (parentContainer != null) parentContainer.addWidget(this);
    }

    public StyleWidget() {
      this((WContainerWidget) null);
    }

    void paintEvent(WPaintDevice paintDevice) {
      WPainter painter = new WPainter(paintDevice);
      for (int row = 0; row < 6; row++) {
        for (int col = 0; col < 6; col++) {
          WBrush brush = new WBrush(new WColor(255 - 42 * row, 255 - 42 * col, 0));
          painter.fillRect(row * 25, col * 25, 25, 25, brush);
        }
      }
      painter.translate(0, 160);
      WPen pen = new WPen();
      pen.setWidth(new WLength(3));
      for (int row = 0; row < 6; row++) {
        for (int col = 0; col < 6; col++) {
          WPainterPath path = new WPainterPath();
          path.addEllipse(3 + col * 25, 3 + row * 25, 20, 20);
          pen.setColor(new WColor(0, 255 - 42 * row, 255 - 42 * col));
          painter.strokePath(path, pen);
        }
      }
      painter.translate(0, 160);
      painter.fillRect(0, 0, 150, 37.5, new WBrush(new WColor(StandardColor.Yellow)));
      painter.fillRect(0, 37.5, 150, 37.5, new WBrush(new WColor(StandardColor.Green)));
      painter.fillRect(0, 75, 150, 37.5, new WBrush(new WColor(StandardColor.Blue)));
      painter.fillRect(0, 112.5, 150, 37.5, new WBrush(new WColor(StandardColor.Red)));
      for (int i = 0; i < 10; i++) {
        WBrush brush = new WBrush(new WColor(255, 255, 255, 255 / 10 * i));
        for (int j = 0; j < 4; j++) {
          WPainterPath path = new WPainterPath();
          path.addRect(5 + i * 14, 5 + j * 37.5, 14, 27.5);
          painter.fillPath(path, brush);
        }
      }
      painter.translate(0, 160);
      painter.fillRect(0, 0, 75, 75, new WBrush(new WColor(StandardColor.Yellow)));
      painter.fillRect(75, 0, 75, 75, new WBrush(new WColor(StandardColor.Green)));
      painter.fillRect(0, 75, 75, 75, new WBrush(new WColor(StandardColor.Blue)));
      painter.fillRect(75, 75, 75, 75, new WBrush(new WColor(StandardColor.Red)));
      for (int i = 1; i < 8; i++) {
        WPainterPath path = new WPainterPath();
        path.addEllipse(75 - i * 10, 75 - i * 10, i * 20, i * 20);
        WBrush brush = new WBrush(new WColor(255, 255, 255, 50));
        painter.fillPath(path, brush);
      }
      painter.translate(0, 170);
      painter.setPen(new WPen(PenStyle.None));
      WGradient linGrad = new WGradient();
      linGrad.setLinearGradient(0, 0, 100, 150);
      linGrad.addColorStop(0, new WColor(255, 0, 0, 255));
      linGrad.addColorStop(0.5, new WColor(0, 0, 255, 255));
      linGrad.addColorStop(1, new WColor(0, 255, 0, 255));
      WBrush linearGradientBrush = new WBrush(linGrad);
      painter.setBrush(linearGradientBrush);
      painter.drawRect(0, 0, 100, 150);
      WGradient radGrad = new WGradient();
      radGrad.setRadialGradient(170, 100, 50, 130, 130);
      radGrad.addColorStop(0.2, new WColor(255, 0, 0, 255));
      radGrad.addColorStop(0.9, new WColor(0, 0, 255, 255));
      radGrad.addColorStop(1, new WColor(0, 0, 255, 0));
      WBrush radialGradientBrush = new WBrush(radGrad);
      painter.setBrush(radialGradientBrush);
      painter.drawEllipse(120, 50, 100, 100);
      painter.translate(0, 170);
      for (int i = 0; i < 11; i++) {
        WPainterPath path = new WPainterPath();
        path.moveTo(i * 14, 0);
        path.lineTo(i * 14, 150);
        pen = new WPen();
        pen.setWidth(new WLength(i + 1));
        painter.strokePath(path, pen);
      }
      painter.translate(160, 0);
      for (int i = 0; i < 11; i++) {
        WPainterPath path = new WPainterPath();
        if (i % 2 == 0) {
          path.moveTo(i * 14 - 0.5, 0);
          path.lineTo(i * 14 - 0.5, 150);
        } else {
          path.moveTo(i * 14, 0);
          path.lineTo(i * 14, 150);
        }
        pen = new WPen();
        pen.setCapStyle(PenCapStyle.Flat);
        pen.setWidth(new WLength(i + 1));
        painter.strokePath(path, pen);
      }
      painter.translate(-160, 170);
      WPainterPath guidePath = new WPainterPath();
      guidePath.moveTo(0, 10);
      guidePath.lineTo(150, 10);
      guidePath.moveTo(0, 140);
      guidePath.lineTo(150, 140);
      pen = new WPen(new WColor(StandardColor.Blue));
      painter.strokePath(guidePath, pen);
      List<WPainterPath> paths = new ArrayList<WPainterPath>();
      for (int i = 0; i < 3; i++) {
        WPainterPath path = new WPainterPath();
        path.moveTo(25 + i * 50, 10);
        path.lineTo(25 + i * 50, 140);
        paths.add(path);
      }
      pen = new WPen();
      pen.setWidth(new WLength(20));
      pen.setCapStyle(PenCapStyle.Flat);
      painter.strokePath(paths.get(0), pen);
      pen = new WPen();
      pen.setWidth(new WLength(20));
      pen.setCapStyle(PenCapStyle.Square);
      painter.strokePath(paths.get(1), pen);
      pen = new WPen();
      pen.setWidth(new WLength(20));
      pen.setCapStyle(PenCapStyle.Round);
      painter.strokePath(paths.get(2), pen);
      painter.translate(0, 170);
      paths.clear();
      for (int i = 0; i < 3; i++) {
        WPainterPath path = new WPainterPath();
        path.moveTo(15, 5 + i * 40);
        path.lineTo(45, 45 + i * 40);
        path.lineTo(75, 5 + i * 40);
        path.lineTo(105, 45 + i * 40);
        path.lineTo(135, 5 + i * 40);
        paths.add(path);
      }
      pen = new WPen();
      pen.setWidth(new WLength(20));
      pen.setJoinStyle(PenJoinStyle.Miter);
      painter.strokePath(paths.get(0), pen);
      pen = new WPen();
      pen.setWidth(new WLength(20));
      pen.setJoinStyle(PenJoinStyle.Bevel);
      painter.strokePath(paths.get(1), pen);
      pen = new WPen();
      pen.setWidth(new WLength(20));
      pen.setJoinStyle(PenJoinStyle.Round);
      painter.strokePath(paths.get(2), pen);
    }
  }

  void PaintingStyle() {
    WContainerWidget container = new WContainerWidget();
    new StyleWidget((WContainerWidget) container);
  }

Top

Images

One of the more fun features of WPainter is that you can render one or more images on a painting using the method drawImage(). Images can be used for dynamic photo compositing or as a background for a drawing (e.g. with shapes, graphs, etc.). An image is specified in terms of a URL, and the width and height.

An image can be drawn in different ways:

  • Draw an image at a starting point.
  • Draw a part of an image at a starting point.
  • Draw an image in a rectangle and scale it to the width and height of it.
  • Draw a part of an image in a rectangle and scale it to the width and height of it.
Please note that the image can become blurry when scaling up or grainy when scaling down too much.

Example
source
  class PaintingImagesWidget extends WPaintedWidget {
    private static Logger logger = LoggerFactory.getLogger(PaintingImagesWidget.class);

    PaintingImagesWidget(WContainerWidget parentContainer) {
      super();
      this.resize(new WLength(639), new WLength(1310));
      if (parentContainer != null) parentContainer.addWidget(this);
    }

    public PaintingImagesWidget() {
      this((WContainerWidget) null);
    }

    void paintEvent(WPaintDevice paintDevice) {
      WPainter painter = new WPainter(paintDevice);
      WPainter.Image image = new WPainter.Image("pics/sintel_trailer.jpg", 639, 354);
      painter.drawImage(0.0, 0.0, image);
      painter.drawImage(0.0, 364.0, image, 110.0, 75.0, 130.0, 110.0);
      WPointF location = new WPointF(0.0, 484.0);
      WRectF sourceRect = new WRectF(110.0, 75.0, 130.0, 110.0);
      painter.drawImage(location, image, sourceRect);
      WRectF destinationRect = new WRectF(0.0, 604.0, 130.0, 110.0);
      painter.drawImage(destinationRect, image);
      sourceRect = new WRectF(60.0, 80.0, 220.0, 180.0);
      destinationRect = new WRectF(0.0, 724.0, 130.0, 110.0);
      painter.drawImage(destinationRect, image, sourceRect);
      sourceRect = new WRectF(294.0, 226.0, 265.0, 41.0);
      destinationRect = new WRectF(0.0, 844.0, 639.0, 110.0);
      painter.drawImage(destinationRect, image, sourceRect);
      painter.translate(0, 964);
      painter.drawImage(0.0, 0.0, image);
      WPainterPath path = new WPainterPath();
      path.addEllipse(369, 91, 116, 116);
      path.addRect(294, 226, 265, 41);
      path.moveTo(92, 330);
      path.lineTo(66, 261);
      path.lineTo(122, 176);
      path.lineTo(143, 33);
      path.lineTo(164, 33);
      path.lineTo(157, 88);
      path.lineTo(210, 90);
      path.lineTo(263, 264);
      path.lineTo(228, 330);
      path.lineTo(92, 330);
      WPen pen = new WPen(new WColor(StandardColor.Red));
      pen.setWidth(new WLength(3));
      painter.strokePath(path, pen);
    }
  }

  void PaintingImages() {
    WContainerWidget container = new WContainerWidget();
    new PaintingImagesWidget((WContainerWidget) container);
  }

Top

Client side interaction and repainting

In order to avoid server round-trips, it's possible to change some of the properties of a WPaintedWidget and redraw it entirely on the client side. The interactive features of WCartesianChart use this functionality, for example.

Example
source
  class PaintingInteractiveWidget extends WPaintedWidget {
    private static Logger logger = LoggerFactory.getLogger(PaintingInteractiveWidget.class);

    PaintingInteractiveWidget(WContainerWidget parentContainer) {
      super();
      this.rotateSlot = new JSlot(1, this);
      this.transform = null;
      this.resize(new WLength(300), new WLength(300));
      this.transform = this.createJSTransform();
      this.rotateSlot.setJavaScript(
          "function(o,e,deg) {if ("
              + this.getObjJsRef()
              + ") {var rad = deg / 180 * Math.PI;var c = Math.cos(rad);var s = Math.sin(rad);"
              + this.transform.getJsRef()
              + " = [c,-s,s,c,0,0];"
              + this.getRepaintSlot().execJs()
              + ";}}");
      if (parentContainer != null) parentContainer.addWidget(this);
    }

    public PaintingInteractiveWidget() {
      this((WContainerWidget) null);
    }

    void rotate(int degrees) {
      double radians = degrees / 180.0 * 3.14159265358979323846;
      double c = Math.cos(radians);
      double s = Math.sin(radians);
      this.transform.setValue(new WTransform(c, -s, s, c, 0, 0));
      this.update();
    }

    public JSlot rotateSlot;

    void paintEvent(WPaintDevice paintDevice) {
      WPainter painter = new WPainter(paintDevice);
      painter.translate(150, 150);
      WPen pen = new WPen();
      pen.setWidth(new WLength(5));
      painter.setPen(pen);
      WPainterPath path = new WPainterPath();
      path.moveTo(-50, 100);
      path.lineTo(50, 100);
      path.lineTo(50, 20);
      path.lineTo(100, 20);
      path.lineTo(0, -100);
      path.lineTo(-100, 20);
      path.lineTo(-50, 20);
      path.lineTo(-50, 100);
      path.lineTo(50, 100);
      WPainterPath transformedPath = this.transform.getValue().map(path);
      painter.drawPath(transformedPath);
    }

    private WJavaScriptHandle<WTransform> transform;
  }

  void PaintingInteractive() {
    WContainerWidget container = new WContainerWidget();
    final PaintingInteractiveWidget widget =
        new PaintingInteractiveWidget((WContainerWidget) container);
    final WSpinBox sb = new WSpinBox((WContainerWidget) container);
    sb.setWidth(new WLength(300));
    sb.setRange(0, 360);
    sb.setValue(0);
    final WSlider slider = new WSlider(Orientation.Horizontal, (WContainerWidget) container);
    slider.resize(new WLength(300), new WLength(50));
    slider.setRange(0, 360);
    slider.sliderMoved().addListener(widget.rotateSlot);
    sb.valueChanged()
        .addListener(
            this,
            () -> {
              slider.setValue(sb.getValue());
              widget.rotate(sb.getValue());
            });
    sb.enterPressed()
        .addListener(
            this,
            () -> {
              slider.setValue(sb.getValue());
              widget.rotate(sb.getValue());
            });
    slider
        .valueChanged()
        .addListener(
            this,
            () -> {
              sb.setValue(slider.getValue());
              widget.rotate(slider.getValue());
            });
  }

Top