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

org.sejda.sambox.util.Matrix Maven / Gradle / Ivy

Go to download

An Apache PDFBox fork intended to be used as PDF processor for Sejda and PDFsam related projects

There is a newer version: 3.0.21
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.sejda.sambox.util;

import org.sejda.sambox.cos.COSArray;
import org.sejda.sambox.cos.COSBase;
import org.sejda.sambox.cos.COSFloat;
import org.sejda.sambox.cos.COSNumber;

import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.Arrays;

/**
 * This class will be used for matrix manipulation.
 *
 * @author Ben Litchfield
 */
public final class Matrix implements Cloneable
{
    public static final int SIZE = 9;
    private float[] single;
    private static final float MAX_FLOAT_VALUE = 3.4028235E38f;

    /**
     * Constructor. This produces an identity matrix.
     */
    public Matrix()
    {
        // a b 0
        // c d 0
        // tx ty 1
        // note: hx and hy are reversed vs.the PDF spec as we use AffineTransform's definition x and y shear
        // sx hy 0
        // hx sy 0
        // tx ty 1
        single = new float[] { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
    }

    /**
     * Constructor. This produces a matrix with the given array as data. The source array is not
     * copied or cloned.
     */
    private Matrix(float[] src)
    {
        single = src;
    }

    /**
     * Creates a matrix from a 6-element (a b c d e f) COS array.
     *
     * @param array source array, elements must be or extend COSNumber
     */
    public Matrix(COSArray array)
    {
        single = new float[SIZE];
        single[0] = (array.getObject(0, COSNumber.class)).floatValue();
        single[1] = (array.getObject(1, COSNumber.class)).floatValue();
        single[3] = (array.getObject(2, COSNumber.class)).floatValue();
        single[4] = (array.getObject(3, COSNumber.class)).floatValue();
        single[6] = (array.getObject(4, COSNumber.class)).floatValue();
        single[7] = (array.getObject(5, COSNumber.class)).floatValue();
        single[8] = 1;
    }

    /**
     * Creates a transformation matrix with the given 6 elements. Transformation matrices are
     * discussed in 8.3.3, "Common Transformations" and 8.3.4, "Transformation Matrices" of the PDF
     * specification. For simple purposes (rotate, scale, translate) it is recommended to use the
     * static methods below.
     * 

* Produces the following matrix: a b 0 c d 0 e f 1 * * @param a the X coordinate scaling element (m00) of the 3x3 matrix * @param b the Y coordinate shearing element (m10) of the 3x3 matrix * @param c the X coordinate shearing element (m01) of the 3x3 matrix * @param d the Y coordinate scaling element (m11) of the 3x3 matrix * @param e the X coordinate translation element (m02) of the 3x3 matrix * @param f the Y coordinate translation element (m12) of the 3x3 matrix * @see Matrix#getRotateInstance(double, float, float) * @see Matrix#getScaleInstance(float, float) * @see Matrix#getTranslateInstance(float, float) */ public Matrix(float a, float b, float c, float d, float e, float f) { single = new float[SIZE]; single[0] = a; single[1] = b; single[3] = c; single[4] = d; single[6] = e; single[7] = f; single[8] = 1; } /** * Creates a matrix with the same elements as the given AffineTransform. * @param at matrix elements will be initialize with the values from this affine transformation, as follows: * * scaleX shearY 0 * shearX scaleY 0 * transX transY 1 * */ public Matrix(AffineTransform at) { single = new float[SIZE]; single[0] = (float) at.getScaleX(); single[1] = (float) at.getShearY(); single[3] = (float) at.getShearX(); single[4] = (float) at.getScaleY(); single[6] = (float) at.getTranslateX(); single[7] = (float) at.getTranslateY(); single[8] = 1; } /** * Convenience method to be used when creating a matrix from unverified data. If the parameter * is a COSArray with at least six numbers, a Matrix object is created from the first six * numbers and returned. If not, then the identity Matrix is returned. * * @param base a COS object, preferably a COSArray with six numbers. * * @return a Matrix object. */ public static Matrix createMatrix(COSBase base) { if (!(base instanceof COSArray array)) { return new Matrix(); } if (array.size() < 6) { return new Matrix(); } for (int i = 0; i < 6; ++i) { if (!(array.getObject(i) instanceof COSNumber)) { return new Matrix(); } } return new Matrix(array); } /** * This method resets the numbers in this Matrix to the original values, which are * the values that a newly constructed Matrix would have. * * @deprecated This method will be removed. */ @Deprecated public void reset() { Arrays.fill(single, 0); single[0] = 1; single[4] = 1; single[8] = 1; } /** * Create an affine transform from this matrix's values. * * @return An affine transform with this matrix's values. */ public AffineTransform createAffineTransform() { return new AffineTransform(single[0], single[1], // m00 m10 = scaleX shearY single[3], single[4], // m01 m11 = shearX scaleY single[6], single[7]); // m02 m12 = tx ty } /** * Set the values of the matrix from the AffineTransform. * * @param af The transform to get the values from. * @deprecated Use the {@link #Matrix(AffineTransform)} constructor instead. */ @Deprecated public void setFromAffineTransform(AffineTransform af) { single[0] = (float) af.getScaleX(); single[1] = (float) af.getShearY(); single[3] = (float) af.getShearX(); single[4] = (float) af.getScaleY(); single[6] = (float) af.getTranslateX(); single[7] = (float) af.getTranslateY(); } /** * This will get a matrix value at some point. * * @param row The row to get the value from. * @param column The column to get the value from. * * @return The value at the row/column position. */ public float getValue(int row, int column) { return single[row * 3 + column]; } /** * This will set a value at a position. * * @param row The row to set the value at. * @param column the column to set the value at. * @param value The value to set at the position. */ public void setValue(int row, int column, float value) { single[row * 3 + column] = value; } /** * Return a single dimension array of all values in the matrix. * * @return The values of this matrix. */ public float[][] getValues() { float[][] retval = new float[3][3]; retval[0][0] = single[0]; retval[0][1] = single[1]; retval[0][2] = single[2]; retval[1][0] = single[3]; retval[1][1] = single[4]; retval[1][2] = single[5]; retval[2][0] = single[6]; retval[2][1] = single[7]; retval[2][2] = single[8]; return retval; } /** * Return a single dimension array of all values in the matrix. * * @return The values ot this matrix. * @deprecated Use {@link #getValues()} instead. */ @Deprecated public double[][] getValuesAsDouble() { double[][] retval = new double[3][3]; retval[0][0] = single[0]; retval[0][1] = single[1]; retval[0][2] = single[2]; retval[1][0] = single[3]; retval[1][1] = single[4]; retval[1][2] = single[5]; retval[2][0] = single[6]; retval[2][1] = single[7]; retval[2][2] = single[8]; return retval; } /** * Concatenates (premultiplies) the given matrix to this matrix. * * @param matrix The matrix to concatenate. */ public void concatenate(Matrix matrix) { matrix.multiply(this, this); } /** * Translates this matrix by the given vector. * * @param vector 2D vector */ public void translate(Vector vector) { concatenate(Matrix.getTranslateInstance(vector.getX(), vector.getY())); } /** * Translates this matrix by the given amount. * * @param tx x-translation * @param ty y-translation */ public void translate(float tx, float ty) { concatenate(Matrix.getTranslateInstance(tx, ty)); } /** * Scales this matrix by the given factors. * * @param sx x-scale * @param sy y-scale */ public void scale(float sx, float sy) { concatenate(Matrix.getScaleInstance(sx, sy)); } /** * Rotares this matrix by the given factors. * * @param theta The angle of rotation measured in radians */ public void rotate(double theta) { concatenate(Matrix.getRotateInstance(theta, 0, 0)); } /** * This method multiplies this Matrix with the specified other Matrix, storing the product in a new instance. It is * allowed to have (other == this). * * @param other the second operand Matrix in the multiplication; required * @return the product of the two matrices. */ public Matrix multiply(Matrix other) { return multiply(other, new Matrix()); } /** * This method multiplies this Matrix with the specified other Matrix, storing the product in the specified result * Matrix. It is allowed to have (other == this) or (result == this) or indeed (other == result). * * See {@link #multiply(Matrix)} if you need a version with a single operator. * * @param other the second operand Matrix in the multiplication; required * @param result the Matrix instance into which the result should be stored. If result is null, a new Matrix instance is * created. * @return the result. * */ @Deprecated public Matrix multiply(Matrix other, Matrix result) { float[] c = result != null && result != other && result != this ? result.single : new float[SIZE]; multiplyArrays(single, other.single, c); if (!Matrix.isFinite(c[0]) // || !Matrix.isFinite(c[1]) // || !Matrix.isFinite(c[2]) // || !Matrix.isFinite(c[3]) // || !Matrix.isFinite(c[4]) // || !Matrix.isFinite(c[5]) // || !Matrix.isFinite(c[6]) // || !Matrix.isFinite(c[7]) // || !Matrix.isFinite(c[8])) throw new IllegalArgumentException("Multiplying two matrices produces illegal values"); if (result == null) { return new Matrix(c); } result.single = c; return result; } private static boolean isFinite(float f) { // this is faster than the combination of "isNaN" and "isInfinite" and Float.isFinite isn't available in java 6 return Math.abs(f) <= MAX_FLOAT_VALUE; } private void multiplyArrays(float[] a, float[] b, float[] c) { c[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6]; c[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7]; c[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8]; c[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6]; c[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7]; c[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8]; c[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6]; c[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7]; c[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8]; } /** * Transforms the given point by this matrix. * * @param point point to transform */ public void transform(Point2D point) { float x = (float) point.getX(); float y = (float) point.getY(); float a = single[0]; float b = single[1]; float c = single[3]; float d = single[4]; float e = single[6]; float f = single[7]; point.setLocation(x * a + y * c + e, x * b + y * d + f); } /** * Transforms the given point by this matrix. * * @param x x-coordinate * @param y y-coordinate */ public Point2D.Float transformPoint(float x, float y) { float a = single[0]; float b = single[1]; float c = single[3]; float d = single[4]; float e = single[6]; float f = single[7]; return new Point2D.Float(x * a + y * c + e, x * b + y * d + f); } /** * Transforms the given point by this matrix. * * @param vector 2D vector */ public Vector transform(Vector vector) { float a = single[0]; float b = single[1]; float c = single[3]; float d = single[4]; float e = single[6]; float f = single[7]; float x = vector.getX(); float y = vector.getY(); return new Vector(x * a + y * c + e, x * b + y * d + f); } /** * Create a new matrix with just the scaling operators. * * @return A new matrix with just the scaling operators. * @deprecated This method is due to be removed, please contact us if you make use of it. */ @Deprecated public Matrix extractScaling() { Matrix matrix = new Matrix(); matrix.single[0] = this.single[0]; matrix.single[4] = this.single[4]; return matrix; } /** * Convenience method to create a scaled instance. * * Produces the following matrix: * x 0 0 * 0 y 0 * 0 0 1 * * @param x The xscale operator. * @param y The yscale operator. * @return A new matrix with just the x/y scaling */ public static Matrix getScaleInstance(float x, float y) { return new Matrix(x, 0, 0, y, 0, 0); } /** * Create a new matrix with just the translating operators. * * @return A new matrix with just the translating operators. * @deprecated This method is due to be removed, please contact us if you make use of it. */ @Deprecated public Matrix extractTranslating() { Matrix matrix = new Matrix(); matrix.single[6] = this.single[6]; matrix.single[7] = this.single[7]; return matrix; } /** * Convenience method to create a translating instance. * * Produces the following matrix: * 1 0 0 * 0 1 0 * x y 1 * * @param x The x translating operator. * @param y The y translating operator. * @return A new matrix with just the x/y translating. * @deprecated Use {@link #getTranslateInstance} instead. */ @Deprecated public static Matrix getTranslatingInstance(float x, float y) { return new Matrix(1, 0, 0, 1, x, y); } /** * Convenience method to create a translating instance. * * Produces the following matrix: 1 0 0 0 1 0 x y 1 * * @param x The x translating operator. * @param y The y translating operator. * @return A new matrix with just the x/y translating. */ public static Matrix getTranslateInstance(float x, float y) { return new Matrix(1, 0, 0, 1, x, y); } /** * Convenience method to create a rotated instance. * * @param theta The angle of rotation measured in radians * @param tx The x translation. * @param ty The y translation. * @return A new matrix with the rotation and the x/y translating. */ public static Matrix getRotateInstance(double theta, float tx, float ty) { float cosTheta = (float) Math.cos(theta); float sinTheta = (float) Math.sin(theta); return new Matrix(cosTheta, sinTheta, -sinTheta, cosTheta, tx, ty); } /** * Produces a copy of the first matrix, with the second matrix concatenated. * * @param a The matrix to copy. * @param b The matrix to concatenate. */ public static Matrix concatenate(Matrix a, Matrix b) { return b.multiply(a); } /** * Clones this object. * @return cloned matrix as an object. */ @Override public Matrix clone() { return new Matrix(single.clone()); } /** * Returns the x-scaling factor of this matrix. This is calculated from the scale and shear. * * @return The x-scaling factor. */ public float getScalingFactorX() { /** * BM: if the trm is rotated, the calculation is a little more complicated * * The rotation matrix multiplied with the scaling matrix is: * ( x 0 0) ( cos sin 0) ( x*cos x*sin 0) * ( 0 y 0) * (-sin cos 0) = (-y*sin y*cos 0) * ( 0 0 1) ( 0 0 1) ( 0 0 1) * * So, if you want to deduce x from the matrix you take * M(0,0) = x*cos and M(0,1) = x*sin and use the theorem of Pythagoras * * sqrt(M(0,0)^2+M(0,1)^2) = * sqrt(x2*cos2+x2*sin2) = * sqrt(x2*(cos2+sin2)) = <- here is the trick cos2+sin2 is one * sqrt(x2) = * abs(x) */ if (single[1] != 0.0f) { return (float) Math.sqrt(Math.pow(single[0], 2) + Math.pow(single[1], 2)); } return single[0]; } /** * Returns the y-scaling factor of this matrix. This is calculated from the scale and shear. * * @return The y-scaling factor. */ public float getScalingFactorY() { if (single[3] != 0.0f) { return (float) Math.sqrt(Math.pow(single[3], 2) + Math.pow(single[4], 2)); } return single[4]; } /** * Returns the x-scaling element of this matrix. * * @see #getScalingFactorX() */ public float getScaleX() { return single[0]; } /** * Returns the y-shear element of this matrix. */ public float getShearY() { return single[1]; } /** * Returns the x-shear element of this matrix. */ public float getShearX() { return single[3]; } /** * Returns the y-scaling element of this matrix. * * @see #getScalingFactorY() */ public float getScaleY() { return single[4]; } /** * Returns the x-translation element of this matrix. */ public float getTranslateX() { return single[6]; } /** * Returns the y-translation element of this matrix. */ public float getTranslateY() { return single[7]; } /** * Get the x position in the matrix. This method is deprecated as it is incorrectly named. * * @return The x-position. * @deprecated Use {@link #getTranslateX} instead */ @Deprecated public float getXPosition() { return single[6]; } /** * Get the y position. This method is deprecated as it is incorrectly named. * * @return The y position. * @deprecated Use {@link #getTranslateY} instead */ @Deprecated public float getYPosition() { return single[7]; } /** * Returns a COS array which represent the geometric relevant * components of the matrix. The last column of the matrix is ignored, * only the first two columns are returned. This is analog to the * Matrix(COSArray) constructor. */ public COSArray toCOSArray() { COSArray array = new COSArray(); array.add(new COSFloat(single[0])); array.add(new COSFloat(single[1])); array.add(new COSFloat(single[3])); array.add(new COSFloat(single[4])); array.add(new COSFloat(single[6])); array.add(new COSFloat(single[7])); return array; } @Override public String toString() { return "[" + single[0] + "," + single[1] + "," + single[3] + "," + single[4] + "," + single[6] + "," + single[7] + "]"; } @Override public int hashCode() { return Arrays.hashCode(single); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } return Arrays.equals(this.single, ((Matrix) obj).single); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy