首页 > 解决方案 > SWT GC 为圆的上半部分和下半部分填充颜色

问题描述

我正在寻找一个圆圈,用一条线分隔圆的上半部分和下半部分,然后使用 GC 填充上半部分和下半部分的颜色。

如果线像这样穿过圆心,我可以做到这一点(要旋转线,我可以简单地更改 fillArc() 的 startAngle):

在此处输入图像描述

但是,如果线垂直向上或向下移动和/或旋转,我无法像这样填充上半部分和下半部分:

在此处输入图像描述

如果线条向上或向下移动和/或旋转,有谁知道如何填充上半部分和下半部分?

这是我的第一张图片的代码:

// Fill top half with red color
gc.setBackground( event.display.getSystemColor( SWT.COLOR_RED ) );
gc.fillArc( xCoord - ( diameter / 2 ),
            yCoord - ( diameter / 2 ),
            diameter,
            diameter,
            0,
            180 );

// Fill bottom half with blue color
gc.setBackground( event.display.getSystemColor( SWT.COLOR_BLUE ) );
gc.fillArc( xCoord - ( diameter/ 2 ),
            yCoord - ( diameter/ 2 ),
            diameter,
            diameter,
            180,
            180 );

// Draw the line separating top half and bottom half
Transform transform = new Transform( event.display );
transform.translate( xCoord, yCoord );
transform .rotate( 0);
gc.setTransform( transform );
gc.drawLine( -diameter / 2, 0, diameter / 2, 0 );
transform.dispose();

标签: javaswt

解决方案


一种可能的方法是:

  1. 绘制没有剪裁的完整蓝色圆圈
  2. 绘制由计算(并且可能旋转)的矩形裁剪的完整红色圆圈,该矩形将覆盖蓝色圆圈的一部分
  3. 绘制圆形轮廓
  4. 使用前一个剪切矩形的适当部分绘制分隔线并由圆形轮廓剪切

我创建了一个实现这个解决方案的类和一个小程序来测试它。

对于第 2 点,我发现使用Tranform来执行旋转是非常有问题的,因为它会变换整个显示,而且我无法找到将变换限制在有限区域的方法。相反,我使用Eclipse GEF创建了一个Rectangle,将其旋转并转换为PathData可用于剪辑的。

对于第 4 点,我重用了PathData第 2 点的起点来绘制剪切矩形的底部段,这相当于两种颜色之间的分隔线。为了避免在圆外绘制部分线段,我用圆轮廓剪裁了它。

这是结果:

分离圆测试

这是测试程序,使用箭头键移动/旋转分隔线:

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

import static org.eclipse.swt.events.KeyListener.keyPressedAdapter;

public class SeparatedCircleTest {

    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setSize(600, 600);
        shell.setLayout(new FillLayout());

        // double buffering to avoid flickering while redrawing the circle
        final SeparatedCircle separatedCircle = new SeparatedCircle(shell, SWT.DOUBLE_BUFFERED, 300, 300, 200, 0, 0.f);

        // to move/rotate the separation
        separatedCircle.addKeyListener(keyPressedAdapter(e -> {
            if(e.keyCode == SWT.ARROW_UP) {
                separatedCircle.setySeparationDelta(separatedCircle.getySeparationDelta() - 5);
            } else if(e.keyCode == SWT.ARROW_DOWN) {
                separatedCircle.setySeparationDelta(separatedCircle.getySeparationDelta() + 5);
            } else if(e.keyCode == SWT.ARROW_LEFT) {
                separatedCircle.setSeparationAngle(separatedCircle.getSeparationAngle() + 5.f);
            } else if(e.keyCode == SWT.ARROW_RIGHT) {
                separatedCircle.setSeparationAngle(separatedCircle.getSeparationAngle() - 5.f);
            }

            if(separatedCircle.needRedraw()) {
                separatedCircle.redraw();
            }
        }));

        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) display.sleep();
        }
        display.dispose();
    }
}

这是实现类:

import org.eclipse.gef.geometry.convert.swt.Geometry2SWT;
import org.eclipse.gef.geometry.euclidean.Angle;
import org.eclipse.gef.geometry.planar.Polygon;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.PathData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;

public class SeparatedCircle extends Canvas {

    private int xCoord;
    private int yCoord;
    private int diameter;
    private int ySeparationDelta;
    private float separationAngle;

    private boolean needRedraw;
    private Rectangle circleBounds;
    private PathData clippingData;

    public SeparatedCircle(Composite parent, int style, int x, int y, int diameter, int ySeparationDelta, float separationAngle) {
        super(parent, style);

        xCoord = x;
        yCoord = y;
        this.diameter = diameter;
        this.ySeparationDelta = ySeparationDelta;
        this.separationAngle = separationAngle;

        needRedraw = true;

        addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                paint(e);
            }
        });
    }

    private void paint(PaintEvent event) {

        // if some variable changed, we recalculate the bounds
        if(needRedraw) {
            calculateBounds();
            needRedraw = false;
        }

        GC gc = event.gc;

        // enable high quality drawing
        gc.setAntialias(SWT.ON);
        gc.setInterpolation(SWT.HIGH);

        // draw the first circle, no clipping
        gc.setBackground( event.display.getSystemColor( SWT.COLOR_BLUE ) );
        gc.fillOval(circleBounds.x, circleBounds.y, circleBounds.width, circleBounds.height);

        // clipping for the second circle
        Path clipping = new Path(gc.getDevice(), clippingData);
        gc.setClipping(clipping);
        clipping.dispose();

        // draw the second circle
        gc.setBackground( event.display.getSystemColor( SWT.COLOR_RED ) );
        gc.fillOval(circleBounds.x, circleBounds.y, circleBounds.width, circleBounds.height);

        // remove the clipping
        gc.setClipping((Rectangle) null);

        // draw the circle outline
        gc.setForeground(event.display.getSystemColor( SWT.COLOR_BLACK ));
        gc.setLineWidth(4);
        gc.drawOval(circleBounds.x, circleBounds.y, circleBounds.width, circleBounds.height);

        // clipping for the separation line
        Path circlePath = new Path(gc.getDevice());
        circlePath.addArc(circleBounds.x, circleBounds.y, circleBounds.width, circleBounds.height, 0.f, 360.f);
        gc.setClipping(circlePath);
        circlePath.dispose();

        // draw the separation line
        // we want to draw the bottom segment of the clipping rectangle (the third segment), so we use its third and fourth point
        gc.drawLine(
                (int) clippingData.points[4], // third point x
                (int) clippingData.points[5], // third point y
                (int) clippingData.points[6], // fourth point x
                (int) clippingData.points[7]  // fourth point y
        );
    }

    private void calculateBounds() {
        circleBounds = calculateCircleBounds();
        clippingData = calculateClipping();
    }

    private Rectangle calculateCircleBounds() {
        return new Rectangle(calculateLeft(), calculateTop(), diameter, diameter);
    }

    private int calculateLeft() {
        return xCoord - ( diameter / 2 );
    }

    private int calculateTop() {
        return yCoord - ( diameter / 2 );
    }

    private PathData calculateClipping() {

        // create the clipping rectangle
        org.eclipse.gef.geometry.planar.Rectangle rectangle = new org.eclipse.gef.geometry.planar.Rectangle(
                circleBounds.x, circleBounds.y, circleBounds.width, calculateClippingRectangleHeight());

        // rotate it, using the center of our circle as its point of rotation
        Polygon rotatedRectangle = rectangle.getRotatedCCW(Angle.fromDeg(separationAngle), xCoord, yCoord);

        // convert the rotated rectangle to PathData
        return Geometry2SWT.toSWTPathData(rotatedRectangle.toPath());
    }

    private int calculateClippingRectangleHeight() {
        return circleBounds.height / 2 + ySeparationDelta;
    }

    public int getxCoord() {
        return xCoord;
    }

    public void setxCoord(int xCoord) {
        this.xCoord = xCoord;
        needRedraw = true;
    }

    public int getyCoord() {
        return yCoord;
    }

    public void setyCoord(int yCoord) {
        this.yCoord = yCoord;
        needRedraw = true;
    }

    public int getDiameter() {
        return diameter;
    }

    public void setDiameter(int diameter) {
        this.diameter = diameter;
        needRedraw = true;
    }

    public int getySeparationDelta() {
        return ySeparationDelta;
    }

    public void setySeparationDelta(int ySeparationDelta) {
        this.ySeparationDelta = ySeparationDelta;
        needRedraw = true;
    }

    public float getSeparationAngle() {
        return separationAngle;
    }

    public void setSeparationAngle(float separationAngle) {
        this.separationAngle = separationAngle;
        needRedraw = true;
    }

    public boolean needRedraw() {
        return needRedraw;
    }

}

要使用 GEF:

要使用 GEF,您只需要包含以下 jar:

org.eclipse.gef.geometry.convert.swt.Geometry2SWT<version>.jar
org.eclipse.gef.geometry<version>.jar

您可以从此处的构建中的“插件”文件夹中检索它们:https ://www.eclipse.org/gef/downloads/index.php 。

选择最新版本并单击更新站点链接以下载完整的 zip。


推荐阅读