ImageConf.java

package swingtree.style;

import com.google.errorprone.annotations.Immutable;
import org.jspecify.annotations.Nullable;
import swingtree.UI;
import swingtree.api.IconDeclaration;
import swingtree.layout.Size;

import javax.swing.ImageIcon;
import java.awt.*;
import java.util.Objects;
import java.util.Optional;

/**
 *  This class represents the style of an image which can be drawn onto the inner
 *  area of a component.
 *  <b>Note that the inner component area is the area enclosed by the border, which
 *  is itself not part of said area!</b>
 *  <p>
 *  The following properties with their respective purpose are available:
 *  <br>
 *  <ol>
 *      <li><b>Layer:</b>
 *          The layer onto which the image will be drawn.
 *          Layers exist to determine the order in which something is drawn onto the component.
 *          Here a list of available layers:
 *          <ul>
 *              <li>{@link swingtree.UI.Layer#BACKGROUND}</li>
 *              <li>{@link swingtree.UI.Layer#CONTENT}   </li>
 *              <li>{@link swingtree.UI.Layer#BORDER}    </li>
 *              <li>{@link swingtree.UI.Layer#FOREGROUND}</li>
 *          </ul>
 *      </li>
 *      <li><b>Primer:</b>
 *          The primer color of the image style which will
 *          be used as a filler color for the image background.
 *          The background is the inner component area of the component.
 *      </li>
 *      <li><b>Image:</b>
 *          The image which will be drawn onto the component,
 *          which may be specified as an instance of {@link Image}, {@link ImageIcon}
 *          or path to an image file (see {@link swingtree.UI#findIcon(String)}).
 *      </li>
 *      <li><b>Placement:</b>
 *          The placement type determines where the image will be drawn onto the component.
 *          The following placement options are available:
 *          <ul>
 *              <li> {@link swingtree.UI.Placement#CENTER} </li>
 *              <li> {@link swingtree.UI.Placement#TOP_LEFT} </li>
 *              <li> {@link swingtree.UI.Placement#TOP_RIGHT} </li>
 *              <li> {@link swingtree.UI.Placement#BOTTOM_LEFT} </li>
 *              <li> {@link swingtree.UI.Placement#BOTTOM_RIGHT} </li>
 *              <li> {@link swingtree.UI.Placement#TOP} </li>
 *              <li> {@link swingtree.UI.Placement#BOTTOM} </li>
 *              <li> {@link swingtree.UI.Placement#LEFT} </li>
 *              <li> {@link swingtree.UI.Placement#RIGHT} </li>
 *          </ul>
 *      </li>
 *      <li><b>Repeat:</b>
 *          If this flag is set to {@code true}, then the image may be painted
 *          multiple times so that it fills up the entire inner component area.
 *          There will not be a noticeable effect of this flag if the
 *          image already fills out the inner component area (see {@link #autoFit(boolean)}, {@link #size(int, int)}).
 *      </li>
 *      <li><b>Fit-Mode:</b>
 *          If this enum determines how the image will be stretched or shrunk
 *          to fill the inner component area dependent on the specified width and height,
 *          meaning that if the width was not specified explicitly through {@link #width(Integer)}
 *          then the image will be scaled to fit the inner component width,
 *          and if a height was not specified through {@link #height(Integer)} then
 *          the image will be scaled to fit the inner component height. <br>
 *          <b>Note that the inner component area is the area enclosed by the border, which
 *          is itself not part of said area!</b>
 *      </li>
 *      <li><b>Width and Height:</b>
 *          These properties allow you to specify the width and height of the image.
 *          If the width or height is not specified, then the image will be drawn
 *          with its original width or height or it will be scaled to fit the inner component area
 *          if {@link #autoFit(boolean)} is set to {@code true}.
 *      </li>
 *      <li><b>Opacity:</b>
 *          This property allows you to specify the opacity of the image.
 *          The opacity must be between 0.0f and 1.0f, where 0.0f means that the image is completely transparent
 *          and 1.0f means that the image is completely opaque.
 *      </li>
 *      <li><b>Padding:</b>
 *          This property allows you to specify the padding of the image.
 *          The padding is the space between the image and the inner component area.
 *          The padding can be specified for each side of the image individually
 *          or for all sides at once.
 *      </li>
 *      <li><b>Offset:</b>
 *          The offset consists of two integers, one for the horizontal offset
 *          and one for the vertical offset. <br>
 *          It allows you to specify the offset of the image from the placement position.
 *          This means that after the image has been placed onto the component,
 *          it will be moved by the specified offset in the horizontal and vertical direction.
 *      </li>
 *      <li><b>Clip Area:</b>
 *          The clip area determines onto which part of the component the image will be drawn.
 *          The following clip areas are available:
 *          <ul>
 *              <li>{@link swingtree.UI.ComponentArea#ALL} -
 *              The entire component, which is the union of all other clip
 *              areas ({@code INTERIOR + EXTERIOR + BORDER + CONTENT}).
 *              </li>
 *              <li>{@link swingtree.UI.ComponentArea#INTERIOR} -
 *              The inner component area, which is defined as {@code ALL - EXTERIOR - BORDER}.
 *              </li>
 *              <li>{@link swingtree.UI.ComponentArea#EXTERIOR} -
 *              The outer component area, which can be expressed as {@code ALL - INTERIOR - BORDER},
 *              or {@code ALL - CONTENT}.
 *              </li>
 *              <li>{@link swingtree.UI.ComponentArea#BORDER} -
 *              The border of the component, which is the area between the inner and outer component area
 *              and which can be expressed as {@code ALL - INTERIOR - EXTERIOR}.
 *              </li>
 *              <li>{@link swingtree.UI.ComponentArea#BODY} -
 *              The body of the component is the inner component area including the border area.
 *              It can be expressed as {@code ALL - EXTERIOR}, or {@code INTERIOR + BORDER}.
 *              </li>
 *          </ul>
 *          <b>Note that the inner/interior component area is the area enclosed by (and excluding) the border,
 *          whereas the exterior component area is the area surrounding the border.
 *          The component body area is the interior/inner component area plus the border.</b>
 *          <p>
 *          The default clip area is {@link swingtree.UI.ComponentArea#BODY}
 *          as this is the area which is most commonly used.
 *      </li>
 *  </ol>
 *  <p>
 *  <b>Take a look at the following example:</b>
 *  <pre>{@code
 *      of(component).withStyle( it -> it
 *          .image( image -> image
 *              .layer(Layer.BACKGROUND)
 *              .image(image)
 *              .placement(Placement.CENTER)
 *              .autoFit(false)
 *              .repeat(true)
 *              .primer(Color.CYAN)
 *              .padding(12)
 *          )
 *      );
 *  }</pre>
 *  <p>
 *      This will draw the specified image onto the background layer of the component.
 *      The image will be drawn at the center of the inner component area with a padding of 12,
 *      without being scaled to fit the inner component area, instead the size of the image
 *      will be used. <br>
 *      If it does not fill the entire inner component area based on its size, then
 *      it will be repeated across said area, and the primer color
 *      will be used as a filler color for the parts of the image which
 *      are transparent.
 *  </p>
 **/
@Immutable
@SuppressWarnings("Immutable")
public final class ImageConf implements Simplifiable<ImageConf>
{
    static final UI.Layer DEFAULT_LAYER = UI.Layer.BACKGROUND;
    private static final ImageConf _NONE = new ImageConf(
                                                null,
                                                null,
                                                UI.Placement.UNDEFINED,
                                                false,
                                                UI.FitComponent.NO,
                                                Size.unknown(),
                                                1.0f,
                                                Outline.none(),
                                                Offset.none(),
                                                UI.ComponentArea.BODY
                                            );

    static ImageConf none() { return _NONE; }

    static ImageConf of(
        @Nullable Color     primer,
        @Nullable ImageIcon image,
        UI.Placement        placement,
        boolean             repeat,
        UI.FitComponent     fitMode,
        Size                size,
        float               opacity,
        Outline             padding,
        Offset              offset,
        UI.ComponentArea    clipArea
    ) {
        if (
            Objects.equals( primer, _NONE._primer )   &&
            Objects.equals( image , _NONE._image  )   &&
            placement.equals( _NONE._placement ) &&
            repeat   == _NONE._repeat            &&
            fitMode  .equals( _NONE._fitMode   ) &&
            size     .equals( _NONE._size      ) &&
            opacity  == _NONE._opacity           &&
            padding  .equals( _NONE._padding   ) &&
            offset   .equals( _NONE._offset    ) &&
            clipArea .equals( _NONE._clipArea  )
        )
            return _NONE;
        else
            return new ImageConf(primer, image, placement, repeat, fitMode, size, opacity, padding, offset, clipArea);
    }


    private final @Nullable Color     _primer;
    private final @Nullable ImageIcon _image;
    private final UI.Placement        _placement;
    private final boolean             _repeat;
    private final UI.FitComponent     _fitMode;
    private final Size                _size;
    private final float               _opacity;
    private final Outline             _padding;
    private final Offset              _offset;
    private final UI.ComponentArea    _clipArea;


    private ImageConf(
        @Nullable Color      primer,
        @Nullable ImageIcon  image,
        UI.Placement         placement,
        boolean              repeat,
        UI.FitComponent      fitMode,
        Size                 size,
        float                opacity,
        Outline              padding,
        Offset               offset,
        UI.ComponentArea     clipArea
    ) {
        _primer    = primer;
        _image     = image;
        _placement = Objects.requireNonNull(placement);
        _repeat    = repeat;
        _fitMode   = Objects.requireNonNull(fitMode);
        _size      = Objects.requireNonNull(size);
        _opacity   = opacity;
        _padding   = Objects.requireNonNull(padding);
        _offset    = Objects.requireNonNull(offset);
        _clipArea  = Objects.requireNonNull(clipArea);
        if ( _opacity < 0.0f || _opacity > 1.0f )
            throw new IllegalArgumentException("transparency must be between 0.0f and 1.0f");
    }

    Optional<Color> primer() { return Optional.ofNullable(_primer); }

    Optional<ImageIcon> image() { return Optional.ofNullable(_image); }

    UI.Placement placement() {
        if ( _placement == UI.Placement.UNDEFINED && _image instanceof SvgIcon )
            return ((SvgIcon) _image).getPreferredPlacement();

        return _placement;
    }

    boolean repeat() { return _repeat; }

    UI.FitComponent fitMode() { return _fitMode; }

    Optional<Integer> width() { return _size.width().map(Number::intValue); }

    Optional<Integer> height() { return _size.height().map(Number::intValue); }

    float opacity() { return _opacity; }

    Outline padding() { return _padding; }
    
    int horizontalOffset() { return (int) _offset.x(); }
    
    int verticalOffset() { return (int) _offset.y(); }

    UI.ComponentArea clipArea() { return _clipArea; }

    /**
     *  Here you can specify the <b>primer color of the image style</b> which will be used
     *  as a filler color for the image background. <br>
     *  Note that the primer color will not be visible if the image is opaque and it fills the entire component.
     *
     * @param color The primer color of the image style.
     * @return A new {@link ImageConf} instance with the specified primer color.
     */
    public ImageConf primer( Color color ) {
        Objects.requireNonNull(color, "Use UI.Color.UNDEFINED instead of null to represent the absence of a color.");
        if ( StyleUtil.isUndefinedColor(color) )
            color = null;
        if ( Objects.equals(color, _primer) )
            return this;
        return ImageConf.of(color, _image, _placement, _repeat, _fitMode, _size, _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  Here you can specify the <b>image</b> which will be drawn onto the component.
     *  The supplied object must be an instance of {@link Image} implementation.
     *
     * @param image The image which will be drawn onto the component.
     * @return A new {@link ImageConf} instance with the specified image.
     */
    public ImageConf image(Image image ) {
        return ImageConf.of(_primer, image == null ? null : new ImageIcon(image), _placement, _repeat, _fitMode, _size, _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  Here you can specify the <b>image icon</b> which will be drawn onto the component.
     *  The supplied object must be an instance of {@link ImageIcon} implementation.
     *
     * @param image The image icon which will be drawn onto the component.
     * @return A new {@link ImageConf} instance with the specified image.
     */
    public ImageConf image(ImageIcon image ) {
        return ImageConf.of(_primer, image, _placement, _repeat, _fitMode, _size, _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  Here you can specify the <b>path to the image in the form of an {@link IconDeclaration}</b>
     *  for which the icon will be loaded and drawn onto the component.
     *  If the icon could not be found, then the image will not be drawn.
     *  The path is relative to the classpath or may be an absolute path.
     *
     * @param image The path to the (icon) image in the form of an {@link IconDeclaration}.
     * @return A new {@link ImageConf} instance with the specified image.
     * @throws NullPointerException If the specified {@code image} is null.
     */
    public ImageConf image(IconDeclaration image ) {
        Objects.requireNonNull(image);
        return image.find().map(this::image).orElse(this);
    }

    /**
     *  Here you can specify the <b>path to the image</b> for which the icon will be loaded,
     *  cached and drawn onto the component.
     *  If the icon could not be found, then the image will not be drawn.
     *  The path is relative to the classpath or may be an absolute path.
     *  (see {@link swingtree.UI#findIcon(String)}).
     *
     * @param path The path to the (icon) image.
     * @return A new {@link ImageConf} instance with the specified image.
     * @throws NullPointerException If the specified {@code path} is null.
     */
    public ImageConf image(String path ) {
        Objects.requireNonNull(path);
        return image(() -> path);
    }

    /**
     *  Here you can specify the <b>placement</b> of the image onto the component.
     *  The default placement is {@link swingtree.UI.Placement#CENTER}. <br>
     *  Here a list of available options and their effect:
     *  <ul>
     *      <li>{@link swingtree.UI.Placement#CENTER} -
     *          The image will be drawn at the center of the component.
     *          So the center of the image will be at the center of the inner component area.
     *      </li>
     *      <li>{@link swingtree.UI.Placement#TOP_LEFT} -
     *          The image will be drawn beginning at the top left corner of the inner component area.
     *          So the top left corner of the image will be in the top left corner of the inner component area.
     *      </li>
     *      <li>{@link swingtree.UI.Placement#TOP_RIGHT} -
     *          The image will be placed in the top right corner of the inner component area.
     *          So the top right corner of the image will be in the top right corner of the inner component area.
     *      </li>
     *      <li>{@link swingtree.UI.Placement#BOTTOM_LEFT} -
     *          The image will be drawn in the bottom left corner of the inner component area.
     *          So the bottom left corner of the image will be in the bottom left corner of the inner component area.
     *      </li>
     *      <li>{@link swingtree.UI.Placement#BOTTOM_RIGHT} -
     *          The image will be drawn in the bottom right corner of the inner component area.
     *          So the bottom right corner of the image will be in the bottom right corner of the inner component area.
     *      </li>
     *      <li>{@link swingtree.UI.Placement#TOP} -
     *          The image will be drawn in the top center of the inner component area.
     *          So the top center of the image will be in the top center of the inner component area.
     *      </li>
     *      <li>{@link swingtree.UI.Placement#BOTTOM} -
     *          The image will be drawn in the bottom center of the inner component area.
     *          So the bottom center of the image will be in the bottom center of the inner component area.
     *      </li>
     *      <li>{@link swingtree.UI.Placement#LEFT} -
     *          The image will be drawn in the left center of the inner component area.
     *          So the left center of the image will be in the left center of the inner component area.
     *      </li>
     *      <li>{@link swingtree.UI.Placement#RIGHT} -
     *          The image will be drawn in the right center of the inner component area.
     *          So the right center of the image will be in the right center of the inner component area.
     *      </li>
     *      <li>{@link swingtree.UI.Placement#UNDEFINED} -
     *          The image will be drawn at a position which is determined by other
     *          factors such as the image size and the component size.
     *  </ul>
     *
     * @param placement The placement of the image onto the component.
     * @return A new {@link ImageConf} instance with the specified placement.
     */
    public ImageConf placement(UI.Placement placement ) {
        return ImageConf.of(_primer, _image, placement, _repeat, _fitMode, _size, _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  If this flag is set to {@code true}, then the image may be painted
     *  multiple times so that it fills up the entire inner component area.
     *  There will not be a noticeable effect of this flag if the
     *  image already fills out the inner component area (see {@link #autoFit(boolean)}, {@link #size(int, int)}).
     *
     * @param repeat Weather the image should be painted repeatedly across the inner component area.
     * @return A new {@link ImageConf} instance with the specified {@code repeat} flag value.
     */
    public ImageConf repeat(boolean repeat ) {
        return ImageConf.of(_primer, _image, _placement, repeat, _fitMode, _size, _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  If this flag is set to {@code true}, then the image will be stretched or shrunk
     *  to fill the inner component area dependent on the specified width and height,
     *  meaning that if the width was not specified explicitly through {@link #width(Integer)}
     *  then the image will be scaled to fit the inner component width,
     *  and if a height was not specified through {@link #height(Integer)} then
     *  the image will be scaled to fit the inner component height. <br>
     *  <b>Note that the inner component area is the area enclosed by the border, which
     *  is itself not part of said area!</b>
     *
     * @param autoFit If true the image will be scaled to fit the inner component area for every
     *                dimension which was not specified,
     *                otherwise the image will not be scaled to fit the inner component area.
     * @return A new {@link ImageConf} instance with the specified {@code autoFit} flag value.
     */
    public ImageConf autoFit(boolean autoFit ) {
        UI.FitComponent fit = autoFit ? UI.FitComponent.WIDTH_AND_HEIGHT : UI.FitComponent.NO;
        return ImageConf.of(_primer, _image, _placement, _repeat, fit, _size, _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  There are different kinds of strategies to fit the image onto the component.
     *  These strategies are identified using the {@link UI.FitComponent} enum
     *  which defines the following fit modes:
     *  <ul>
     *      <li>{@link UI.FitComponent#NO} -
     *          The image will not be scaled to fit the inner component area.
     *      </li>
     *      <li>{@link UI.FitComponent#WIDTH} -
     *          The image will be scaled to fit the inner component width.
     *      </li>
     *      <li>{@link UI.FitComponent#HEIGHT} -
     *          The image will be scaled to fit the inner component height.
     *      </li>
     *      <li>{@link UI.FitComponent#WIDTH_AND_HEIGHT} -
     *          The image will be scaled to fit both the component width and height.
     *      </li>
     *      <li>{@link UI.FitComponent#MAX_DIM} -
     *          The image will be scaled to fit the smaller
     *          of the two dimension of the inner component area.
     *      </li>
     *      <li>{@link UI.FitComponent#MIN_DIM} -
     *          The image will be scaled to fit the larger
     *          of the two dimension of the inner component area.
     *      </li>
     *  </ul>
     * @param fit The fit mode of the image.
     * @return A new {@link ImageConf} instance with the specified {@code fit} mode.
     */
    public ImageConf fitMode( UI.FitComponent fit ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, fit, _size, _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  Ensures that the image has the specified width.
     *
     * @param width The width of the image.
     * @return A new {@link ImageConf} instance with the specified {@code width}.
     */
    public ImageConf width(Integer width ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, _size.withWidth(width), _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  Ensures that the image has the specified height.
     *
     * @param height The height of the image.
     * @return A new {@link ImageConf} instance with the specified {@code height}.
     */
    public ImageConf height( Integer height ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, _size.withHeight(height), _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  Ensures that the image has the specified width and height.
     *
     * @param width The width of the image.
     * @param height The height of the image.
     * @return A new {@link ImageConf} instance with the specified {@code width} and {@code height}.
     */
    public ImageConf size(int width, int height ) {
        return size(Size.of(width, height));
    }

    /**
     *  Ensures that the image has the specified width and height.
     *
     * @param size The size of the image.
     * @return A new {@link ImageConf} instance with the specified {@code size}.
     */
    public ImageConf size(Size size ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, size, _opacity, _padding, _offset, _clipArea);
    }

    /**
     *  This method allows you to specify the opacity of the image.
     *  The opacity must be between 0.0f and 1.0f, where 0.0f means that the image is completely transparent
     *  and 1.0f means that the image is completely opaque.
     *
     * @param opacity The opacity of the image.
     * @return A new {@link ImageConf} instance with the specified opacity.
     */
    public ImageConf opacity(float opacity ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, _size, opacity, _padding, _offset, _clipArea);
    }

    /**
     *  This method allows you to specify the padding of the image.
     *  The padding is the space between the image and the inner component area.
     *
     * @param padding The padding of the image.
     * @return A new {@link ImageConf} instance with the specified padding.
     */
    ImageConf padding(Outline padding ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, _size, _opacity, padding, _offset, _clipArea);
    }

    /**
     *  This method allows you to specify the padding of the image.
     *  The padding is the space between the image and the inner component area.
     *
     * @param top The top padding of the image.
     * @param right The right padding of the image.
     * @param bottom The bottom padding of the image.
     * @param left The left padding of the image.
     * @return A new {@link ImageConf} instance with the specified padding.
     */
    public ImageConf padding(int top, int right, int bottom, int left ) {
        return padding(Outline.of(top, right, bottom, left));
    }

    /**
     *  This method allows you to specify the padding of the image.
     *  The padding is the space between the image and the inner component area.
     *
     * @param topBottom The top and bottom padding of the image.
     * @param leftRight The left and right padding of the image.
     * @return A new {@link ImageConf} instance with the specified padding.
     */
    public ImageConf padding(int topBottom, int leftRight ) {
        return padding(Outline.of(topBottom, leftRight, topBottom, leftRight));
    }

    /**
     *  This method allows you to specify the padding for all sides of the image.
     *  The padding is the space between the image and the inner component area.
     *
     * @param padding The padding of the image.
     * @return A new {@link ImageConf} instance with the specified padding.
     */
    public ImageConf padding(int padding ) {
        return padding(Outline.of(padding, padding, padding, padding));
    }

    /**
     *  Use this to specify the vertical and horizontal offset by which the image will be moved
     *  and drawn onto the component.
     *
     *  @param x The horizontal offset.
     *  @param y The vertical offset.
     *  @return A new {@link ImageConf} instance with the specified offset.
     */
    public ImageConf offset(int x, int y ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, _size, _opacity, _padding, Offset.of(x, y), _clipArea);
    }

    /**
     *  Use this to specify the horizontal offset by which the image will be moved
     *  and drawn onto the component.
     *
     *  @param x The horizontal offset.
     *  @return A new {@link ImageConf} instance with the specified offset.
     */
    public ImageConf horizontalOffset(int x ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, _size, _opacity, _padding, _offset.withX(x), _clipArea);
    }

    /**
     *  Use this to specify the vertical offset by which the image will be moved
     *  and drawn onto the component.
     *
     *  @param y The vertical offset.
     *  @return A new {@link ImageConf} instance with the specified offset.
     */
    public ImageConf verticalOffset(int y ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, _size, _opacity, _padding, _offset.withY(y), _clipArea);
    }

    /**
     *  Use this to specify the clip area of the image,
     *  which determines on which part of the component the image will be drawn.
     *  The {@link swingtree.UI.ComponentArea} enum defines the following clip areas:
     *  <ul>
     *      <li>{@link swingtree.UI.ComponentArea#ALL} -
     *      The image will be drawn onto the entire component, which
     *      is the union of all other clip areas ({@code INTERIOR + EXTERIOR + BORDER + CONTENT}).
     *      </li>
     *      <li>{@link swingtree.UI.ComponentArea#INTERIOR} -
     *      The image will be drawn onto the inner component area,
     *      which is defined as {@code ALL - EXTERIOR - BORDER}.
     *      </li>
     *      <li>{@link swingtree.UI.ComponentArea#EXTERIOR} -
     *      The image will be drawn onto the outer component area,
     *      which can be expressed as {@code ALL - INTERIOR - BORDER},
     *      or {@code ALL - CONTENT}.
     *      </li>
     *      <li>{@link swingtree.UI.ComponentArea#BORDER} -
     *      The image will be drawn onto the border of the component,
     *      which is the area between the inner and outer component area
     *      and which can be expressed as {@code ALL - INTERIOR - EXTERIOR}.
     *      </li>
     *      <li>{@link swingtree.UI.ComponentArea#BODY} -
     *      The image will be drawn onto the component body,
     *      which is the inner component area including the border area.
     *      It can be expressed as {@code ALL - EXTERIOR}, or {@code INTERIOR + BORDER}.
     *      </li>
     *  </ul>
     *  The default clip area is {@link swingtree.UI.ComponentArea#INTERIOR},
     *  which means that the image will be drawn onto the inner component area.
     *  <p>
     *  Use {@link UI.ComponentArea#ALL} to draw the image without any additional clipping
     *  onto the entire component, which may also cover the border and margin area of the component.
     *
     *  @param clipArea The clip area of the image.
     *  @return A new {@link ImageConf} instance with the specified clip area.
     */
    public ImageConf clipTo(UI.ComponentArea clipArea ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, _size, _opacity, _padding, _offset, clipArea);
    }

    ImageConf _scale(double scaleFactor ) {
        return ImageConf.of(_primer, _image, _placement, _repeat, _fitMode, _size.scale(scaleFactor), _opacity, _padding.scale(scaleFactor), _offset.scale(scaleFactor), _clipArea);
    }

    @Override
    public ImageConf simplified() {
        if ( this.equals(_NONE) )
            return _NONE;

        ImageIcon simplifiedImage = _opacity == 0.0f ? null : _image;
        Color simplifiedPrimer = _primer == null || _primer.getAlpha() == 0 ? null : _primer;

        if ( StyleUtil.isUndefinedColor(simplifiedPrimer) )
            simplifiedPrimer = null;

        if ( simplifiedImage == null && simplifiedPrimer == null )
            return none();

        return ImageConf.of(
                    simplifiedPrimer,
                    simplifiedImage,
                    _placement,
                    _repeat,
                    _fitMode,
                    _size,
                    _opacity,
                    _padding.simplified(),
                    _offset,
                    _clipArea
                );
    }

    @Override
    public int hashCode() {
        return Objects.hash(_primer, _image, _placement, _repeat, _fitMode, _size, _opacity, _padding, _offset, _clipArea);
    }

    @Override
    public boolean equals( Object obj ) {
        if ( obj == null ) return false;
        if ( obj == this ) return true;
        if ( obj.getClass() != getClass() ) return false;
        ImageConf rhs = (ImageConf) obj;
        return Objects.equals(_primer,    rhs._primer)    &&
               Objects.equals(_image,     rhs._image)     &&
               Objects.equals(_placement, rhs._placement) &&
               _repeat == rhs._repeat    &&
               Objects.equals(_fitMode,   rhs._fitMode)   &&
               Objects.equals(_size,      rhs._size)      &&
               _opacity == rhs._opacity   &&
               Objects.equals(_padding,   rhs._padding)   &&
               Objects.equals(_offset,    rhs._offset)    &&
               Objects.equals(_clipArea,  rhs._clipArea);
    }

    @Override
    public String toString() {
        if ( this.equals(_NONE) ) return this.getClass().getSimpleName()+"[NONE]";
        return this.getClass().getSimpleName() + "[" +
                    "primer="        + StyleUtil.toString(_primer)                          + ", " +
                    "image="         + ( _image == null ? "?" : _image.toString() )            + ", " +
                    "placement="     + _placement                                              + ", " +
                    "repeat="        + _repeat                                                 + ", " +
                    "fitComponent="  + _fitMode                                                + ", " +
                    "width="         + _size.width().map(Objects::toString).orElse("?")  + ", " +
                    "height="        + _size.height().map(Objects::toString).orElse("?") + ", " +
                    "opacity="       + _opacity                                                + ", " +
                    "padding="       + _padding                                                + ", " +
                    "offset="        + _offset                                                 + ", " +
                    "clipArea="      + _clipArea                                               +
                "]";
    }

}