All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jfree.swt.SWTGraphics2D Maven / Gradle / Ivy

/* ===========================================
 * SWTGraphics2D : a bridge from Java2D to SWT
 * ===========================================
 *
 * (C) Copyright 2006-2021, by Object Refinery Limited and Contributors.
 *
 * Project Info:  https://github.com/jfree/swtgraphics2d
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.]
 *
 * ------------------
 * SWTGraphics2D.java
 * ------------------
 * (C) Copyright 2006-2021, by Henry Proudhon and Contributors.
 *
 * Original Author:  Henry Proudhon (henry.proudhon AT mines-paristech.fr);
 * Contributor(s):   David Gilbert (for Object Refinery Limited);
 *                   Cedric Chabanois (cchabanois AT no-log.org, resource pools);
 *                   Ronnie Duan (https://sourceforge.net/p/jfreechart/bugs/914/);
 *                   Kevin Xu (parts of patch https://sourceforge.net/p/jfreechart/patches/297/);
 *
 */

package org.jfree.swt;

import java.awt.*;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints.Key;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Pattern;
import org.eclipse.swt.graphics.Resource;
import org.eclipse.swt.graphics.Transform;

/**
 * An implementation of the {@code Graphics2D} API targeting an SWT graphics
 * context.
 */
public class SWTGraphics2D extends Graphics2D {

    /** The SWT graphic composite */
    private GC gc;

    /**
     * The rendering hints.  For now, these are not used, but at least the
     * basic mechanism is present.
     */
    private RenderingHints hints;

    /** The user clip. */
    private Shape clip;

    /** Save the initial clip for when the user clip is reset to null. */
    private org.eclipse.swt.graphics.Rectangle swtInitialClip;

    private Font awtFont;

    /** The AWT color that has been set. */
    private Color awtColor;

    /** The AWT paint that has been set. */
    private Paint awtPaint;

    /** The current transform (protect this, only hand out copies). */
    private AffineTransform transform;

    /** 
     * A reference to the compositing rule to apply. This is necessary
     * due to the poor compositing interface of the SWT toolkit. 
     */
    private java.awt.Composite composite;

    /**
     * The device configuration (this is lazily instantiated in the
     * getDeviceConfiguration() method).
     */
    private GraphicsConfiguration deviceConfiguration;

    /** A HashMap to store the SWT color resources. */
    private Map colorsPool = new HashMap();

    /** A HashMap to store the SWT font resources. */
    private Map fontsPool = new HashMap();

    /** A pool for storing SWT pattern resources. */
    private Map patternsPool = new HashMap<>();

    /** A HashMap to store the SWT transform resources. */
    private Map transformsPool = new HashMap();

    /** A List to store the SWT resources. */
    private List resourcePool = new ArrayList();

    /**
     * Creates a new instance.
     *
     * @param gc  the graphics context.
     */
    public SWTGraphics2D(GC gc) {
        super();
        this.gc = gc;
        this.hints = new RenderingHints(null);
        this.transform = new AffineTransform();
        this.composite = AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f);
        this.clip = null;
        this.swtInitialClip = gc.getClipping();
        setStroke(new BasicStroke());
    }

    /**
     * Creates a new graphics object that is a copy of this graphics object.
     *
     * @return A new graphics object.
     */
    public Graphics create() {
        SWTGraphics2D copy = new SWTGraphics2D(this.gc);
        copy.setRenderingHints(getRenderingHints());
        copy.setTransform(getTransform());
        copy.setClip(getClip());
        copy.setPaint(getPaint());
        copy.setColor(getColor());
        copy.setComposite(getComposite());
        copy.setStroke(getStroke());
        copy.setFont(getFont());
        copy.setBackground(getBackground());
        return copy;
    }

    /**
     * Returns the device configuration associated with this
     * {@code Graphics2D}.
     *
     * @return The device configuration (never {@code null}).
     */
    @Override
    public GraphicsConfiguration getDeviceConfiguration() {
        if (this.deviceConfiguration == null) {
            int width = this.gc.getDevice().getBounds().width;
            int height = this.gc.getDevice().getBounds().height;
            this.deviceConfiguration = new SWTGraphicsConfiguration(width,
                    height);
        }
        return this.deviceConfiguration;
    }

    /**
     * Returns the current value for the specified hint key, or
     * {@code null} if no value is set.
     *
     * @param hintKey  the hint key ({@code null} permitted).
     *
     * @return The hint value, or {@code null}.
     *
     * @see #setRenderingHint(RenderingHints.Key, Object)
     */
    @Override
    public Object getRenderingHint(Key hintKey) {
        return this.hints.get(hintKey);
    }

    /**
     * Sets the value for a rendering hint.  For now, this graphics context
     * ignores all hints.
     *
     * @param hintKey  the key ({@code null} not permitted).
     * @param hintValue  the value (must be compatible with the specified key).
     *
     * @throws IllegalArgumentException if {@code hintValue} is not
     *         compatible with the {@code hintKey}.
     *
     * @see #getRenderingHint(RenderingHints.Key)
     */
    @Override
    public void setRenderingHint(Key hintKey, Object hintValue) {
        this.hints.put(hintKey, hintValue);
    }

    /**
     * Returns a copy of the hints collection for this graphics context.
     *
     * @return A copy of the hints collection.
     */
    @Override
    public RenderingHints getRenderingHints() {
        return (RenderingHints) this.hints.clone();
    }

    /**
     * Adds the hints in the specified map to the graphics context, replacing
     * any existing hints.  For now, this graphics context ignores all hints.
     *
     * @param hints  the hints ({@code null} not permitted).
     *
     * @see #setRenderingHints(Map)
     */
    @Override
    public void addRenderingHints(Map hints) {
        this.hints.putAll(hints);
    }

    /**
     * Replaces the existing hints with those contained in the specified
     * map.  Note that, for now, this graphics context ignores all hints.
     *
     * @param hints  the hints ({@code null} not permitted).
     *
     * @see #addRenderingHints(Map)
     */
    @Override
    public void setRenderingHints(Map hints) {
        if (hints == null) {
            throw new NullPointerException("Null 'hints' argument.");
        }
        this.hints = new RenderingHints(hints);
    }

    /**
     * Returns the current paint for this graphics context.
     *
     * @return The current paint.
     *
     * @see #setPaint(Paint)
     */
    @Override
    public Paint getPaint() {
        return this.awtPaint;
    }

    /**
     * Sets the paint for this graphics context.  For now, this graphics
     * context only supports instances of {@link Color} or
     * {@link GradientPaint} (in the latter case there is no real gradient
     * support, the paint used is the {@code Color} returned by
     * {@code getColor1()}).
     *
     * @param paint  the paint ({@code null} permitted, ignored).
     *
     * @see #getPaint()
     * @see #setColor(Color)
     */
    @Override
    public void setPaint(Paint paint) {
        if (paint == null) {
            return;  // to be consistent with other Graphics2D implementations
        }
        this.awtPaint = paint;
        if (paint instanceof Color) {
            this.awtColor = (Color) paint;
            org.eclipse.swt.graphics.Color swtColor = getSwtColorFromPool(this.awtColor);
            this.gc.setForeground(swtColor);
            // handle transparency and compositing.
            if (this.composite instanceof AlphaComposite) {
                AlphaComposite acomp = (AlphaComposite) this.composite;
                switch (acomp.getRule()) {
                    case AlphaComposite.SRC_OVER:
                        this.gc.setAlpha((int) (this.awtColor.getAlpha() * acomp.getAlpha()));
                        break;
                    default:
                        this.gc.setAlpha(this.awtColor.getAlpha());
                        break;
                }
            }
        }
        else if (paint instanceof GradientPaint) {
            GradientPaint gp = (GradientPaint) paint;
            Pattern pattern = fetchOrCreateSWTPattern(gp);
            this.gc.setForegroundPattern(pattern);
            this.gc.setBackgroundPattern(pattern);
        } else if (paint instanceof MultipleGradientPaint) {
            MultipleGradientPaint mgp = (MultipleGradientPaint) paint;
            // how to handle?
        }
        else {
            throw new RuntimeException("Can only handle 'Color' and 'GradientPaint' at present.");
        }
    }

    /**
     * Returns the current color for this graphics context.
     *
     * @return The current color.
     *
     * @see #setColor(Color)
     */
    @Override
    public Color getColor() {
        return this.awtColor;
    }

    /**
     * Sets the foreground color.  This method exists for backwards
     * compatibility in AWT, you should use the
     * {@link #setPaint(java.awt.Paint)} method.
     *
     * @param color  the color ({@code null} permitted but ignored).
     *
     * @see #setPaint(java.awt.Paint)
     */
    @Override
    public void setColor(Color color) {
        if (color == null) {
            return;
        }
        setPaint(color);
    }

    private Color backgroundColor;
    
    /**
     * Sets the background color.
     *
     * @param color  the color.
     */
    @Override
    public void setBackground(Color color) {
        // since this is only used by clearRect(), we don't update the GC yet
        this.backgroundColor = color;
    }

    /**
     * Returns the background color.
     *
     * @return The background color (possibly {@code null})..
     */
    @Override
    public Color getBackground() {
        return this.backgroundColor;
    }

    /**
     * Not implemented - see {@link Graphics#setPaintMode()}.
     */
    @Override
    public void setPaintMode() {
        // TODO Auto-generated method stub
    }

    /**
     * Not implemented - see {@link Graphics#setXORMode(Color)}.
     *
     * @param color  the color.
     */
    @Override
    public void setXORMode(Color color) {
        // TODO Auto-generated method stub
    }

    /**
     * Returns the current composite.
     *
     * @return The current composite.
     *
     * @see #setComposite(Composite)
     */
    @Override
    public Composite getComposite() {
        return this.composite;
    }

    /**
     * Sets the current composite.  This implementation currently supports
     * only the {@link AlphaComposite} class.
     *
     * @param comp  the composite ({@code null} not permitted).
     */
    @Override
    public void setComposite(Composite comp) {
        if (comp == null) {
            throw new IllegalArgumentException("Null 'comp' argument.");
        }
        this.composite = comp;
        if (comp instanceof AlphaComposite) {
            AlphaComposite acomp = (AlphaComposite) comp;
            int alpha = (int) (acomp.getAlpha() * 0xFF);
            this.gc.setAlpha(alpha);
        }
    }

    /**
     * Returns the current stroke for this graphics context.
     *
     * @return The current stroke.
     *
     * @see #setStroke(Stroke)
     */
    @Override
    public Stroke getStroke() {
        return new BasicStroke(this.gc.getLineWidth(),
                toAwtLineCap(this.gc.getLineCap()),
                toAwtLineJoin(this.gc.getLineJoin()));
    }

    /**
     * Sets the stroke for this graphics context.  For now, this implementation
     * only recognises the {@link BasicStroke} class.
     *
     * @param stroke  the stroke ({@code null} not permitted).
     *
     * @see #getStroke()
     */
    @Override
    public void setStroke(Stroke stroke) {
        if (stroke == null) {
            throw new IllegalArgumentException("Null 'stroke' argument.");
        }
        if (stroke instanceof BasicStroke) {
            BasicStroke bs = (BasicStroke) stroke;
            this.gc.setLineWidth((int) bs.getLineWidth());
            this.gc.setLineJoin(toSwtLineJoin(bs.getLineJoin()));
            this.gc.setLineCap(toSwtLineCap(bs.getEndCap()));

            // set the line style to solid by default
            this.gc.setLineStyle(SWT.LINE_SOLID);

            // apply dash style if any
            float[] dashes = bs.getDashArray();
            if (dashes != null) {
                int[] swtDashes = new int[dashes.length];
                for (int i = 0; i < swtDashes.length; i++) {
                    swtDashes[i] = (int) dashes[i];
                }
                this.gc.setLineDash(swtDashes);
            }
        }
        else {
            throw new RuntimeException(
                    "Can only handle 'Basic Stroke' at present.");
        }
    }

    /**
     * Applies the specified clip.
     *
     * @param s  the shape for the clip.
     */
    @Override
    public void clip(Shape s) {
        if (s instanceof Line2D) {
            s = s.getBounds2D();
        }
        if (this.clip == null) {
            setClip(s);
            return;
        }
        Shape ts = this.transform.createTransformedShape(s);
        if (!ts.intersects(this.clip.getBounds2D())) {
            setClip(new Rectangle2D.Double());
            return;
        } else {
            Area a1 = new Area(s);
            Area a2 = new Area(getClip());
            a1.intersect(a2);
            setClip(new Path2D.Double(a1));
        }
    }

    /**
     * Returns the clip bounds.
     *
     * @return The clip bounds (possibly {@code null)}.
     */
    @Override
    public Rectangle getClipBounds() {
        if (getClip() == null) {
            return null;
        }
        return getClip().getBounds();
    }

    /**
     * Sets the clipping to the intersection of the current clip region and
     * the specified rectangle.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the width.
     * @param height  the height.
     */
    @Override
    public void clipRect(int x, int y, int width, int height) {
        setRect(x, y, width, height);
        clip(this.rect);
    }

    /**
     * Returns the user clipping region.  The initial default value is
     * {@code null}.
     *
     * @return The user clipping region (possibly {@code null}).
     *
     * @see #setClip(java.awt.Shape)
     */
    @Override
    public Shape getClip() {
        if (this.clip == null) {
            return null;
        }
        try {
            AffineTransform inv = this.transform.createInverse();
            return inv.createTransformedShape(this.clip);
        } catch (NoninvertibleTransformException ex) {
            return null;
        }
    }

    /**
     * Sets the clip region.
     *
     * @param region  the clip.
     */
    @Override
    public void setClip(Shape region) {
        this.clip = this.transform.createTransformedShape(region);
        if (this.clip != null) {
            Path clipPath = toSwtPath(region);
            this.gc.setClipping(clipPath);
            clipPath.dispose();
        } else {
            AffineTransform saved = getTransform();
            setTransform(null);
            this.gc.setClipping(swtInitialClip);
            setTransform(saved);
        }
    }

    /**
     * Sets the clip region to the specified rectangle.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the width.
     * @param height  the height.
     */
    @Override
    public void setClip(int x, int y, int width, int height) {
        setRect(x, y, width, height);
        setClip(this.rect);
    }

    /**
     * Returns a copy of the current transform.
     *
     * @return A copy of the current transform (never {@code null}).
     *
     * @see #setTransform(java.awt.geom.AffineTransform)
     */
    @Override
    public AffineTransform getTransform() {
        return (AffineTransform) this.transform.clone();
    }

    /**
     * Sets the transform.
     *
     * @param t  the new transform ({@code null} permitted, resets to the
     *     identity transform).
     *
     * @see #getTransform()
     */
    @Override
    public void setTransform(AffineTransform t) {
        if (t == null) {
            this.transform = new AffineTransform();
        } else {
            this.transform = new AffineTransform(t);
        }
        Transform swtTransform = getSwtTransformFromPool(this.transform);
        this.gc.setTransform(swtTransform);
    }

    /**
     * Applies this transform to the existing transform by concatenating it.
     *
     * @param t  the transform ({@code null} not permitted).
     */
    @Override
    public void transform(AffineTransform t) {
        AffineTransform tx = getTransform();
        tx.concatenate(t);
        setTransform(tx);
    }

    /**
     * Applies the translation {@code (tx, ty)}.  This call is delegated
     * to {@link #translate(double, double)}.
     *
     * @param tx  the x-translation.
     * @param ty  the y-translation.
     *
     * @see #translate(double, double)
     */
    @Override
    public void translate(int tx, int ty) {
        translate((double) tx, (double) ty);
    }

    /**
     * Applies the translation {@code (tx, ty)}.
     *
     * @param tx  the x-translation.
     * @param ty  the y-translation.
     */
    @Override
    public void translate(double tx, double ty) {
        AffineTransform t = getTransform();
        t.translate(tx, ty);
        setTransform(t);
    }

    /**
     * Applies a rotation (anti-clockwise) about {@code (0, 0)}.
     *
     * @param theta  the rotation angle (in radians).
     */
    @Override
    public void rotate(double theta) {
        AffineTransform t = getTransform();
        t.rotate(theta);
        setTransform(t);
    }

    /**
     * Applies a rotation (anti-clockwise) about {@code (x, y)}.
     *
     * @param theta  the rotation angle (in radians).
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     */
    @Override
    public void rotate(double theta, double x, double y) {
        translate(x, y);
        rotate(theta);
        translate(-x, -y);
    }

    /**
     * Applies a scale transform.
     *
     * @param sx  the scale factor along the x-axis.
     * @param sy  the scale factor along the y-axis.
     */
    @Override
    public void scale(double sx, double sy) {
        AffineTransform t = getTransform();
        t.scale(sx, sy);
        setTransform(t);
    }

    /**
     * Applies a shear transformation. This is equivalent to the following
     * call to the {@code transform} method:
     * 

*
  • * {@code transform(AffineTransform.getShearInstance(shx, shy));} *
* * @param shx the x-shear factor. * @param shy the y-shear factor. */ @Override public void shear(double shx, double shy) { transform(AffineTransform.getShearInstance(shx, shy)); } /** * Draws the outline of the specified shape using the current stroke and * paint settings. * * @param shape the shape ({@code null} not permitted). * * @see #getPaint() * @see #getStroke() * @see #fill(Shape) */ @Override public void draw(Shape shape) { Path path = toSwtPath(shape); this.gc.drawPath(path); path.dispose(); } /** * Draws a line from (x1, y1) to (x2, y2) using the current stroke * and paint settings. * * @param x1 the x-coordinate for the starting point. * @param y1 the y-coordinate for the starting point. * @param x2 the x-coordinate for the ending point. * @param y2 the y-coordinate for the ending point. * * @see #draw(Shape) */ @Override public void drawLine(int x1, int y1, int x2, int y2) { this.gc.drawLine(x1, y1, x2, y2); } /** * Draws the outline of the polygon specified by the given points, using * the current paint and stroke settings. * * @param xPoints the x-coordinates. * @param yPoints the y-coordinates. * @param npoints the number of points in the polygon. * * @see #draw(Shape) */ @Override public void drawPolygon(int [] xPoints, int [] yPoints, int npoints) { drawPolyline(xPoints, yPoints, npoints); if (npoints > 1) { this.gc.drawLine(xPoints[npoints - 1], yPoints[npoints - 1], xPoints[0], yPoints[0]); } } /** * Draws a sequence of connected lines specified by the given points, using * the current paint and stroke settings. * * @param xPoints the x-coordinates. * @param yPoints the y-coordinates. * @param npoints the number of points in the polygon. * * @see #draw(Shape) */ @Override public void drawPolyline(int[] xPoints, int[] yPoints, int npoints) { if (npoints > 1) { int x0 = xPoints[0]; int y0 = yPoints[0]; int x1 = 0, y1 = 0; for (int i = 1; i < npoints; i++) { x1 = xPoints[i]; y1 = yPoints[i]; this.gc.drawLine(x0, y0, x1, y1); x0 = x1; y0 = y1; } } } /** * Draws an oval that fits within the specified rectangular region. * * @param x the x-coordinate. * @param y the y-coordinate. * @param width the frame width. * @param height the frame height. * * @see #fillOval(int, int, int, int) * @see #draw(Shape) */ @Override public void drawOval(int x, int y, int width, int height) { this.gc.drawOval(x, y, width - 1, height - 1); } /** * Draws an arc that is part of an ellipse that fits within the specified * framing rectangle. * * @param x the x-coordinate. * @param y the y-coordinate. * @param width the frame width. * @param height the frame height. * @param arcStart the arc starting point, in degrees. * @param arcAngle the extent of the arc. * * @see #fillArc(int, int, int, int, int, int) */ @Override public void drawArc(int x, int y, int width, int height, int arcStart, int arcAngle) { this.gc.drawArc(x, y, width - 1, height - 1, arcStart, arcAngle); } /** * Draws a rectangle with rounded corners that fits within the specified * framing rectangle. * * @param x the x-coordinate. * @param y the y-coordinate. * @param width the frame width. * @param height the frame height. * @param arcWidth the width of the arc defining the roundedness of the * rectangle's corners. * @param arcHeight the height of the arc defining the roundedness of the * rectangle's corners. * * @see #fillRoundRect(int, int, int, int, int, int) */ @Override public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { this.gc.drawRoundRectangle(x, y, width - 1, height - 1, arcWidth, arcHeight); } /** * Fills the specified shape using the current paint. * * @param shape the shape ({@code null} not permitted). * * @see #getPaint() * @see #draw(Shape) */ @Override public void fill(Shape shape) { Path path = toSwtPath(shape); // Note that for consistency with the AWT implementation, it is // necessary to switch temporarily the foreground and background // colors if (this.gc.getForegroundPattern() == null) { switchColors(); } if (shape instanceof Path2D) { Path2D p2d = (Path2D) shape; switch (p2d.getWindingRule()) { case Path2D.WIND_EVEN_ODD: this.gc.setFillRule(SWT.FILL_EVEN_ODD); break; case Path2D.WIND_NON_ZERO: this.gc.setFillRule(SWT.FILL_WINDING); break; default: // not recognised } } this.gc.fillPath(path); if (this.gc.getForegroundPattern() == null) { switchColors(); } path.dispose(); } /** * Fill a rectangle area on the SWT graphic composite. * The {@code fillRectangle} method of the {@code GC} * class uses the background color so we must switch colors. * @see java.awt.Graphics#fillRect(int, int, int, int) */ @Override public void fillRect(int x, int y, int width, int height) { this.switchColors(); this.gc.fillRectangle(x, y, width, height); this.switchColors(); } /** * Fills the specified rectangle with the current background color. * * @param x the x-coordinate for the rectangle. * @param y the y-coordinate for the rectangle. * @param width the width. * @param height the height. * * @see #fillRect(int, int, int, int) */ @Override public void clearRect(int x, int y, int width, int height) { Color bgcolor = getBackground(); if (bgcolor == null) { return; // we can't do anything } Paint saved = getPaint(); setPaint(bgcolor); fillRect(x, y, width, height); setPaint(saved); } /** * Fills the specified polygon. * * @param xPoints the x-coordinates. * @param yPoints the y-coordinates. * @param npoints the number of points. */ @Override public void fillPolygon(int[] xPoints, int[] yPoints, int npoints) { int[] pointArray = new int[npoints * 2]; for (int i = 0; i < npoints; i++) { pointArray[2 * i] = xPoints[i]; pointArray[2 * i + 1] = yPoints[i]; } switchColors(); this.gc.fillPolygon(pointArray); switchColors(); } /** * Draws a rectangle with rounded corners that fits within the specified * framing rectangle. * * @param x the x-coordinate. * @param y the y-coordinate. * @param width the frame width. * @param height the frame height. * @param arcWidth the width of the arc defining the roundedness of the * rectangle's corners. * @param arcHeight the height of the arc defining the roundedness of the * rectangle's corners. * * @see #drawRoundRect(int, int, int, int, int, int) */ @Override public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { switchColors(); this.gc.fillRoundRectangle(x, y, width - 1, height - 1, arcWidth, arcHeight); switchColors(); } /** * Fills an oval that fits within the specified rectangular region. * * @param x the x-coordinate. * @param y the y-coordinate. * @param width the frame width. * @param height the frame height. * * @see #drawOval(int, int, int, int) * @see #fill(Shape) */ @Override public void fillOval(int x, int y, int width, int height) { switchColors(); this.gc.fillOval(x, y, width - 1, height - 1); switchColors(); } /** * Fills an arc that is part of an ellipse that fits within the specified * framing rectangle. * * @param x the x-coordinate. * @param y the y-coordinate. * @param width the frame width. * @param height the frame height. * @param arcStart the arc starting point, in degrees. * @param arcAngle the extent of the arc. * * @see #drawArc(int, int, int, int, int, int) */ @Override public void fillArc(int x, int y, int width, int height, int arcStart, int arcAngle) { switchColors(); this.gc.fillArc(x, y, width - 1, height - 1, arcStart, arcAngle); switchColors(); } /** * Returns the font in form of an AWT font created * with the parameters of the font of the SWT graphic * composite. * @return The font. * @see java.awt.Graphics#getFont() */ @Override public Font getFont() { return this.awtFont; } /** * Set the font SWT graphic composite from the specified * AWT font. Be careful that the newly created SWT font * must be disposed separately. * @see java.awt.Graphics#setFont(java.awt.Font) */ @Override public void setFont(Font font) { if (font == null) { return; } this.awtFont = font; org.eclipse.swt.graphics.Font swtFont = getSwtFontFromPool(font); this.gc.setFont(swtFont); } /** * Returns the font metrics. * * @param font the font. * * @return The font metrics. */ @Override public FontMetrics getFontMetrics(Font font) { return SWTUtils.DUMMY_PANEL.getFontMetrics(font); } /** * Returns the font render context. * * @return The font render context. */ @Override public FontRenderContext getFontRenderContext() { FontRenderContext fontRenderContext = new FontRenderContext( new AffineTransform(), true, true); return fontRenderContext; } /** * Draws the specified glyph vector at the location {@code (x, y)}. * * @param g the glyph vector ({@code null} not permitted). * @param x the x-coordinate. * @param y the y-coordinate. */ @Override public void drawGlyphVector(GlyphVector g, float x, float y) { fill(g.getOutline(x, y)); } /** * Draws a string at {@code (x, y)}. The start of the text at the * baseline level will be aligned with the {@code (x, y)} point. * * @param text the string ({@code null} not permitted). * @param x the x-coordinate. * @param y the y-coordinate. * * @see #drawString(java.lang.String, float, float) */ @Override public void drawString(String text, int x, int y) { drawString(text, (float) x, (float) y); } /** * Draws a string at the specified position. * * @param text the string. * @param x the x-coordinate. * @param y the y-coordinate. */ @Override public void drawString(String text, float x, float y) { if (text == null) { throw new NullPointerException("Null 'text' argument."); } float fm = this.gc.getFontMetrics().getAscent(); this.gc.drawString(text, (int) x, (int) (y - fm), true); } /** * Draws a string at the specified position. * * @param iterator the string. * @param x the x-coordinate. * @param y the y-coordinate. */ @Override public void drawString(AttributedCharacterIterator iterator, int x, int y) { // for now we simply want to extract the chars from the iterator // and call an unstyled text renderer StringBuffer sb = new StringBuffer(); int numChars = iterator.getEndIndex() - iterator.getBeginIndex(); char c = iterator.first(); for (int i = 0; i < numChars; i++) { sb.append(c); c = iterator.next(); } drawString(new String(sb),x,y); } /** * Draws a string at the specified position. * * @param iterator the string. * @param x the x-coordinate. * @param y the y-coordinate. */ @Override public void drawString(AttributedCharacterIterator iterator, float x, float y) { drawString(iterator, (int) x, (int) y); } /** * Returns {@code true} if the rectangle (in device space) intersects * with the shape (the interior, if {@code onStroke} is false, * otherwise the stroked outline of the shape). * * @param rect a rectangle (in device space). * @param s the shape. * @param onStroke test the stroked outline only? * * @return A boolean. */ @Override public boolean hit(Rectangle rect, Shape s, boolean onStroke) { AffineTransform transform = getTransform(); Shape ts; if (onStroke) { Stroke stroke = getStroke(); ts = transform.createTransformedShape(stroke.createStrokedShape(s)); } else { ts = transform.createTransformedShape(s); } if (!rect.getBounds2D().intersects(ts.getBounds2D())) { return false; } Area a1 = new Area(rect); Area a2 = new Area(ts); a1.intersect(a2); return !a1.isEmpty(); } /** * Not implemented - see {@link Graphics#copyArea(int, int, int, int, int, * int)}. */ @Override public void copyArea(int x, int y, int width, int height, int dx, int dy) { // TODO Auto-generated method stub } /** * Draws an image with the specified transform. Note that the * {@code observer} is ignored in this implementation. * * @param image the image. * @param xform the transform. * @param obs the image observer (ignored). * * @return {@code true} if the image is drawn. */ @Override public boolean drawImage(Image image, AffineTransform xform, ImageObserver obs) { AffineTransform savedTransform = getTransform(); if (xform != null) { transform(xform); } boolean result = drawImage(image, 0, 0, obs); if (xform != null) { setTransform(savedTransform); } return result; } /** * Draws the image resulting from applying the {@code BufferedImageOp} * to the specified image at the location {@code (x, y)}. * * @param image the image. * @param op the operation ({@code null} permitted). * @param x the x-coordinate. * @param y the y-coordinate. */ @Override public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y) { BufferedImage imageToDraw = image; if (op != null) { imageToDraw = op.filter(image, null); } drawImage(imageToDraw, new AffineTransform(1f, 0f, 0f, 1f, x, y), null); } /** * Draws an SWT image with the top left corner of the image aligned to the * point (x, y). * * @param image the image. * @param x the x-coordinate. * @param y the y-coordinate. */ public void drawImage(org.eclipse.swt.graphics.Image image, int x, int y) { this.gc.drawImage(image, x, y); } /** * Draws a rendered image. If {@code img} is {@code null} this method * does nothing. * * @param image the rendered image ({@code null} permitted). * @param xform the transform. */ @Override public void drawRenderedImage(RenderedImage image, AffineTransform xform) { if (image == null) { return; } BufferedImage bi = convertRenderedImage(image); drawImage(bi, xform, null); } /** * Converts a rendered image to a {@code BufferedImage}. This utility * method has come from a forum post by Jim Moore at: *

* * http://www.jguru.com/faq/view.jsp?EID=114602 * * @param img the rendered image. * * @return A buffered image. */ private static BufferedImage convertRenderedImage(RenderedImage img) { if (img instanceof BufferedImage) { return (BufferedImage) img; } ColorModel cm = img.getColorModel(); int width = img.getWidth(); int height = img.getHeight(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); Hashtable properties = new Hashtable(); String[] keys = img.getPropertyNames(); if (keys != null) { for (int i = 0; i < keys.length; i++) { properties.put(keys[i], img.getProperty(keys[i])); } } BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties); img.copyData(raster); return result; } /** * Draws the renderable image. * * @param image the renderable image. * @param xform the transform. */ @Override public void drawRenderableImage(RenderableImage image, AffineTransform xform) { RenderedImage ri = image.createDefaultRendering(); drawRenderedImage(ri, xform); } /** * Draws an image with the top left corner aligned to the point (x, y). * * @param image the image ({@code null} permitted...method will do nothing). * @param x the x-coordinate. * @param y the y-coordinate. * @param observer ignored here. * * @return {@code true} if the image has been drawn. */ @Override public boolean drawImage(Image image, int x, int y, ImageObserver observer) { if (image == null) { return true; } int w = image.getWidth(observer); if (w < 0) { return false; } int h = image.getHeight(observer); if (h < 0) { return false; } return drawImage(image, x, y, w, h, observer); } /** * Draws an image with the top left corner aligned to the point (x, y), * and scaled to the specified width and height. * * @param image the image ({@code null} permitted...draws nothing). * @param x the x-coordinate. * @param y the y-coordinate. * @param width the width for the rendered image. * @param height the height for the rendered image. * @param observer ignored here. * * @return {@code true} if the image has been drawn. */ @Override public boolean drawImage(Image image, int x, int y, int width, int height, ImageObserver observer) { if (image == null) { return true; } if (width <= 0 || height <= 0) { return true; } ImageData data = SWTUtils.convertAWTImageToSWT(image); if (data == null) { return false; } org.eclipse.swt.graphics.Image im = new org.eclipse.swt.graphics.Image( this.gc.getDevice(), data); org.eclipse.swt.graphics.Rectangle bounds = im.getBounds(); this.gc.drawImage(im, 0, 0, bounds.width, bounds.height, x, y, width, height); im.dispose(); return true; } /** * Draws an image. * * @param image the image ({@code null} permitted...draws nothing). * @param x the x-coordinate. * @param y the y-coordinate. * @param bgcolor the background color. * @param observer an image observer. * * @return A boolean. */ @Override public boolean drawImage(Image image, int x, int y, Color bgcolor, ImageObserver observer) { if (image == null) { return true; } int w = image.getWidth(null); if (w < 0) { return false; } int h = image.getHeight(null); if (h < 0) { return false; } return drawImage(image, x, y, w, h, bgcolor, observer); } /** * Draws an image to the rectangle {@code (x, y, w, h)} (scaling it if * required), first filling the background with the specified color. Note * that the {@code observer} is ignored. * * @param image the image. * @param x the x-coordinate. * @param y the y-coordinate. * @param width the width. * @param height the height. * @param bgcolor the background color ({@code null} permitted). * @param observer ignored. * * @return {@code true} if the image is drawn. */ @Override public boolean drawImage(Image image, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) { Paint saved = getPaint(); setPaint(bgcolor); fillRect(x, y, width, height); setPaint(saved); return drawImage(image, x, y, width, height, observer); } /** * Draws part of an image (defined by the source rectangle * {@code (sx1, sy1, sx2, sy2)}) into the destination rectangle * {@code (dx1, dy1, dx2, dy2)}. Note that the {@code observer} * is ignored. * * @param image the image. * @param dx1 the x-coordinate for the top left of the destination. * @param dy1 the y-coordinate for the top left of the destination. * @param dx2 the x-coordinate for the bottom right of the destination. * @param dy2 the y-coordinate for the bottom right of the destination. * @param sx1 the x-coordinate for the top left of the source. * @param sy1 the y-coordinate for the top left of the source. * @param sx2 the x-coordinate for the bottom right of the source. * @param sy2 the y-coordinate for the bottom right of the source. * * @return {@code true} if the image is drawn. */ @Override public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { int w = dx2 - dx1; int h = dy2 - dy1; BufferedImage img2 = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = img2.createGraphics(); g2.drawImage(image, 0, 0, w, h, sx1, sy1, sx2, sy2, null); return drawImage(img2, dx1, dy1, null); } /** * Draws part of an image (defined by the source rectangle * {@code (sx1, sy1, sx2, sy2)}) into the destination rectangle * {@code (dx1, dy1, dx2, dy2)}. The destination rectangle is first * cleared by filling it with the specified {@code bgcolor}. Note that * the {@code observer} is ignored. * * @param image the image. * @param dx1 the x-coordinate for the top left of the destination. * @param dy1 the y-coordinate for the top left of the destination. * @param dx2 the x-coordinate for the bottom right of the destination. * @param dy2 the y-coordinate for the bottom right of the destination. * @param sx1 the x-coordinate for the top left of the source. * @param sy1 the y-coordinate for the top left of the source. * @param sx2 the x-coordinate for the bottom right of the source. * @param sy2 the y-coordinate for the bottom right of the source. * @param bgcolor the background color ({@code null} permitted). * @param observer ignored. * * @return {@code true} if the image is drawn. */ public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) { Paint saved = getPaint(); setPaint(bgcolor); fillRect(dx1, dy1, dx2 - dx1, dy2 - dy1); setPaint(saved); return drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer); } /** * Releases resources held by this instance (but note that the caller * must dispose of the 'GC' passed to the constructor). * * @see java.awt.Graphics#dispose() */ @Override public void dispose() { // we dispose resources we own but user must dispose gc disposeResourcePool(); } /** * Add given SWT resource to the resource pool. All resources added * to the resource pool will be disposed when {@link #dispose()} is called. * * @param resource the resource to add to the pool. * @return the SWT {@code Resource} just added. */ private Resource addToResourcePool(Resource resource) { this.resourcePool.add(resource); return resource; } /** * Dispose the resource pool. */ private void disposeResourcePool() { for (Iterator it = this.resourcePool.iterator(); it.hasNext();) { Resource resource = (Resource) it.next(); resource.dispose(); } this.fontsPool.clear(); this.colorsPool.clear(); this.patternsPool.clear(); this.transformsPool.clear(); this.resourcePool.clear(); } /** * Internal method to convert a AWT font object into * a SWT font resource. If a corresponding SWT font * instance is already in the pool, it will be used * instead of creating a new one. This is used in * {@link #setFont(Font)} for instance. * * @param font The AWT font to convert. * @return The SWT font instance. */ private org.eclipse.swt.graphics.Font getSwtFontFromPool(Font font) { org.eclipse.swt.graphics.Font swtFont = (org.eclipse.swt.graphics.Font) this.fontsPool.get(font); if (swtFont == null) { swtFont = new org.eclipse.swt.graphics.Font(this.gc.getDevice(), SWTUtils.toSwtFontData(this.gc.getDevice(), font, true)); addToResourcePool(swtFont); this.fontsPool.put(font, swtFont); } return swtFont; } /** * Internal method to convert a AWT color object into * a SWT color resource. If a corresponding SWT color * instance is already in the pool, it will be used * instead of creating a new one. This is used in * {@link #setColor(Color)} for instance. * * @param awtColor The AWT color to convert. * @return A SWT color instance. */ private org.eclipse.swt.graphics.Color getSwtColorFromPool(Color awtColor) { org.eclipse.swt.graphics.Color swtColor = (org.eclipse.swt.graphics.Color) this.colorsPool.get(Integer.valueOf(awtColor.getRGB())); if (swtColor == null) { swtColor = SWTUtils.toSwtColor(this.gc.getDevice(), awtColor); addToResourcePool(swtColor); this.colorsPool.put(Integer.valueOf(awtColor.getRGB()), swtColor); } return swtColor; } /** * Fetches an SWT Pattern matching the supplied gradient paint, or creates one, and returns it. * * @param gp the gradient paint. * * @return The SWT Pattern. */ private Pattern fetchOrCreateSWTPattern(GradientPaint gp) { Pattern swtPattern = this.patternsPool.get(gp); if (swtPattern == null) { swtPattern = new Pattern(this.gc.getDevice(), (float) gp.getPoint1().getX(), (float) gp.getPoint1().getY(), (float) gp.getPoint2().getX(), (float) gp.getPoint2().getY(), getSwtColorFromPool(gp.getColor1()), getSwtColorFromPool(gp.getColor2())); addToResourcePool(swtPattern); this.patternsPool.put(gp, swtPattern); } return swtPattern; } /** * Internal method to convert a AWT transform object into * a SWT transform resource. If a corresponding SWT transform * instance is already in the pool, it will be used * instead of creating a new one. This is used in * {@link #setTransform(AffineTransform)} for instance. * * @param awtTransform The AWT transform to convert. * @return A SWT transform instance. */ private Transform getSwtTransformFromPool(AffineTransform awtTransform) { Transform t = (Transform) this.transformsPool.get(awtTransform); if (t == null) { t = new Transform(this.gc.getDevice()); double[] matrix = new double[6]; awtTransform.getMatrix(matrix); t.setElements((float) matrix[0], (float) matrix[1], (float) matrix[2], (float) matrix[3], (float) matrix[4], (float) matrix[5]); addToResourcePool(t); this.transformsPool.put(awtTransform, t); } return t; } /** * Perform a switch between foreground and background * color of gc. This is needed for consistency with * the AWT behaviour, and is required notably for the * filling methods. */ private void switchColors() { org.eclipse.swt.graphics.Color bg = this.gc.getBackground(); org.eclipse.swt.graphics.Color fg = this.gc.getForeground(); this.gc.setBackground(fg); this.gc.setForeground(bg); } /** * Converts an AWT {@code Shape} into a SWT {@code Path}. * * @param shape the shape ({@code null} not permitted). * * @return The path. */ private Path toSwtPath(Shape shape) { int type; float[] coords = new float[6]; Path path = new Path(this.gc.getDevice()); PathIterator pit = shape.getPathIterator(null); while (!pit.isDone()) { type = pit.currentSegment(coords); switch (type) { case (PathIterator.SEG_MOVETO): path.moveTo(coords[0], coords[1]); break; case (PathIterator.SEG_LINETO): path.lineTo(coords[0], coords[1]); break; case (PathIterator.SEG_QUADTO): path.quadTo(coords[0], coords[1], coords[2], coords[3]); break; case (PathIterator.SEG_CUBICTO): path.cubicTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case (PathIterator.SEG_CLOSE): path.close(); break; default: break; } pit.next(); } return path; } /** * Converts an SWT transform into the equivalent AWT transform. * * @param swtTransform the SWT transform. * * @return The AWT transform. */ private AffineTransform toAwtTransform(Transform swtTransform) { float[] elements = new float[6]; swtTransform.getElements(elements); AffineTransform awtTransform = new AffineTransform(elements); return awtTransform; } /** * Returns the AWT line cap corresponding to the specified SWT line cap. * * @param swtLineCap the SWT line cap. * * @return The AWT line cap. */ private int toAwtLineCap(int swtLineCap) { if (swtLineCap == SWT.CAP_FLAT) { return BasicStroke.CAP_BUTT; } else if (swtLineCap == SWT.CAP_ROUND) { return BasicStroke.CAP_ROUND; } else if (swtLineCap == SWT.CAP_SQUARE) { return BasicStroke.CAP_SQUARE; } else { throw new IllegalArgumentException("SWT LineCap " + swtLineCap + " not recognised"); } } /** * Returns the AWT line join corresponding to the specified SWT line join. * * @param swtLineJoin the SWT line join. * * @return The AWT line join. */ private int toAwtLineJoin(int swtLineJoin) { if (swtLineJoin == SWT.JOIN_BEVEL) { return BasicStroke.JOIN_BEVEL; } else if (swtLineJoin == SWT.JOIN_MITER) { return BasicStroke.JOIN_MITER; } else if (swtLineJoin == SWT.JOIN_ROUND) { return BasicStroke.JOIN_ROUND; } else { throw new IllegalArgumentException("SWT LineJoin " + swtLineJoin + " not recognised"); } } /** * Returns the SWT line cap corresponding to the specified AWT line cap. * * @param awtLineCap the AWT line cap. * * @return The SWT line cap. */ private int toSwtLineCap(int awtLineCap) { if (awtLineCap == BasicStroke.CAP_BUTT) { return SWT.CAP_FLAT; } else if (awtLineCap == BasicStroke.CAP_ROUND) { return SWT.CAP_ROUND; } else if (awtLineCap == BasicStroke.CAP_SQUARE) { return SWT.CAP_SQUARE; } else { throw new IllegalArgumentException("AWT LineCap " + awtLineCap + " not recognised"); } } /** * Returns the SWT line join corresponding to the specified AWT line join. * * @param awtLineJoin the AWT line join. * * @return The SWT line join. */ private int toSwtLineJoin(int awtLineJoin) { if (awtLineJoin == BasicStroke.JOIN_BEVEL) { return SWT.JOIN_BEVEL; } else if (awtLineJoin == BasicStroke.JOIN_MITER) { return SWT.JOIN_MITER; } else if (awtLineJoin == BasicStroke.JOIN_ROUND) { return SWT.JOIN_ROUND; } else { throw new IllegalArgumentException("AWT LineJoin " + awtLineJoin + " not recognised"); } } /** A reusable rectangle to avoid garbage. */ private Rectangle2D rect; /** * Sets the attributes of the reusable {@link Rectangle2D} object. * * @param x the x-coordinate. * @param y the y-coordinate. * @param width the width. * @param height the height. */ private void setRect(int x, int y, int width, int height) { if (this.rect == null) { this.rect = new Rectangle2D.Double(x, y, width, height); } else { this.rect.setRect(x, y, width, height); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy