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

com.sun.javafx.sg.prism.NGRectangle Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.javafx.sg.prism;

import com.sun.javafx.geom.*;
import com.sun.javafx.sg.PGNode;
import com.sun.javafx.sg.PGShape;
import com.sun.prism.RectShadowGraphics;
import com.sun.javafx.geom.transform.Affine3D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.sg.PGRectangle;
import com.sun.prism.Graphics;
import com.sun.prism.BasicStroke;
import com.sun.prism.paint.Color;
import com.sun.prism.paint.Paint;
import com.sun.prism.shape.ShapeRep;
import com.sun.scenario.effect.Effect;
import static com.sun.javafx.sg.PGShape.Mode.*;
import static com.sun.javafx.geom.transform.BaseTransform.*;

/**
 */
public class NGRectangle extends NGShape implements PGRectangle {

    private RoundRectangle2D rrect = new RoundRectangle2D();

    @Override public void updateRectangle(float x, float y, float width, float height,
                                          float arcWidth, float arcHeight) {
        rrect.x = x;
        rrect.y = y;
        rrect.width = width;
        rrect.height = height;
        rrect.arcWidth = arcWidth;
        rrect.arcHeight = arcHeight;
        geometryChanged();
        invalidateOpaqueRegion();
    }

    @Override
    public void setFillPaint(Object fillPaint) {
        invalidateOpaqueRegion();
        super.setFillPaint(fillPaint);
    }

    @Override
    public void setMode(Mode mode) {
        invalidateOpaqueRegion();
        super.setMode(mode);
    }

    @Override
    public void setOpacity(float opacity) {
        invalidateOpaqueRegion();
        super.setOpacity(opacity);
    }

    @Override
    public void setClipNode(PGNode clipNode) {
        invalidateOpaqueRegion();
        super.setClipNode(clipNode);
    }

    @Override
    public void setEffect(Object effect) {
        invalidateOpaqueRegion();
        super.setEffect(effect);
    }

    @Override
    public void setTransformMatrix(BaseTransform tx) {
        invalidateOpaqueRegion();
        super.setTransformMatrix(tx);
    }

    @Override protected RectBounds computeOpaqueRegion(RectBounds opaqueRegion) {
        // compute opaque region
        final Mode mode = getMode();
        final Paint fillPaint = getFillPaint();
        final PGNode clip = getClipNode();
        final Effect effect = getEffect();
        if ((effect == null || !effect.reducesOpaquePixels()) &&
                getOpacity() == 1f &&
                (mode == Mode.FILL || mode == Mode.STROKE_FILL) &&
                (fillPaint != null && fillPaint.isOpaque()) &&
                (clip == null || 
                (clip instanceof NGRectangle && ((NGRectangle)clip).isRectClip(BaseTransform.IDENTITY_TRANSFORM, true)))) {
            // Normally the "opaque region" for a rectangle would be the
            // x, y, w, h unless it has rounded corners, in which case
            // we subtract the arc width and arc height.
            final float x = rrect.x + rrect.arcWidth;
            final float y = rrect.y + rrect.arcHeight;
            final float width = rrect.getWidth() - rrect.arcWidth - rrect.arcWidth;
            final float height = rrect.getHeight() - rrect.arcHeight - rrect.arcHeight;
            if (width > 1 && height > 1) {
                if (opaqueRegion == null) {
                    opaqueRegion = new RectBounds(x, y, x + width, y + height); 
                } else {
                    opaqueRegion.deriveWithNewBounds(x, y, 0, x + width, y + height, 0);
                }
                if (clip != null) {
                    opaqueRegion.intersectWith(((NGRectangle)clip).getOpaqueRegion());
                }
                return opaqueRegion;
            }
        }
        return null;
    }

    boolean isRounded() {
        return rrect.arcWidth > 0f && rrect.arcHeight > 0f;
    }

    @Override
    protected void renderEffect(Graphics g) {
        if (!(g instanceof RectShadowGraphics) || !renderEffectDirectly(g)) {
            super.renderEffect(g);
        }
    }

    private boolean renderEffectDirectly(Graphics g) {
        if (mode != Mode.FILL || isRounded()) {
            // TODO: Allow solid strokes that are square in the corners... (RT-26974)
            return false;
        }
        float alpha = g.getExtraAlpha();
        if (fillPaint instanceof Color) {
            alpha *= ((Color) fillPaint).getAlpha();
        } else {
            // TODO: Check if all colors in a gradient have same alpha... (RT-26974)
            return false;
        }
        Effect effect = getEffect();
        if (EffectUtil.renderEffectForRectangularNode(this, g, effect,
                                                      alpha, true /* antialiased */,
                                                      rrect.x, rrect.y,
                                                      rrect.width, rrect.height))
        {
            return true;
        }
        return false;
    }

    @Override
    public final Shape getShape() {
        return rrect;
    }

    @Override
    protected ShapeRep createShapeRep(Graphics g, boolean needs3D) {
        return g.getResourceFactory().createRoundRectRep(needs3D);
    }

    private static final double SQRT_2 = Math.sqrt(2.0);
    private static boolean hasRightAngleMiterAndNoDashes(BasicStroke bs) {
        return (bs.getLineJoin() == BasicStroke.JOIN_MITER &&
                bs.getMiterLimit() >= SQRT_2 &&
                bs.getDashArray() == null);
    }

    static boolean rectContains(float x, float y,
                                NGShape node,
                                RectangularShape r)
    {
        double rw = r.getWidth();
        double rh = r.getHeight();
        if (rw < 0 || rh < 0) {
            return false;
        }
        Mode mode = node.mode;
        if (mode == Mode.EMPTY) {
            return false;
        }
        double rx = r.getX();
        double ry = r.getY();
        if (mode == Mode.FILL) {
            // shortcut for common case
            return (x >= rx && y >= ry && x < rx+rw && y < ry+rh);
        }
        // mode is STROKE or STROKE_FILL
        float outerpad = -1.0f; // check bounds+outerpad if >= 0.0
        float innerpad = -1.0f; // check bounds-innerpad if >= 0.0
        boolean checkstroke = false; // manually check stroke shape if true
        BasicStroke drawstroke = node.drawStroke;
        int type = drawstroke.getType();
        if (type == BasicStroke.TYPE_INNER) {
            if (mode == Mode.STROKE_FILL) {
                outerpad = 0.0f;
            } else {
                if (drawstroke.getDashArray() == null) {
                    outerpad = 0.0f;
                    innerpad = drawstroke.getLineWidth();
                } else {
                    checkstroke = true;
                }
            }
        } else if (type == BasicStroke.TYPE_OUTER) {
            if (hasRightAngleMiterAndNoDashes(drawstroke)) {
                outerpad = drawstroke.getLineWidth();
                if (mode == Mode.STROKE) {
                    innerpad = 0.0f;
                }
            } else {
                if (mode == Mode.STROKE_FILL) {
                    outerpad = 0.0f;
                }
                checkstroke = true;
            }
        } else if (type == BasicStroke.TYPE_CENTERED) {
            if (hasRightAngleMiterAndNoDashes(drawstroke)) {
                outerpad = drawstroke.getLineWidth() / 2.0f;
                if (mode == Mode.STROKE) {
                    innerpad = outerpad;
                }
            } else {
                if (mode == Mode.STROKE_FILL) {
                    outerpad = 0.0f;
                }
                checkstroke = true;
            }
        } else {
            // TODO: This should never happen... (RT-26974)
            if (mode == Mode.STROKE_FILL) {
                outerpad = 0.0f;
            }
            checkstroke = true;
        }
        if (outerpad >= 0.0f) {
            if (x >= rx   -outerpad && y >= ry   -outerpad &&
                x <  rx+rw+outerpad && y <  ry+rh+outerpad) {
                // point falls inside padded rectangle
                if (innerpad >= 0.0f &&
                    // we have an inner hole to test as well...
                    innerpad < rw/2.0f && innerpad < rh/2.0f &&
                    // and lw is small enough to make an inner hole
                    x >= rx   +innerpad && y >= ry   +innerpad &&
                    x <  rx+rw-innerpad && y <  ry+rh-innerpad)
                {
                    // point falls inside inner hole of stroked rectangle
                    return false;
                }
                return true;
            }
        }
        if (checkstroke) {
            return node.getStrokeShape().contains(x, y);
        }
        return false;
    }

    /**
     * Returns whether a clip represented by this node can be rendered using
     * axis aligned rect clip.
     *
     * The return value depends on whether the rectangle has arcs, current
     * rendering mode (stroked or not), and whether the transform is only
     * translate or scale or something more complex.
     *
     * @return whether this rectangle is axis aligned when rendered given node's
     * and rendering transform
     */
    final boolean isRectClip(BaseTransform xform, boolean permitRoundedRectangle) {
        // must be a simple fill of a non-round rect with opaque paint
        // With more work we could optimize the case of a Rectangle with a
        // Rectangle as a clip, but that would likely slow down some more
        // common cases with an optimization of questionable value.
        if (mode != FILL || getClipNode() != null || (getEffect() != null && getEffect().reducesOpaquePixels()) ||
            getOpacity() < 1f || (!permitRoundedRectangle && isRounded()) || !fillPaint.isOpaque())
        {
            return false;
        }

        BaseTransform nodeXform = getTransform();
        if (!nodeXform.isIdentity()) {
            // only bother concatenating if the passed xform is non-id
            // otherwise we can just use this node's tx
            if (!xform.isIdentity()) {
                TEMP_TRANSFORM.setTransform(xform);
                TEMP_TRANSFORM.concatenate(nodeXform);
                xform = TEMP_TRANSFORM;
            } else {
                xform = nodeXform;
            }
        }

        long t = xform.getType();
        return
            (t & ~(TYPE_TRANSLATION|TYPE_QUADRANT_ROTATION|TYPE_MASK_SCALE)) == 0;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy