AffineParameters2D.java

/*
 * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.irurueta.geometry;

import com.irurueta.algebra.Matrix;
import com.irurueta.algebra.WrongSizeException;

import java.io.Serializable;

/**
 * This class defines additional parameters that can be defined on affine
 * 2D transformations.
 */
public class AffineParameters2D implements Serializable {

    /**
     * Default scale value having no effect on transformations.
     */
    public static final double DEFAULT_SCALE = 1.0;

    /**
     * Default skewness value having no effect on transformations.
     */
    public static final double DEFAULT_SKEWNESS = 0.0;

    /**
     * Number of inhomogeneous coordinates in 2D space.
     */
    public static final int INHOM_COORDS = 2;

    /**
     * Default threshold to determine whether a matrix is a valid 2x2 upper
     * triangular one.
     */
    private static final double DEFAULT_VALID_THRESHOLD = 1e-8;

    /**
     * Horizontal scale.
     */
    private double scaleX;

    /**
     * Vertical scale.
     */
    private double scaleY;

    /**
     * Skewness factor. If 0.0, horizontal and vertical axes remain orthogonal,
     * otherwise they get slanted.
     */
    private double skewness;

    /**
     * Constructor.
     * Sets default scale and skewness
     */
    public AffineParameters2D() {
        scaleX = scaleY = DEFAULT_SCALE;
        skewness = DEFAULT_SKEWNESS;
    }

    /**
     * Constructor with scale.
     * Sets default skewness and provided scale.
     *
     * @param scale Scale to be set (both, horizontal and vertical). A value
     *              between 0.0 and 1.0 will reduce objects size, a value larger than 1.0
     *              will enlarge objects size, and negative values will reverse objects.
     */
    public AffineParameters2D(final double scale) {
        scaleX = scaleY = scale;
        skewness = DEFAULT_SKEWNESS;
    }

    /**
     * Constructor with scale and skewness.
     *
     * @param scale    Scale to be set (both, horizontal and vertical). A value
     *                 between 0.0 and 1.0 will reduce objects size, a value larger than 1.0
     *                 will enlarge objects size, and negative values will reverse objects.
     * @param skewness Skewness to be set.
     */
    public AffineParameters2D(final double scale, final double skewness) {
        scaleX = scaleY = scale;
        this.skewness = skewness;
    }

    /**
     * Constructor with horizontal scale, vertical scale and skewness.
     *
     * @param scaleX   Horizontal scale to be set. A value between 0.0 and 1.0
     *                 will reduce objects size, a value larger than 1.0 will enlarge objects
     *                 size, and negative values will reverse objects.
     * @param scaleY   Vertical scale to be set. A value between 0.0 and 1.0 will
     *                 reduce objects size, a value larger than 1.0 will enlarge objects
     *                 size, and negative values will reverse objects.
     * @param skewness Skewness to be set.
     */
    public AffineParameters2D(final double scaleX, final double scaleY, final double skewness) {
        this.scaleX = scaleX;
        this.scaleY = scaleY;
        this.skewness = skewness;
    }

    /**
     * Constructor with matrix and threshold.
     *
     * @param m         Upper triangular matrix to extract affine parameters from.
     * @param threshold Threshold to determine whether provided matrix is a
     *                  valid upper triangular one.
     * @throws IllegalArgumentException Raised if provided matrix is not 2x2 or
     *                                  if it is not upper triangular, or if threshold is negative.
     */
    public AffineParameters2D(final Matrix m, final double threshold) {
        fromMatrix(m, threshold);
    }

    /**
     * Constructor with matrix.
     *
     * @param m Upper triangular matrix to extract affine parameters from.
     * @throws IllegalArgumentException Raised if provided matrix is not 2x2 or
     *                                  if it is not upper triangular.
     */
    public AffineParameters2D(final Matrix m) {
        fromMatrix(m);
    }

    /**
     * Returns horizontal scale.
     * A value between 0.0 and 1.0 will reduce objects size, a value larger than
     * 1.0 will enlarge objects size, and negative values will reverse objects.
     *
     * @return Horizontal scale.
     */
    public double getScaleX() {
        return scaleX;
    }

    /**
     * Sets horizontal scale.
     * A value between 0.0 and 1.0 will reduce objects size, a value larger than
     * 1.0 will enlarge objects size, and negative values will reverse objects.
     *
     * @param scaleX Horizontal scale to be set.
     */
    public void setScaleX(final double scaleX) {
        this.scaleX = scaleX;
    }

    /**
     * Returns vertical scale.
     * A value between 0.0 and 1.0 will reduce objects size, a value larger than
     * 1.0 will enlarge objects size, and negative values will reverse objects.
     *
     * @return Vertical scale.
     */
    public double getScaleY() {
        return scaleY;
    }

    /**
     * Sets vertical scale.
     * A value between 0.0 and 1.0 will reduce objects size, a value larger than
     * 1.0 will enlarge objects size, and negative values will reverse objects.
     *
     * @param scaleY Vertical scale to be set.
     */
    public void setScaleY(final double scaleY) {
        this.scaleY = scaleY;
    }

    /**
     * Sets overall scale (both, horizontal and vertical).
     * A value between 0.0 and 1.0 will reduce objects size, a value larger than
     * 1.0 will enlarge objects size, and negative values will reverse objects.
     *
     * @param scale Horizontal and vertical scale to be set.
     */
    public void setScale(final double scale) {
        scaleX = scaleY = scale;
    }

    /**
     * Returns skewness value.
     * A value of 0.0 indicates that horizontal and vertical axis are
     * orthogonal, otherwise they are slanted to each other.
     *
     * @return Skewness value.
     */
    public double getSkewness() {
        return skewness;
    }

    /**
     * Sets skewness value.
     * A value of 0.0 indicates that horizontal and vertical axis are
     * orthogonal, otherwise they are slanted to each other.
     *
     * @param skewness Skewness to be set.
     */
    public void setSkewness(final double skewness) {
        this.skewness = skewness;
    }

    /**
     * Converts this affine parameters instance into matrix representation.
     *
     * @return A matrix representation of this instance.
     */
    public Matrix asMatrix() {
        Matrix m = null;
        try {
            m = new Matrix(INHOM_COORDS, INHOM_COORDS);
            asMatrix(m);
        } catch (final WrongSizeException ignore) {
            // never happens
        }
        return m;
    }

    /**
     * Converts this affine parameters instance into matrix representation and
     * stores the result into provided matrix.
     *
     * @param m Matrix where representation of this instance will be stored.
     * @throws IllegalArgumentException Raised if provided matrix is not 2x2.
     */
    public void asMatrix(final Matrix m) {
        if (m.getRows() != INHOM_COORDS || m.getColumns() != INHOM_COORDS) {
            throw new IllegalArgumentException();
        }

        m.setElementAt(0, 0, scaleX);
        m.setElementAt(1, 0, 0.0);
        m.setElementAt(0, 1, skewness);
        m.setElementAt(1, 1, scaleY);
    }

    /**
     * Sets parameters of this instance from provided matrix.
     *
     * @param m Matrix to set parameters from. Provided matrix must be 2x2 and
     *          upper triangular.
     * @throws IllegalArgumentException Raised if provided matrix is not 2x2 or
     *                                  if it is not upper triangular.
     */
    public final void fromMatrix(final Matrix m) {
        fromMatrix(m, DEFAULT_VALID_THRESHOLD);
    }

    /**
     * Sets parameters of this instance from provided matrix.
     *
     * @param m         Matrix to set parameters from. Provided matrix must be 2x2 and
     *                  upper triangular up to provided threshold.
     * @param threshold Threshold to determine whether provided matrix is upper
     *                  triangular. Matrix will be considered upper triangular if its lower
     *                  triangular elements are smaller or equal than provided threshold.
     * @throws IllegalArgumentException Raised if provided matrix is not 2x2 or
     *                                  if it is not upper triangular or if threshold is negative.
     */
    public final void fromMatrix(final Matrix m, final double threshold) {
        if (!isValidMatrix(m, threshold)) {
            throw new IllegalArgumentException();
        }

        scaleX = m.getElementAt(0, 0);
        skewness = m.getElementAt(0, 1);
        scaleY = m.getElementAt(1, 1);
    }

    /**
     * Returns boolean indicating whether provided matrix is a valid matrix
     * to set affine parameters from.
     * Valid matrices need to be 2x2 and upper triangular.
     *
     * @param m A matrix to determine whether it is valid to set affine
     *          parameters from.
     * @return True if matrix is valid, false otherwise.
     */
    public static boolean isValidMatrix(final Matrix m) {
        return isValidMatrix(m, DEFAULT_VALID_THRESHOLD);
    }

    /**
     * Returns boolean indicating whether provided matrix is a valid matrix to
     * set affine parameters form.
     * Valid matrices need to be 2x2 and upper triangular up to provided
     * threshold. In Layman terms, a valid matrix lower triangular elements need
     * to be smaller or equal than provided threshold.
     *
     * @param m         A matrix to determine whether it is valid to set affine
     *                  parameters from.
     * @param threshold A threshold to determine whether provided matrix is
     *                  upper triangular. Matrix will be considered upper triangular if its lower
     *                  triangular elements are smaller or equal than provided threshold (without
     *                  taking into account the sign of the elements).
     * @return True if matrix is valid, false otherwise.
     * @throws IllegalArgumentException Raised if provided threshold is negative.
     */
    @SuppressWarnings("DuplicatedCode")
    public static boolean isValidMatrix(final Matrix m, final double threshold) {
        if (threshold < 0.0) {
            throw new IllegalArgumentException();
        }

        if (m.getRows() != INHOM_COORDS || m.getColumns() != INHOM_COORDS) {
            return false;
        }

        // check is upper triangular
        final var rows = m.getRows();
        final var cols = m.getColumns();

        for (var v = 0; v < cols; v++) {
            for (var u = 0; u < rows; u++) {
                if (u > v && Math.abs(m.getElementAt(u, v)) > threshold) {
                    return false;
                }
            }
        }

        return true;
    }
}