/* * Copyright (c) 2014, 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.impl.state; import com.sun.javafx.geom.Rectangle; import com.sun.scenario.effect.Color4f; import com.sun.scenario.effect.FilterContext; import com.sun.scenario.effect.ImageData; import com.sun.scenario.effect.impl.EffectPeer; import com.sun.scenario.effect.impl.Renderer; import java.nio.FloatBuffer; /** * The {@code LinearConvolveRenderState} object manages the strategies of * applying a 1 or 2 pass linear convolution to an input and calculates the * necessary data for the filter shader to compute the convolution. * The object is constructed based on the transform that was provided for * the entire filter operation and determines its strategy. * Methods prefixed by {@code getInput*()} return information about the * general plan for obtaining and managing the input source image. * After the input effect is called with the information from the * {@code getInput*()} methods and its result {@code ImageData} is obtained, * the {@code validatePassInput()} method is used to examine the size and * transform of the supplied input and determine the parameters needed to * perform the convolution for the first pass. * Once validated, the methods prefixed by {@code getPass*()} return information * for applying the convolution for that validated pass. * If necessary, the {@code validatePassInput()} method is called on the * results of the first pass to calculate further data for the second pass. * Finally the {@code getResultTransform()} method is used to possibly transform * the final resulting {@code ImageData} of the last pass. */ public abstract class LinearConvolveRenderState implements RenderState { public static final int MAX_KERNEL_SIZE = 128; static final float MIN_EFFECT_RADIUS = 1.0f / 256.0f; static final float[] BLACK_COMPONENTS = Color4f.BLACK.getPremultipliedRGBComponents(); public enum PassType { /** * The kernel on this pass will be applied horizontally with * the kernel centered symmetrically around each pixel. * The specific conditions indicated by this type are: * */ HORIZONTAL_CENTERED, /** * The kernel on this pass will be applied vertically with * the kernel centered symmetrically around each pixel. * The specific conditions indicated by this type are: * */ VERTICAL_CENTERED, /** * The kernel on this pass can be applied in any direction or with * any kind of offset. * No assumptions are made about the offset and delta of the kernel * vector. */ GENERAL_VECTOR, }; /** * Returns the peer sample count for a given kernel size. There are * only a few peers defined to operate on specific sizes of convolution * kernel. If there are peers defined only for kernel sizes of 8 and 16 * and a given effect has a linear convolution kernel with 5 weights, * then the peer for size 8 will be used and the buffer of weights must * be padded out to the appropriate size with 0s so that the shader * constant pool will be fully initialized and the extra unneeded * convolution samples will be ignored by the 0 weights. * * @param ksize the number of computed convolution kernel weights * @return the number of convolution weights which will be applied by * the associated peer. */ public static int getPeerSize(int ksize) { if (ksize < 32) return ((ksize + 3) & (~3)); if (ksize <= MAX_KERNEL_SIZE) return ((ksize + 31) & (~31)); throw new RuntimeException("No peer available for kernel size: "+ksize); } /** * Returns true if summing v over size pixels ends up close enough to * 0.0 that we will not have shifted the sampling by enough to see any * changes. * "Close enough" in this context is measured by whether or not using * the coordinate in a linear interpolating sampling operation on 8-bit * per sample images will cause the next pixel over to be blended in. * * @param v the value being summed across the pixels * @param size the number of pixels being summed across * @return true if the accumulated value will be negligible */ static boolean nearZero(float v, int size) { return (Math.abs(v * size) < 1.0/512.0); } /** * Returns true if summing v over size pixels ends up close enough to * size.0 that we will not have shifted the sampling by enough to see any * changes. * "Close enough" in this context is measured by whether or not using * the coordinate in a linear interpolating sampling operation on 8-bit * per sample images will cause the next pixel over to be blended in. * * @param v the value being summed across the pixels * @param size the number of pixels being summed across * @return true if the accumulated value will be close enough to size */ static boolean nearOne(float v, int size) { return (Math.abs(v * size - size) < 1.0/512.0); } /** * Returns true if this is a shadow convolution operation where a * constant color is substituted for the color components of the * output. * This value is dependent only on the original {@code Effect} from which * this {@code RenderState} was instantiated and does not vary as the * filter operation progresses. * * @return true if this is a shadow operation */ public abstract boolean isShadow(); /** * Returns the {@code Color4f} representing the shadow color if this * is a shadow operation. * This value is dependent only on the original {@code Effect} from which * this {@code RenderState} was instantiated and does not vary as the * filter operation progresses. * * @return the {@code Color4f} for the shadow color, or null */ public abstract Color4f getShadowColor(); /** * Returns the size of the desired convolution kernel for the given pass * as it would be applied in the coordinate space indicated by the * {@link #getInputKernelSize(int)} method. * This value is calculated at the start of the render operation and * does not vary as the filter operation progresses, but it may not * represent the actual kernel size used when the indicated pass actually * occurs if the {@link #validatePassInput()} method needs to choose * different values when it sees the incoming image source. * * @param pass the pass for which the intended kernel size is desired * @return the intended kernel size for the requested pass */ public abstract int getInputKernelSize(int pass); /** * Returns true if the resulting operation is globally a NOP operation. * This condition is calculated at the start of the render operation and * is based on whether the perturbations of the convolution kernel would * be noticeable at all in the coordinate space of the output. * * @return true if the operation is a global NOP */ public abstract boolean isNop(); /** * Validates the {@code RenderState} object for a given pass of the * convolution. * The supplied source image is provided so that the {@code RenderState} * object can determine if it needs to change its strategy for how the * convolution operation will be performed and to scale its data for * the {@code getPass*()} methods relative to the source dimensions and * transform. * * @param src the {@code ImageData} object supplied by the source effect * @param pass the pass of the operation being applied (usually horizontal * for pass 0 and vertical for pass 1) * @return the {@code ImageData} to be used for the actual convolution * operation */ public abstract ImageData validatePassInput(ImageData src, int pass); /** * Returns true if the operation of the currently validated pass would * be a NOP operation. * * @return true if the current pass is a NOP */ public abstract boolean isPassNop(); /** * Return the {@code EffectPeer} to be used to perform the currently * validated pass of the convolution operation, or null if this pass * is a NOP. * * @param r the {@code Renderer} being used for this filter operation * @param fctx the {@code FilterContext} being used for this filter operation * @return the {@code EffectPeer} to use for this pass, or null */ public EffectPeer getPassPeer(Renderer r, FilterContext fctx) { if (isPassNop()) { return null; } int ksize = getPassKernelSize(); int psize = getPeerSize(ksize); String opname = isShadow() ? "LinearConvolveShadow" : "LinearConvolve"; return r.getPeerInstance(fctx, opname, psize); } /** * Returns the size of the scaled result image needed to hold the output * for the currently validated pass with the indicated input dimensions. * The image may be further scaled after the shader operation is through * to obtain the final result bounds. * This value is only of use to the actual shader to understand exactly * how much room to allocate for the shader result. * * @param srcdimension the bounds of the input image * @return the bounds of the result image */ public abstract Rectangle getPassResultBounds(Rectangle srcdimension); /** * Return a hint about the way that the weights will be applied to the * pixels for the currently validated pass. * * @return the appropriate {@link PassType} that describes the filtering * operation for this pass of the algorithm */ public PassType getPassType() { return PassType.GENERAL_VECTOR; } /** * A {@link FloatBuffer} padded out to the required size as specified by * the {@link #getPeerSize()} method filled with the convolution weights * needed for the currently validated pass. * * @return a {@code FloatBuffer} containing the kernel convolution weights */ public abstract FloatBuffer getPassWeights(); /** * Returns the maximum number of valid float4 elements that should be * referenced from the buffer returned by getWeights() for the currently * validated pass. * * @return the maximum number of valid float4 elements in the weights buffer */ public abstract int getPassWeightsArrayLength(); /** * Returns an array of 4 floats used to initialize a float4 Shader * constant with the relative starting location of the first weight * in the convolution kernel and the incremental offset between each * sample to be weighted and accumulated. The values are stored in * the array in the following order: *
     *     shadervec.x = vector[0] = incdx // X delta between subsequent samples
     *     shadervec.y = vector[1] = incdy // Y delta between subsequent samples
     *     shadervec.z = vector[2] = startdx // X offset to first convolution sample
     *     shadervec.w = vector[3] = startdy // Y offset to first convolution sample
     * 
* These values are used in the shader loop as follows: *
     *     samplelocation = outputpixellocation.xy + shadervec.zw;
     *     for (each weight) {
     *         sum += weight * sample(samplelocation.xy);
     *         samplelocation.xy += shadervec.xy;
     *     }
     * 
* The values are relative to the texture coordinate space which are * normalized to the range [0,1] over the source texture. * * @return an array of 4 floats representing * {@code [ incdx, incdy, startdx, startdy ]} */ public abstract float[] getPassVector(); /** * For a shadow convolution operation, return the 4 float versions of * the color components, in the range {@code [0, 1]} for the shadow color * to be substituted for the input colors. * This method will only be called if {@link #isShadow()} returns true. * * @return the array of 4 floats representing the shadow color components */ public abstract float[] getPassShadowColorComponents(); /** * Returns the appropriate kernel size for the pass that was last * validated using validateInput(). * * @return the pixel kernel size of the current pass */ public abstract int getPassKernelSize(); }