You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by le...@apache.org on 2020/06/13 08:27:12 UTC

svn commit: r1878792 - in /pdfbox/branches/2.0/pdfbox: ./ src/main/java/org/apache/pdfbox/util/Matrix.java src/test/java/org/apache/pdfbox/util/MatrixTest.java

Author: lehmi
Date: Sat Jun 13 08:27:12 2020
New Revision: 1878792

URL: http://svn.apache.org/viewvc?rev=1878792&view=rev
Log:
PDFBOX-4877: improve Matrix implementation based on a proposal by Alfred Faltiska

Modified:
    pdfbox/branches/2.0/pdfbox/   (props changed)
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/Matrix.java
    pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/util/MatrixTest.java

Propchange: pdfbox/branches/2.0/pdfbox/
------------------------------------------------------------------------------
  Merged /pdfbox/trunk/pdfbox:r1878751

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/Matrix.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/Matrix.java?rev=1878792&r1=1878791&r2=1878792&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/Matrix.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/Matrix.java Sat Jun 13 08:27:12 2020
@@ -32,32 +32,42 @@ import org.apache.pdfbox.cos.COSBase;
  */
 public final class Matrix implements Cloneable
 {
-    static final float[] DEFAULT_SINGLE =
-    {
-        1,0,0,  //  a  b  0     sx hy 0    note: hx and hy are reversed vs. the PDF spec as we use
-        0,1,0,  //  c  d  0  =  hx sy 0          AffineTransform's definition x and y shear
-        0,0,1   //  tx ty 1     tx ty 1
-    };
-
-    private final float[] single;
+    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()
     {
-        single = new float[DEFAULT_SINGLE.length];
-        System.arraycopy(DEFAULT_SINGLE, 0, single, 0, DEFAULT_SINGLE.length);
+        // 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
+     * @param array source array, elements must be or extend COSNumber
      */
     public Matrix(COSArray array)
     {
-        single = new float[DEFAULT_SINGLE.length];
+        single = new float[SIZE];
         single[0] = ((COSNumber)array.getObject(0)).floatValue();
         single[1] = ((COSNumber)array.getObject(1)).floatValue();
         single[3] = ((COSNumber)array.getObject(2)).floatValue();
@@ -73,6 +83,11 @@ public final class Matrix implements Clo
      * 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
+     *
      * @see Matrix#getRotateInstance(double, float, float)
      * @see Matrix#getScaleInstance(float, float)
      * @see Matrix#getTranslateInstance(float, float)
@@ -86,7 +101,7 @@ public final class Matrix implements Clo
      */
     public Matrix(float a, float b, float c, float d, float e, float f)
     {
-        single = new float[DEFAULT_SINGLE.length];
+        single = new float[SIZE];
         single[0] = a;
         single[1] = b;
         single[3] = c;
@@ -98,18 +113,23 @@ public final class Matrix implements Clo
 
     /**
      * Creates a matrix with the same elements as the given AffineTransform.
-     * @param at
+     * @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[DEFAULT_SINGLE.length];
-        System.arraycopy(DEFAULT_SINGLE, 0, single, 0, DEFAULT_SINGLE.length);
+        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;
     }
 
     /**
@@ -151,7 +171,10 @@ public final class Matrix implements Clo
     @Deprecated
     public void reset()
     {
-        System.arraycopy(DEFAULT_SINGLE, 0, single, 0, DEFAULT_SINGLE.length);
+        Arrays.fill(single, 0);
+        single[0] = 1;
+        single[4] = 1;
+        single[8] = 1;
     }
 
     /**
@@ -268,8 +291,7 @@ public final class Matrix implements Clo
      */
     public void translate(Vector vector)
     {
-        Matrix m = Matrix.getTranslateInstance(vector.getX(), vector.getY());
-        concatenate(m);
+        concatenate(Matrix.getTranslateInstance(vector.getX(), vector.getY()));
     }
 
     /**
@@ -280,8 +302,7 @@ public final class Matrix implements Clo
      */
     public void translate(float tx, float ty)
     {
-        Matrix m = Matrix.getTranslateInstance(tx, ty);
-        concatenate(m);
+        concatenate(Matrix.getTranslateInstance(tx, ty));
     }
 
     /**
@@ -292,8 +313,7 @@ public final class Matrix implements Clo
      */
     public void scale(float sx, float sy)
     {
-        Matrix m = Matrix.getScaleInstance(sx, sy);
-        concatenate(m);
+        concatenate(Matrix.getScaleInstance(sx, sy));
     }
 
     /**
@@ -303,113 +323,81 @@ public final class Matrix implements Clo
      */
     public void rotate(double theta)
     {
-        Matrix m = Matrix.getRotateInstance(theta, 0, 0);
-        concatenate(m);
+        concatenate(Matrix.getRotateInstance(theta, 0, 0));
     }
 
     /**
-     * This will take the current matrix and multiply it with a matrix that is passed in.
-     *
-     * @param b The matrix to multiply by.
+     * This method multiplies this Matrix with the specified other Matrix, storing the product in a new instance. It is
+     * allowed to have (other == this).
      *
-     * @return The result of the two multiplied matrices.
+     * @param other the second operand Matrix in the multiplication; required
+     * @return the product of the two matrices.
      */
-    public Matrix multiply( Matrix b )
+    public Matrix multiply(Matrix other)
     {
-        return this.multiply(b, new Matrix());
+        return multiply(other, new Matrix());
     }
 
     /**
-     * This method multiplies this Matrix with the specified other Matrix, storing the product in the specified
-     * result Matrix. By reusing Matrix instances like this, multiplication chains can be executed without having
-     * to create many temporary Matrix objects.
-     * <p>
-     * It is allowed to have (other == this) or (result == this) or indeed (other == result) but if this is done,
-     * the backing float[] matrix values may be copied in order to ensure a correct product.
+     * 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
-     * @param result the Matrix instance into which the result should be stored. If result is null, a new Matrix
-     *               instance is created.
-     * @return the product of the two matrices.
+     * @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)
         {
-            result = new Matrix();
+            return new Matrix(c);
         }
-
-        if (other != null && other.single != null)
+        else
         {
-            // the operands
-            float[] thisOperand = this.single;
-            float[] otherOperand = other.single;
-
-            // We're multiplying 2 sets of floats together to produce a third, but we allow
-            // any of these float[] instances to be the same objects.
-            // There is the possibility then to overwrite one of the operands with result values
-            // and therefore corrupt the result.
-
-            // If either of these operands are the same float[] instance as the result, then
-            // they need to be copied.
-
-            if (this == result)
-            {
-                final float[] thisOrigVals = new float[this.single.length];
-                System.arraycopy(this.single, 0, thisOrigVals, 0, this.single.length);
-
-                thisOperand = thisOrigVals;
-            }
-            if (other == result)
-            {
-                final float[] otherOrigVals = new float[other.single.length];
-                System.arraycopy(other.single, 0, otherOrigVals, 0, other.single.length);
-
-                otherOperand = otherOrigVals;
-            }
-
-            result.single[0] = thisOperand[0] * otherOperand[0]
-                             + thisOperand[1] * otherOperand[3]
-                             + thisOperand[2] * otherOperand[6];
-            result.single[1] = thisOperand[0] * otherOperand[1]
-                             + thisOperand[1] * otherOperand[4]
-                             + thisOperand[2] * otherOperand[7];
-            result.single[2] = thisOperand[0] * otherOperand[2]
-                             + thisOperand[1] * otherOperand[5]
-                             + thisOperand[2] * otherOperand[8];
-            result.single[3] = thisOperand[3] * otherOperand[0]
-                             + thisOperand[4] * otherOperand[3]
-                             + thisOperand[5] * otherOperand[6];
-            result.single[4] = thisOperand[3] * otherOperand[1]
-                             + thisOperand[4] * otherOperand[4]
-                             + thisOperand[5] * otherOperand[7];
-            result.single[5] = thisOperand[3] * otherOperand[2]
-                             + thisOperand[4] * otherOperand[5]
-                             + thisOperand[5] * otherOperand[8];
-            result.single[6] = thisOperand[6] * otherOperand[0]
-                             + thisOperand[7] * otherOperand[3]
-                             + thisOperand[8] * otherOperand[6];
-            result.single[7] = thisOperand[6] * otherOperand[1]
-                             + thisOperand[7] * otherOperand[4]
-                             + thisOperand[8] * otherOperand[7];
-            result.single[8] = thisOperand[6] * otherOperand[2]
-                             + thisOperand[7] * otherOperand[5]
-                             + thisOperand[8] * otherOperand[8];
+            result.single = c;
+            return result;
         }
-        if (Float.isInfinite(result.single[0]) || Float.isNaN(result.single[0]) //
-                || Float.isInfinite(result.single[1]) || Float.isNaN(result.single[1]) //
-                || Float.isInfinite(result.single[2]) || Float.isNaN(result.single[2]) //
-                || Float.isInfinite(result.single[3]) || Float.isNaN(result.single[3]) //
-                || Float.isInfinite(result.single[4]) || Float.isNaN(result.single[4]) //
-                || Float.isInfinite(result.single[5]) || Float.isNaN(result.single[5]) //
-                || Float.isInfinite(result.single[6]) || Float.isNaN(result.single[6]) //
-                || Float.isInfinite(result.single[7]) || Float.isNaN(result.single[7]) //
-                || Float.isInfinite(result.single[8]) || Float.isNaN(result.single[8]))
-            throw new IllegalArgumentException(
-                    "Multiplying two matrices produces illegal values");
-        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.
      *
@@ -481,16 +469,18 @@ public final class Matrix implements Clo
     /**
      * Convenience method to create a scaled instance.
      *
-     * @param sx The xscale operator.
-     * @param sy The yscale operator.
+     * 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 sx, float sy)
+    public static Matrix getScaleInstance(float x, float y)
     {
-        Matrix matrix = new Matrix();
-        matrix.single[0] = sx;
-        matrix.single[4] = sy;
-        return matrix;
+        return new Matrix(x, 0, 0, y, 0, 0);
     }
 
     /**
@@ -511,30 +501,34 @@ public final class Matrix implements Clo
     /**
      * Convenience method to create a translating instance.
      *
-     * @param tx The x translating operator.
-     * @param ty The y translating operator.
+     * 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 tx, float ty)
+    public static Matrix getTranslatingInstance(float x, float y)
     {
-        return getTranslateInstance(tx, ty);
+        return new Matrix(1, 0, 0, 1, x, y);
     }
 
     /**
      * Convenience method to create a translating instance.
      *
-     * @param tx The x translating operator.
-     * @param ty The y translating operator.
+     * 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 tx, float ty)
+    public static Matrix getTranslateInstance(float x, float y)
     {
-        Matrix matrix = new Matrix();
-        matrix.single[6] = tx;
-        matrix.single[7] = ty;
-        return matrix;
+        return new Matrix(1, 0, 0, 1, x, y);
     }
 
     /**
@@ -550,14 +544,7 @@ public final class Matrix implements Clo
         float cosTheta = (float)Math.cos(theta);
         float sinTheta = (float)Math.sin(theta);
 
-        Matrix matrix = new Matrix();
-        matrix.single[0] = cosTheta;
-        matrix.single[1] = sinTheta;
-        matrix.single[3] = -sinTheta;
-        matrix.single[4] = cosTheta;
-        matrix.single[6] = tx;
-        matrix.single[7] = ty;
-        return matrix;
+        return new Matrix(cosTheta, sinTheta, -sinTheta, cosTheta, tx, ty);
     }
 
     /**
@@ -568,9 +555,7 @@ public final class Matrix implements Clo
      */
     public static Matrix concatenate(Matrix a, Matrix b)
     {
-        Matrix copy = a.clone();
-        copy.concatenate(b);
-        return copy;
+        return b.multiply(a);
     }
 
     /**
@@ -580,9 +565,7 @@ public final class Matrix implements Clo
     @Override
     public Matrix clone()
     {
-        Matrix clone = new Matrix();
-        System.arraycopy( single, 0, clone.single, 0, 9 );
-        return clone;
+        return new Matrix(single.clone());
     }
 
     /**

Modified: pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/util/MatrixTest.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/util/MatrixTest.java?rev=1878792&r1=1878791&r2=1878792&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/util/MatrixTest.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/util/MatrixTest.java Sat Jun 13 08:27:12 2020
@@ -40,7 +40,68 @@ public class MatrixTest
     }
 
     @Test
-    public void testMultiplication() throws Exception
+    public void testMultiplication()
+    {
+        // These matrices will not change - we use it to drive the various multiplications.
+        final Matrix const1 = new Matrix();
+        final Matrix const2 = new Matrix();
+
+        // Create matrix with values
+        // [ 0, 1, 2
+        // 1, 2, 3
+        // 2, 3, 4]
+        for (int x = 0; x < 3; x++)
+        {
+            for (int y = 0; y < 3; y++)
+            {
+                const1.setValue(x, y, x + y);
+                const2.setValue(x, y, 8 + x + y);
+            }
+        }
+
+        float[] m1MultipliedByM1 = new float[] { 5,  8,  11,  8, 14, 20, 11, 20,  29 };
+        float[] m1MultipliedByM2 = new float[] { 29, 32, 35, 56, 62, 68, 83, 92, 101 };
+        float[] m2MultipliedByM1 = new float[] { 29, 56, 83, 32, 62, 92, 35, 68, 101 };
+
+        Matrix var1 = const1.clone();
+        Matrix var2 = const2.clone();
+
+        // Multiply two matrices together producing a new result matrix.
+        Matrix result = var1.multiply(var2);
+        assertEquals(const1, var1);
+        assertEquals(const2, var2);
+        assertMatrixValuesEqualTo(m1MultipliedByM2, result);
+
+        // Multiply two matrices together with the result being written to a third matrix
+        // (Any existing values there will be overwritten).
+        result = var1.multiply(var2);
+        assertEquals(const1, var1);
+        assertEquals(const2, var2);
+        assertMatrixValuesEqualTo(m1MultipliedByM2, result);
+
+        // Multiply two matrices together with the result being written into 'this' matrix
+        var1 = const1.clone();
+        var2 = const2.clone();
+        var1.concatenate(var2);
+        assertEquals(const2, var2);
+        assertMatrixValuesEqualTo(m2MultipliedByM1, var1);
+
+        var1 = const1.clone();
+        var2 = const2.clone();
+        result = Matrix.concatenate(var1, var2);
+        assertEquals(const1, var1);
+        assertEquals(const2, var2);
+        assertMatrixValuesEqualTo(m2MultipliedByM1, result);
+
+        // Multiply the same matrix with itself with the result being written into 'this' matrix
+        var1 = const1.clone();
+        result = var1.multiply(var1);
+        assertEquals(const1, var1);
+        assertMatrixValuesEqualTo(m1MultipliedByM1, result);
+    }
+
+    @Test
+    public void testOldMultiplication() throws Exception
     {
         // This matrix will not change - we use it to drive the various multiplications.
         final Matrix testMatrix = new Matrix();
@@ -190,4 +251,19 @@ public class MatrixTest
         }
     }
     
+    //Uncomment annotation to run the test
+    // @Test
+    public void testMultiplicationPerformance() {
+        long start = System.currentTimeMillis();
+        Matrix c;
+        Matrix d;
+        for (int i=0; i<100000000; i++) {
+            c = new Matrix(15, 3, 235, 55, 422, 1);
+            d = new Matrix(45, 345, 23, 551, 66, 832);
+            c.multiply(d);
+            c.concatenate(d);
+        }
+        long stop = System.currentTimeMillis();
+        System.out.println("Matrix multiplication took " + (stop - start) + "ms.");
+    }
 }