com.sun.scenario.effect.Effect Maven / Gradle / Ivy
/*
* Copyright (c) 2008, 2022, 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.scenario.effect;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.sun.scenario.effect.impl.Renderer;
import com.sun.scenario.effect.impl.state.AccessHelper;
import com.sun.javafx.geom.Point2D;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.DirtyRegionContainer;
import com.sun.javafx.geom.DirtyRegionPool;
import com.sun.javafx.geom.RectBounds;
import com.sun.javafx.geom.Rectangle;
import com.sun.javafx.geom.transform.BaseTransform;
/**
* The base class for all filter effects.
*/
public abstract class Effect {
/**
* A convenient constant for using a readable value to specify
* a {@code null} value for input {@code Effect}s in method and
* constructor parameter lists.
* Specifying {@code effect.setInput(DefaultInput)} is equivalent
* to specifying {@code effect.setInput(null)}.
*/
public static final Effect DefaultInput = null;
private final List inputs;
private final List unmodifiableInputs;
private final int maxInputs;
static {
AccessHelper.setStateAccessor(effect -> effect.getState());
}
/**
* Constructs an {@code Effect} with no inputs.
*/
protected Effect() {
this.inputs = Collections.emptyList();
this.unmodifiableInputs = inputs;
this.maxInputs = 0;
}
/**
* Constructs an {@code Effect} with exactly one input.
*
* @param input the input {@code Effect}
*/
protected Effect(Effect input) {
this.inputs = new ArrayList<>(1);
this.unmodifiableInputs = Collections.unmodifiableList(inputs);
this.maxInputs = 1;
setInput(0, input);
}
/**
* Constructs an {@code Effect} with exactly two inputs.
*
* @param input1 the first input {@code Effect}
* @param input2 the second input {@code Effect}
*/
protected Effect(Effect input1, Effect input2) {
this.inputs = new ArrayList<>(2);
this.unmodifiableInputs = Collections.unmodifiableList(inputs);
this.maxInputs = 2;
setInput(0, input1);
setInput(1, input2);
}
/**
* Returns state object that is associated with this effect instance.
* Subclasses may override this method to return some sort of state
* object that contains implementation details that are hidden from
* the public API. Classes outside this package can use the AccessHelper
* class to get access to this package-private method.
*/
Object getState() {
return null;
}
/**
* Returns the number of inputs processed by this {@code Effect}.
*
* @return the number of inputs for this {@code Effect}
*/
public int getNumInputs() {
return inputs.size();
}
/**
* Returns the (immutable) list of input {@code Effect}s, or an empty
* list if no inputs were specified at construction time.
*
* @return the list of input {@code Effect}s
*/
public final List getInputs() {
return unmodifiableInputs;
}
/**
* Sets the indexed input for this {@code Effect} to a specific
* {@code Effect} or to the default input if {@code input} is
* {@code null}.
*
* @param index the index of the input {@code Effect}
* @param input the input {@code Effect}
* @throws IllegalArgumentException if {@code index} is less than
* zero or greater than or equal to the number of inputs specified
* at construction time
*/
protected void setInput(int index, Effect input) {
if (index < 0 || index >= maxInputs) {
throw new IllegalArgumentException("Index must be within allowable range");
}
if (index < inputs.size()) {
inputs.set(index, input);
} else {
inputs.add(input);
}
}
public static BaseBounds combineBounds(BaseBounds... inputBounds) {
BaseBounds ret = null;
if (inputBounds.length == 1) {
ret = inputBounds[0];
} else {
for (int i = 0; i < inputBounds.length; i++) {
BaseBounds r = inputBounds[i];
if (r != null && !r.isEmpty()) {
if (ret == null) {
ret = new RectBounds();
ret = ret.deriveWithNewBounds(r);
} else {
ret = ret.deriveWithUnion(r);
}
}
}
}
if (ret == null) {
ret = new RectBounds();
}
return ret;
}
public static Rectangle combineBounds(Rectangle... inputBounds) {
Rectangle ret = null;
if (inputBounds.length == 1) {
ret = inputBounds[0];
} else {
for (int i = 0; i < inputBounds.length; i++) {
Rectangle r = inputBounds[i];
if (r != null && !r.isEmpty()) {
if (ret == null) {
ret = new Rectangle(r);
} else {
ret.add(r);
}
}
}
}
if (ret == null) {
ret = new Rectangle();
}
return ret;
}
public Rectangle getResultBounds(BaseTransform transform,
Rectangle outputClip,
ImageData... inputDatas)
{
int numinputs = inputDatas.length;
Rectangle inputBounds[] = new Rectangle[numinputs];
for (int i = 0; i < numinputs; i++) {
inputBounds[i] = inputDatas[i].getTransformedBounds(outputClip);
}
Rectangle rb = combineBounds(inputBounds);
return rb;
}
/**
* Applies this filter effect to the series of images represented by
* the input {@code Effect}s and/or the given {@code defaultInput}
* viewed under the given {@code transform}.
* The filter does not need to create pixel data for any pixels that
* fall outside of the destination device-space (pixel) bounds specified
* by the {@code outputClip} {@code Rectangle}.
*
* The filter might be able to use the {@code renderHelper} object to
* render the results directly on its own if the object is not null and
* implements an interface, such as {@link ImageHelper}, that the filter
* recognizes.
* If the effect renders itself then it will return a {@code null} for
* the {@code ImageData} result.
*
* Note that the {@code ImageData} object returned by this method must be
* validated prior to use with
* {@link ImageData#validate(com.sun.scenario.effect.FilterContext) } method.
*
*
boolean valid;
do {
ImageData res = filter(fctx, transform, clip, renderer, defaultInput);
if (res == null) {
break;
}
if (valid = res.validate(fctx)) {
// Render res.getImage() to the appropriate destination
// or use it as an input to another chain of effects.
}
res.unref();
} while (!valid);
*
* @param fctx the {@code FilterContext} that determines the
* environment (e.g. the graphics device or code path) on which
* the filter operation will be performed
* @param transform an optional transform under which the filter and
* its inputs will be viewed
* @param outputClip the device space (pixel) bounds of the output
* image or window or clip into which the result of the Effect will
* be rendered, or null if the output dimensions are not known.
* @param renderHelper an object which might be used to render
* the results of the effect directly.
* @param defaultInput the default input {@code Effect} to be used in
* all cases where a filter has a null input.
* @return the {@code ImageData} holding the result of this filter
* operation or {@code null} if the filter had no output or used the
* {@code renderHelper} to render its results directly.
*/
public abstract ImageData filter(FilterContext fctx,
BaseTransform transform,
Rectangle outputClip,
Object renderHelper,
Effect defaultInput);
public static BaseBounds transformBounds(BaseTransform tx, BaseBounds r) {
if (tx == null || tx.isIdentity()) {
return r;
}
BaseBounds ret = new RectBounds();
ret = tx.transform(r, ret);
return ret;
}
protected ImageData ensureTransform(FilterContext fctx,
ImageData original,
BaseTransform transform,
Rectangle clip)
{
if (transform == null || transform.isIdentity()) {
return original;
}
if (!original.validate(fctx)) {
original.unref();
return new ImageData(fctx, null, new Rectangle());
}
return original.transform(transform);
//
// Rectangle origBounds = original.getBounds();
// if (transform.isTranslateOrIdentity()) {
// double tx = transform.getMxt();
// double ty = transform.getMyt();
// int itx = (int) tx;
// int ity = (int) ty;
// if (itx == tx && ity == ty) {
// Rectangle r = new Rectangle(origBounds);
// r.translate(itx, ity);
// ImageData ret = new ImageData(original, r);
// original.unref();
// return ret;
// }
// }
// RectBounds transformedBounds = transformBounds(transform, origBounds.toRectBounds());
// Rectangle xformBounds = new Rectangle(transformedBounds);
// if (clip != null) {
// xformBounds.intersectWith(clip);
// }
// return Renderer.getRenderer(fctx).
// transform(fctx, original, transform, origBounds, xformBounds);
}
/**
* Returns the dirty region container containing dirty regions affected
* by this filter operation.
*
* @param defaultInput the default input {@code Effect} to be used in
* all cases where a filter has a null input
* @param drc the container of dirty regions in scene coordinates.
* @param regionPool the pool of dirty regions
* @return the dirty region container
*/
public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
DirtyRegionContainer merge = null;
for (int i = 0; i < inputs.size(); i++) {
DirtyRegionContainer drc = getDefaultedInput(i, defaultInput).getDirtyRegions(defaultInput, regionPool);
if (merge == null) {
merge = drc;
} else {
merge.merge(drc);
regionPool.checkIn(drc);
}
}
if (merge == null) {
merge = regionPool.checkOut();
}
return merge;
}
Effect getDefaultedInput(int inputIndex, Effect defaultInput) {
return getDefaultedInput(inputs.get(inputIndex), defaultInput);
}
static Effect getDefaultedInput(Effect listedInput, Effect defaultInput) {
return (listedInput == null) ? defaultInput : listedInput;
}
/**
* Returns the bounding box that will be affected by this filter
* operation when viewed under the specified {@code transform},
* given its list of input {@code Effect}s and the specified
* {@code defaultInput} effect.
* Note that the returned bounds can be smaller or larger than one
* or more of the inputs.
*
* @param transform the transform the effect will be viewed under
* @param defaultInput the default input {@code Effect} to be used in
* all cases where a filter has a null input
* @return the bounding box of this filter
*/
public abstract BaseBounds getBounds(BaseTransform transform,
Effect defaultInput);
/**
* Transform the specified point {@code p} from the coordinate space
* of the primary content input to the coordinate space of the effect
* output.
* In essence, this method asks the question "Which output coordinate
* is most affected by the data at the specified coordinate in the
* primary source input?"
*
* The definition of which input represents the primary content input
* and how the coordinate space of that input compares to the coordinate
* space of the result varies from effect to effect.
* Note that some effects may have a reasonable definition of how to
* map source coordinates to destination coordinates, but not the
* reverse.
* In particular, effects which map source coordinates discontiguously
* into the result may have several output coordinates that are affected
* by a given input coordinate and may choose to return one of many
* equally valid answers, or an undefined result such as {@code NaN},
* or some other anomalous value.
* Most effects perform simple transformations of the color of each
* pixel and so represent an identity transform and return the point
* unchanged.
*
* @param p the point in the coordinate space of the primary content
* input to be transformed
* @param defaultInput the default input {@code Effect} to be used in
* all cases where a filter has a null input
* @return the transformed point in the coordinate space of the result
*/
public Point2D transform(Point2D p, Effect defaultInput) {
return p;
}
/**
* Transform the specified point {@code p} from the coordinate space
* of the output of the effect into the coordinate space of the
* primary content input.
* In essence, this method asks the question "Which source coordinate
* contributes most to the definition of the output at the specified
* coordinate?"
*
* The definition of which input represents the primary content input
* and how the coordinate space of that input compares to the coordinate
* space of the result varies from effect to effect.
* Note that some effects may have a reasonable definition of how to
* map destination coordinates back to source coordinates, but not the
* reverse.
* In particular, effects which produce entirely synthetic results not
* based on any content input may not be able to give a meaningful
* result to this query and may return undefined coordinates such as
* {@code 0}, {@code Infinity}, or {@code NaN}.
* Most effects perform simple transformations of the color of each
* pixel and so represent an identity transform and return the point
* unchanged.
*
* @param p the point in the coordinate space of the result output
* to be transformed
* @param defaultInput the default input {@code Effect} to be used in
* all cases where a filter has a null input
* @return the untransformed point in the coordinate space of the
* primary content input
*/
public Point2D untransform(Point2D p, Effect defaultInput) {
return p;
}
/**
* Returns a new image that is most compatible with the
* given {@code FilterContext}. This method will select the image
* type that is most appropriate for use with the current rendering
* pipeline, graphics hardware, and screen pixel layout.
* The image will be cleared prior to being returned.
*
* This method may return {@code null} if the image can't be created so
* callers have to check for return value.
*
* @param fctx the {@code FilterContext} for the target screen device
* @param w the width of the image
* @param h the height of the image
* @return a new image with the given dimensions, or null if one
* can't be created
* @throws IllegalArgumentException if {@code gc} is null, or if
* either {@code w} or {@code h} is non-positive
*/
public static Filterable createCompatibleImage(FilterContext fctx, int w, int h) {
return Renderer.getRenderer(fctx).createCompatibleImage(w, h);
}
/**
* Returns an image that is most compatible with the
* given {@code FilterContext}. This method will select the image
* type that is most appropriate for use with the current rendering
* pipeline, graphics hardware, and screen pixel layout.
* The image will be cleared prior to being returned.
*
* Note that the framework attempts to pool images for recycling purposes
* whenever possible. Therefore, when finished using an image returned
* by this method, it is highly recommended that you
* {@link #releaseCompatibleImage release} the image back to the
* shared pool for others to use.
*
* This method may return {@code null} if the image can't be created so
* callers have to check for return value.
*
* @param fctx the {@code FilterContext} for the target screen device
* @param w the width of the image
* @param h the height of the image
* @return an image with the given dimensions or null if one can't
* be created
* @throws IllegalArgumentException if {@code gc} is null, or if
* either {@code w} or {@code h} is non-positive
* @see #releaseCompatibleImage
*/
public static Filterable getCompatibleImage(FilterContext fctx, int w, int h) {
return Renderer.getRenderer(fctx).getCompatibleImage(w, h);
}
/**
* Releases an image created by the
* {@link #getCompatibleImage getCompatibleImage()} method
* back into the shared pool.
*
* @param fctx the {@code FilterContext} for the target screen device
* @param image the image to be released
* @see #getCompatibleImage
*/
public static void releaseCompatibleImage(FilterContext fctx, Filterable image) {
Renderer.getRenderer(fctx).releaseCompatibleImage(image);
}
/**
* Whether an opacity for any pixel is different (lower)
* than the corresponding pixel in the default input.
* It is always safe to return true from this method,
* though the consequences may be that the caller chooses to not utilize a planned optimization.
* @return true if this effect may reduce opacity of some pixels of one of it's input
* (and thus the default input) or it's relevant input(s) might have reduced opaque pixels
* of the default input already.
*/
public abstract boolean reducesOpaquePixels();
/**
* A set of values that represent the possible levels of acceleration
* for an {@code Effect} implementation.
*
* @see Effect#getAccelType
*/
public enum AccelType {
/**
* Indicates that this {@code Effect} is implemented on top of
* intrinsic operations built in to the Java 2D APIs.
*/
INTRINSIC("Intrinsic"),
/**
* Indicates that this {@code Effect} is implemented in software
* (i.e., running on the CPU), without any special acceleration.
*/
NONE("CPU/Java"),
/**
* Indicates that this {@code Effect} is implemented in software
* (i.e., running on the CPU), accelerated using native
* SIMD instructions (e.g. SSE).
*/
SIMD("CPU/SIMD"),
/**
* Indicates that this {@code Effect} is implemented in software
* (i.e., running on the CPU), accelerated using native
* fixed-point arithmetic.
*/
FIXED("CPU/Fixed"),
/**
* Indicates that this {@code Effect} is being accelerated in
* graphics hardware via OpenGL.
*/
OPENGL("OpenGL"),
/**
* Indicates that this {@code Effect} is being accelerated in
* graphics hardware via Direct3D.
*/
DIRECT3D("Direct3D");
private String text;
private AccelType(String text) {
this.text = text;
}
@Override
public String toString() {
return text;
}
}
/**
* Returns one of the {@link AccelType AccelType} values, indicating
* whether this {@code Effect} is accelerated in hardware for the
* given {@code FilterContext}.
*
* @param config the {@code FilterContext} that will be used
* for performing the filter operation
* @return one of the {@code AccelType} values
*/
public abstract AccelType getAccelType(FilterContext fctx);
}