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.");
+ }
}