FilterConf.java
package swingtree.style;
import com.google.errorprone.annotations.Immutable;
import swingtree.UI;
import swingtree.layout.Size;
import java.util.Objects;
/**
* The filter configuration object
* defines if and how a filter should be applied
* on the rendering of the parent of a particular component.
* This may include a blur, a convolution kernel, a scale and translation
* applied to the parent layer. <br>
* Note that this configuration may only have an effect if the component
* it is applied to is non-opaque, which is to say that it does
* not have another other styles which would
* obstruct the rendering of the parent layer. <br>
* <br>
* This class exposes the following properties
* with their respective purpose and default values:
* <ul>
* <li><b>Kernel</b>
* <p>
* The convolution kernel to apply to the parent layer
* using the {@link java.awt.image.ConvolveOp} class.
* Default value is {@link KernelConf#none()}
* </p>
* </li>
* <li><b>Area</b>
* <p>
* The area of the current component to which
* the filter should be applied to.
* Default value is {@link UI.ComponentArea#BODY},
* which includes the interior and border area of the component.
* </p>
* </li><li><b>Offset</b>
* <p>
* The translation to apply to the filtered parent layer.
* Default value is {@code (0.0, 0.0)}, which means no translation.
* </p>
* </li><li><b>Scale</b>
* <p>
* The scale to apply to the filtered parent layer.
* Default value is {@code (1.0, 1.0)}, which means no scaling
* is applied to the parent layer.
* </p>
* </li><li><b>Blur</b>
* <p>
* The blur radius, which is used to apply a gaussian blur
* to the parent layer using the {@link java.awt.image.ConvolveOp} class.
* Default value is {@code 0.0}, which means no blur is applied.
* </p>
* </li>
* </ul>
*/
@Immutable
@SuppressWarnings("Immutable")
public final class FilterConf
{
private static final FilterConf _NONE = new FilterConf(
KernelConf.none(),
UI.ComponentArea.BODY,
Offset.none(),
Scale.none(),
0f
);
static FilterConf none() {
return _NONE;
}
private static FilterConf of(
KernelConf kernel,
UI.ComponentArea area,
Offset offset,
Scale scale,
float blur
) {
blur = Math.max(0, blur);
if (
_NONE._kernel.equals(kernel) &&
_NONE._area.equals(area) &&
_NONE._offset.equals(offset) &&
_NONE._scale.equals(scale) &&
_NONE._blur == blur
) {
return _NONE;
}
return new FilterConf(kernel, area, offset, scale, blur);
}
private final KernelConf _kernel;
private final UI.ComponentArea _area;
private final Offset _offset;
private final Scale _scale;
private final float _blur;
FilterConf(
KernelConf kernel,
UI.ComponentArea area,
Offset offset,
Scale scale,
float blur
) {
_kernel = Objects.requireNonNull(kernel);
_area = Objects.requireNonNull(area);
_offset = Objects.requireNonNull(offset);
_scale = Objects.requireNonNull(scale);
_blur = blur;
}
KernelConf kernel() {
return _kernel;
}
UI.ComponentArea area() {
return _area;
}
Offset offset() {
return _offset;
}
Scale scale() {
return _scale;
}
float blur() {
return _blur;
}
/**
* Use this to configure a custom convolution kernel
* based on a row major matrix represented by a width, height
* and an array of values whose length is the product
* of the supplied width and height.<br>
* Note that this operation will be applied after
* the parent layer was translated and scaled
* and blurred.
*
* @param size The {@link Size} object representing the width and height of the matrix
* to be used as the convolution kernel.
* @param matrix A var args array of double values
* used to form a row major matrix.
* @return An updated filter configuration
* containing the desired convolution kernel.
*/
public FilterConf kernel( Size size, double... matrix ) {
return of(KernelConf.of(size, matrix), _area, _offset, _scale, _blur);
}
/**
* Define the component area which should display the filtered parent.
* Everything outside that area will not be affected by the filter.
* Take a look at the {@link UI.ComponentArea} enum documentation
* for more information on the available areas.
*
* @param area A constant representing the area of the component
* to which the filter should be clipped to.
* @return An updated filter configuration
* with the desired clipping area.
*/
public FilterConf area(UI.ComponentArea area) {
return of(_kernel, area, _offset, _scale, _blur);
}
/**
* Uses the given x and y offset values to translate the
* parent layer before applying other filtering operations.
*
* @param x The amount to translate the parent layer along the x-axis before filtering.
* @param y The amount to translate the parent layer along the y-axis before filtering.
* @return An updated filter configuration having the desired translation.
*/
public FilterConf offset( double x, double y ) {
return of(_kernel, _area, Offset.of(x, y), _scale, _blur);
}
/**
* Scales the parent layer by the given x and y factors
* before applying other filtering operations.
*
* @param x The factor to scale the parent layer along the x-axis before filtering.
* @param y The factor to scale the parent layer along the y-axis before filtering.
* @return An updated filter configuration having the desired scaling.
*/
public FilterConf scale( double x, double y ) {
return of(_kernel, _area, _offset, Scale.of(x, y), _blur);
}
/**
* Supply values greater than 0 to this to apply a gaussian blur
* to the parent layer.
* Note that this operation will be applied after
* the parent layer was translated and scaled.
*
* @param radius The radius of the gaussian blur filter
* to apply to the parent layer.
* @return An updated filter configuration having the desired blur radius.
*/
public FilterConf blur( double radius ) {
return of(_kernel, _area, _offset, _scale, (float)radius);
}
FilterConf _scale( double factor ) {
if ( factor == 1 ) {
return this;
}
if ( this.equals(FilterConf.none()) ) {
return this;
}
return of(_kernel, _area, _offset.scale(factor), _scale, (float) (_blur * factor));
}
FilterConf simplified() {
KernelConf kernel = _kernel.simplified();
if (
KernelConf.none().equals(kernel) &&
_scale.equals(Scale.none()) &&
_offset.equals(Offset.none()) &&
_blur <= 0
) {
return _NONE;
}
return of(kernel, _area, _offset, _scale, _blur);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
FilterConf other = (FilterConf) obj;
return
_kernel.equals(other._kernel) &&
_area.equals(other._area) &&
_offset.equals(other._offset) &&
_scale.equals(other._scale) &&
_blur == other._blur;
}
@Override
public int hashCode() {
return Objects.hash(_kernel, _area, _offset, _scale, _blur);
}
@Override
public String toString() {
if ( this.equals(FilterConf.none()) )
return this.getClass().getSimpleName() + "[NONE]";
return this.getClass().getSimpleName() + "["
+ "kernel=" + _kernel + ", "
+ "area=" + _area + ", "
+ "center=" + _offset + ", "
+ "scale=" + _scale + ", "
+ "blur=" + _blur
+ "]";
}
}