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

com.sun.scenario.effect.Effect Maven / Gradle / Ivy

There is a newer version: 24-ea+15
Show newest version
/*
 * 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); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy