javafx.scene.paint.ImagePattern Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2024, 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 javafx.scene.paint;
import javafx.beans.NamedArg;
import javafx.scene.image.Image;
import com.sun.javafx.beans.event.AbstractNotifyListener;
import com.sun.javafx.tk.Toolkit;
import com.sun.javafx.util.InterpolationUtils;
import java.util.Objects;
/**
* The {@code ImagePattern} class fills a shape with an image pattern. The
* user may specify the anchor rectangle, which defines the position,
* width, and height of the image relative to the upper left corner of the
* shape. If the shape extends out of the anchor rectangle, the image is tiled.
*
*
* If the {@code proportional} variable is set to true (the default)
* then the anchor rectangle should be specified relative to the unit
* square (0.0->1.0) and will be stretched across the shape.
* If the {@code proportional} variable is set to false, then the anchor
* rectangle should be specified in the local coordinate system of the shape
* and the image will be stretched to fit the anchor rectangle. The anchor
* rectangle will not be stretched across the shape.
*
* The example below demonstrates the use of the {@code proportional}
* variable. The shapes on the top row use proportional coordinates
* (the default) to specify the anchor rectangle. The shapes on the
* bottom row use absolute coordinates. The flower image is stretched
* to fill the entire triangle shape, while the dot pattern image is tiled
* within the circle shape.
*
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
public class HelloImagePattern extends Application {
private static final String flowerURL = "file:flower.png";
private static final String dotsURL = "file:dots.png";
{@literal @Override public void start(Stage stage)} {
stage.setTitle("Image Pattern");
Group root = new Group();
Scene scene = new Scene(root, 600, 450);
Image dots = new Image(dotsURL);
Image flower = new Image(flowerURL);
Polygon p = new Polygon();
p.setLayoutX(10);
p.setLayoutY(10);
p.getPoints().add(50.0);
p.getPoints().add(0.0);
p.getPoints().add(100.0);
p.getPoints().add(100.0);
p.getPoints().add(0.0);
p.getPoints().add(100.0);
p.setFill(new ImagePattern(flower, 0, 0, 1, 1, true));
root.getChildren().add(p);
Polygon p2 = new Polygon();
p2.setLayoutX(10);
p2.setLayoutY(120);
p2.getPoints().add(50.0);
p2.getPoints().add(0.0);
p2.getPoints().add(100.0);
p2.getPoints().add(100.0);
p2.getPoints().add(0.0);
p2.getPoints().add(100.0);
p2.setFill(new ImagePattern(flower, 0, 0, 100, 100, false));
root.getChildren().add(p2);
Circle circ = new Circle(50);
circ.setTranslateX(120);
circ.setTranslateY(10);
circ.setCenterX(50);
circ.setCenterY(50);
circ.setFill(new ImagePattern(dots, 0.2, 0.2, 0.4, 0.4, true));
root.getChildren().add(circ);
Circle circ2 = new Circle(50);
circ2.setTranslateX(120);
circ2.setTranslateY(10);
circ2.setCenterX(50);
circ2.setCenterY(50);
circ2.setFill(new ImagePattern(dots, 20, 20, 40, 40, false));
root.getChildren().add(circ2);
stage.setScene(scene);
stage.show();
}
* The code above produces the following:
*
*
* @since JavaFX 2.2
*/
public final class ImagePattern extends Paint {
private Image image;
/**
* Gets the image to be used as a paint.
*
* @interpolationType discrete
* @return Image to be used as a paint.
*/
public final Image getImage() {
return image;
}
private double x;
/**
* Gets the x origin of the anchor rectangle.
*
* @defaultValue 0.0
* @interpolationType linear
* if both values are absolute or both values are {@link #isProportional() proportional},
* discrete otherwise
* @return The x origin of the anchor rectangle.
*/
public final double getX() {
return x;
}
private double y;
/**
* Gets the y origin of the anchor rectangle.
*
* @defaultValue 0.0
* @interpolationType linear
* if both values are absolute or both values are {@link #isProportional() proportional},
* discrete otherwise
* @return The y origin of the anchor rectangle.
*/
public final double getY() {
return y;
}
private double width = 1f;
/**
* Gets the width of the anchor rectangle.
*
* @defaultValue 1.0
* @interpolationType linear
* if both values are absolute or both values are {@link #isProportional() proportional},
* discrete otherwise
* @return The width of the anchor rectangle.
*/
public final double getWidth() {
return width;
}
private double height = 1f;
/**
* Gets the height of the anchor rectangle.
*
* @defaultValue 1.0
* @interpolationType linear
* if both values are absolute or both values are {@link #isProportional() proportional},
* discrete otherwise
* @return The height of the anchor rectangle.
*/
public final double getHeight() {
return height;
}
private boolean proportional = true;
/**
* Gets a boolean that indicates whether start and end locations are
* proportional or absolute. If this flag is true, the two end points are
* defined in a coordinate space where coordinates in the range
* {@code [0..1]} are scaled to map onto the bounds of the shape that the
* pattern fills. If this flag is false, then the coordinates are specified
* in the local coordinate system of the node.
*
* @defaultValue true
* @interpolationType discrete
* @return boolean that is true if this paint is proportional.
*/
public final boolean isProportional() {
return proportional;
}
@Override public final boolean isOpaque() {
// We only ever have Prism as our painting system these days, so we can just
// cast this platformPaint to a prism paint type and check for isOpaque.
// It would be better to change the type on platformPaint accordingly and
// ditch the cast.
return ((com.sun.prism.paint.ImagePattern)acc_getPlatformPaint()).isOpaque();
}
private Object platformPaint;
private int hash;
/**
* Creates a new instance of ImagePattern from the specified image. Default
* values are used for all other parameters.
*
* @param image the image to be used as the paint.
* @throws NullPointerException if the image is null.
* @throws IllegalArgumentException if image is not done loading,
* that is if progress is < 1.
*/
public ImagePattern(@NamedArg("image") Image image) {
if (image == null) {
throw new NullPointerException("Image must be non-null.");
} else if (image.getProgress() < 1.0) {
throw new IllegalArgumentException("Image not yet loaded");
}
this.image = image;
}
/**
* Creates a new instance of ImagePattern.
*
* @param image the image to be used as the paint.
* @param x the x origin of the anchor rectangle.
* @param y the y origin of the anchor rectangle.
* @param width the width of the anchor rectangle.
* @param height the height of the anchor rectangle.
* @param proportional whether the coordinates are proportional
* to the shape which ImagePattern fills
* @throws NullPointerException if the image is null.
* @throws IllegalArgumentException if image is not done loading,
* that is if progress is < 1.
*/
public ImagePattern(@NamedArg("image") Image image, @NamedArg("x") double x, @NamedArg("y") double y, @NamedArg("width") double width,
@NamedArg("height") double height, @NamedArg("proportional") boolean proportional) {
if (image == null) {
throw new NullPointerException("Image must be non-null.");
} else if (image.getProgress() < 1.0) {
throw new IllegalArgumentException("Image not yet loaded");
}
this.image = image;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.proportional = proportional;
}
/**
* Returns an intermediate value between the value of this {@code ImagePattern} and the specified
* {@code endValue} using the linear interpolation factor {@code t}, ranging from 0 (inclusive)
* to 1 (inclusive).
*
* @param endValue the target value
* @param t the interpolation factor
* @throws NullPointerException if {@code endValue} is {@code null}
* @return the intermediate value
* @since 24
*/
public ImagePattern interpolate(ImagePattern endValue, double t) {
Objects.requireNonNull(endValue, "endValue cannot be null");
if (t <= 0 || equals(endValue)) {
return this;
}
if (t >= 1) {
return endValue;
}
if (proportional != endValue.proportional) {
return InterpolationUtils.interpolateDiscrete(this, endValue, t);
}
return new ImagePattern(
InterpolationUtils.interpolateDiscrete(image, endValue.image, t),
InterpolationUtils.interpolate(x, endValue.x, t),
InterpolationUtils.interpolate(y, endValue.y, t),
InterpolationUtils.interpolate(width, endValue.width, t),
InterpolationUtils.interpolate(height, endValue.height, t),
proportional);
}
/**
* {@inheritDoc}
*
* @throws NullPointerException {@inheritDoc}
* @since 24
*/
@Override
public Paint interpolate(Paint endValue, double t) {
return InterpolationUtils.interpolatePaint(this, endValue, t);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ImagePattern other) {
return proportional == other.proportional
&& x == other.x
&& y == other.y
&& width == other.width
&& height == other.height
&& image.equals(other.image);
}
return false;
}
@Override
public int hashCode() {
if (hash == 0) {
long bits = 17L;
bits = 37L * bits + Double.doubleToLongBits(x);
bits = 37L * bits + Double.doubleToLongBits(y);
bits = 37L * bits + Double.doubleToLongBits(width);
bits = 37L * bits + Double.doubleToLongBits(height);
bits = 37L * bits + ((proportional) ? 1231L : 1237L);
bits = 37L * bits + image.hashCode();
hash = (int) (bits ^ (bits >> 32));
}
return hash;
}
@Override
boolean acc_isMutable() {
return Toolkit.getImageAccessor().isAnimation(image);
}
@Override
void acc_addListener(AbstractNotifyListener platformChangeListener) {
Toolkit.getImageAccessor().getImageProperty(image)
.addListener(platformChangeListener);
}
@Override
void acc_removeListener(AbstractNotifyListener platformChangeListener) {
Toolkit.getImageAccessor().getImageProperty(image)
.removeListener(platformChangeListener);
}
@Override Object acc_getPlatformPaint() {
if (acc_isMutable() || platformPaint == null) {
platformPaint = Toolkit.getToolkit().getPaint(this);
assert platformPaint != null;
}
return platformPaint;
}
}