You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2016/06/23 16:29:33 UTC
svn commit: r1749936 - in /pdfbox/trunk/pdfbox/src:
main/java/org/apache/pdfbox/filter/
main/java/org/apache/pdfbox/pdmodel/graphics/image/
test/java/org/apache/pdfbox/pdmodel/graphics/image/
Author: tilman
Date: Thu Jun 23 16:29:33 2016
New Revision: 1749936
URL: http://svn.apache.org/viewvc?rev=1749936&view=rev
Log:
PDFBOX-3069: add CCITT G4 (T6) encoding from BufferedImage with encoder from twelvemonkeys; add check for orientation
Added:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxEncoderStream.java (with props)
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxFilter.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java
Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxEncoderStream.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxEncoderStream.java?rev=1749936&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxEncoderStream.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxEncoderStream.java Thu Jun 23 16:29:33 2016
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2013, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.apache.pdfbox.filter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * CCITT Modified Group 4 (T6) fax compression.
+ *
+ * @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
+ *
+ * Taken from commit 047884e3d9e1b30516c79b147ead763303dc9bcb of 21.4.2016 from
+ * twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStream.java
+ *
+ * Initial changes for PDFBox:
+ * - removed Validate
+ * - G4 compression only
+ * - removed options
+ */
+final class CCITTFaxEncoderStream extends OutputStream {
+
+ private int currentBufferLength = 0;
+ private final byte[] inputBuffer;
+ private final int inputBufferLength;
+ private final int columns;
+ private final int rows;
+
+ private int[] changesCurrentRow;
+ private int[] changesReferenceRow;
+ private int currentRow = 0;
+ private int changesCurrentRowLength = 0;
+ private int changesReferenceRowLength = 0;
+ private byte outputBuffer = 0;
+ private byte outputBufferBitLength = 0;
+ private final int fillOrder;
+ private final OutputStream stream;
+
+ CCITTFaxEncoderStream(final OutputStream stream, final int columns, final int rows, final int fillOrder) {
+
+ this.stream = stream;
+ this.columns = columns;
+ this.rows = rows;
+ this.fillOrder = fillOrder;
+
+ this.changesReferenceRow = new int[columns];
+ this.changesCurrentRow = new int[columns];
+
+ inputBufferLength = (columns + 7) / 8;
+ inputBuffer = new byte[inputBufferLength];
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ inputBuffer[currentBufferLength] = (byte) b;
+ currentBufferLength++;
+
+ if (currentBufferLength == inputBufferLength) {
+ encodeRow();
+ currentBufferLength = 0;
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ stream.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ stream.close();
+ }
+
+ private void encodeRow() throws IOException {
+ currentRow++;
+ int[] tmp = changesReferenceRow;
+ changesReferenceRow = changesCurrentRow;
+ changesCurrentRow = tmp;
+ changesReferenceRowLength = changesCurrentRowLength;
+ changesCurrentRowLength = 0;
+
+ int index = 0;
+ boolean white = true;
+ while (index < columns) {
+ int byteIndex = index / 8;
+ int bit = index % 8;
+ if ((((inputBuffer[byteIndex] >> (7 - bit)) & 1) == 1) == (white)) {
+ changesCurrentRow[changesCurrentRowLength] = index;
+ changesCurrentRowLength++;
+ white = !white;
+ }
+ index++;
+ }
+
+ encodeRowType6();
+
+ if (currentRow == rows) {
+ writeEOL();
+ writeEOL();
+ fill();
+ }
+ }
+
+
+ private void encodeRowType6() throws IOException {
+ encode2D();
+ }
+
+ private int[] getNextChanges(int pos, boolean white) {
+ int[] result = new int[] {columns, columns};
+ for (int i = 0; i < changesCurrentRowLength; i++) {
+ if (pos < changesCurrentRow[i] || (pos == 0 && white)) {
+ result[0] = changesCurrentRow[i];
+ if ((i + 1) < changesCurrentRowLength) {
+ result[1] = changesCurrentRow[i + 1];
+ }
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ private void writeRun(int runLength, boolean white) throws IOException {
+ int nonterm = runLength / 64;
+ Code[] codes = white ? WHITE_NONTERMINATING_CODES : BLACK_NONTERMINATING_CODES;
+ while (nonterm > 0) {
+ if (nonterm >= codes.length) {
+ write(codes[codes.length - 1].code, codes[codes.length - 1].length);
+ nonterm -= codes.length;
+ }
+ else {
+ write(codes[nonterm - 1].code, codes[nonterm - 1].length);
+ nonterm = 0;
+ }
+ }
+
+ Code c = white ? WHITE_TERMINATING_CODES[runLength % 64] : BLACK_TERMINATING_CODES[runLength % 64];
+ write(c.code, c.length);
+ }
+
+ private void encode2D() throws IOException {
+ boolean white = true;
+ int index = 0; // a0
+ while (index < columns) {
+ int[] nextChanges = getNextChanges(index, white); // a1, a2
+
+ int[] nextRefs = getNextRefChanges(index, white); // b1, b2
+
+ int difference = nextChanges[0] - nextRefs[0];
+ if (nextChanges[0] > nextRefs[1]) {
+ // PMODE
+ write(1, 4);
+ index = nextRefs[1];
+ }
+ else if (difference > 3 || difference < -3) {
+ // HMODE
+ write(1, 3);
+ writeRun(nextChanges[0] - index, white);
+ writeRun(nextChanges[1] - nextChanges[0], !white);
+ index = nextChanges[1];
+
+ }
+ else {
+ // VMODE
+ switch (difference) {
+ case 0:
+ write(1, 1);
+ break;
+ case 1:
+ write(3, 3);
+ break;
+ case 2:
+ write(3, 6);
+ break;
+ case 3:
+ write(3, 7);
+ break;
+ case -1:
+ write(2, 3);
+ break;
+ case -2:
+ write(2, 6);
+ break;
+ case -3:
+ write(2, 7);
+ break;
+ }
+ white = !white;
+ index = nextRefs[0] + difference;
+ }
+ }
+ }
+
+ private int[] getNextRefChanges(int a0, boolean white) {
+ int[] result = new int[] {columns, columns};
+ for (int i = (white ? 0 : 1); i < changesReferenceRowLength; i += 2) {
+ if (changesReferenceRow[i] > a0 || (a0 == 0 && i == 0)) {
+ result[0] = changesReferenceRow[i];
+ if ((i + 1) < changesReferenceRowLength) {
+ result[1] = changesReferenceRow[i + 1];
+ }
+ break;
+ }
+ }
+ return result;
+ }
+
+ private void write(int code, int codeLength) throws IOException {
+
+ for (int i = 0; i < codeLength; i++) {
+ boolean codeBit = ((code >> (codeLength - i - 1)) & 1) == 1;
+ if (fillOrder == TIFFExtension.FILL_LEFT_TO_RIGHT) {
+ outputBuffer |= (codeBit ? 1 << (7 - ((outputBufferBitLength) % 8)) : 0);
+ }
+ else {
+ outputBuffer |= (codeBit ? 1 << (((outputBufferBitLength) % 8)) : 0);
+ }
+ outputBufferBitLength++;
+
+ if (outputBufferBitLength == 8) {
+ stream.write(outputBuffer);
+ clearOutputBuffer();
+ }
+ }
+ }
+
+ private void writeEOL() throws IOException {
+ write(1, 12);
+ }
+
+ private void fill() throws IOException {
+ if (outputBufferBitLength != 0) {
+ stream.write(outputBuffer);
+ }
+ clearOutputBuffer();
+ }
+
+ private void clearOutputBuffer() {
+ outputBuffer = 0;
+ outputBufferBitLength = 0;
+ }
+
+ public static class Code {
+ private Code(int code, int length) {
+ this.code = code;
+ this.length = length;
+ }
+
+ final int code;
+ final int length;
+ }
+
+ public static final Code[] WHITE_TERMINATING_CODES;
+
+ public static final Code[] WHITE_NONTERMINATING_CODES;
+
+ public static final Code[] BLACK_TERMINATING_CODES;
+
+ public static final Code[] BLACK_NONTERMINATING_CODES;
+
+ static {
+ // Setup HUFFMAN Codes
+ WHITE_TERMINATING_CODES = new Code[64];
+ WHITE_NONTERMINATING_CODES = new Code[40];
+ for (int i = 0; i < CCITTFaxDecoderStream.WHITE_CODES.length; i++) {
+ int bitLength = i + 4;
+ for (int j = 0; j < CCITTFaxDecoderStream.WHITE_CODES[i].length; j++) {
+ int value = CCITTFaxDecoderStream.WHITE_RUN_LENGTHS[i][j];
+ int code = CCITTFaxDecoderStream.WHITE_CODES[i][j];
+
+ if (value < 64) {
+ WHITE_TERMINATING_CODES[value] = new Code(code, bitLength);
+ }
+ else {
+ WHITE_NONTERMINATING_CODES[(value / 64) - 1] = new Code(code, bitLength);
+ }
+ }
+ }
+
+ BLACK_TERMINATING_CODES = new Code[64];
+ BLACK_NONTERMINATING_CODES = new Code[40];
+ for (int i = 0; i < CCITTFaxDecoderStream.BLACK_CODES.length; i++) {
+ int bitLength = i + 2;
+ for (int j = 0; j < CCITTFaxDecoderStream.BLACK_CODES[i].length; j++) {
+ int value = CCITTFaxDecoderStream.BLACK_RUN_LENGTHS[i][j];
+ int code = CCITTFaxDecoderStream.BLACK_CODES[i][j];
+
+ if (value < 64) {
+ BLACK_TERMINATING_CODES[value] = new Code(code, bitLength);
+ }
+ else {
+ BLACK_NONTERMINATING_CODES[(value / 64) - 1] = new Code(code, bitLength);
+ }
+ }
+ }
+ }
+}
Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxEncoderStream.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxFilter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxFilter.java?rev=1749936&r1=1749935&r2=1749936&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxFilter.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxFilter.java Thu Jun 23 16:29:33 2016
@@ -21,10 +21,11 @@ import java.io.InputStream;
import java.io.OutputStream;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.io.IOUtils;
/**
* Decodes image data that has been encoded using either Group 3 or Group 4
- * CCITT facsimile (fax) encoding.
+ * CCITT facsimile (fax) encoding, and encodes image data to Group 4.
*
* @author Ben Litchfield
* @author Marcel Kammer
@@ -110,7 +111,7 @@ final class CCITTFaxFilter extends Filte
return new DecodeResult(parameters);
}
- public void readFromDecoderStream(CCITTFaxDecoderStream decoderStream, byte[] result)
+ void readFromDecoderStream(CCITTFaxDecoderStream decoderStream, byte[] result)
throws IOException
{
int pos = 0;
@@ -138,6 +139,11 @@ final class CCITTFaxFilter extends Filte
protected void encode(InputStream input, OutputStream encoded, COSDictionary parameters)
throws IOException
{
- throw new UnsupportedOperationException("CCITTFaxFilter encoding not implemented, use the CCITTFactory methods instead");
+ int cols = parameters.getInt(COSName.COLUMNS);
+ int rows = parameters.getInt(COSName.ROWS);
+ CCITTFaxEncoderStream ccittFaxEncoderStream =
+ new CCITTFaxEncoderStream(encoded, cols, rows, TIFFExtension.FILL_LEFT_TO_RIGHT);
+ IOUtils.copy(input, ccittFaxEncoderStream);
+ input.close();
}
}
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java?rev=1749936&r1=1749935&r2=1749936&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java Thu Jun 23 16:29:33 2016
@@ -16,16 +16,21 @@
*/
package org.apache.pdfbox.pdmodel.graphics.image;
+import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
+import javax.imageio.stream.MemoryCacheImageOutputStream;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.filter.Filter;
+import org.apache.pdfbox.filter.FilterFactory;
import org.apache.pdfbox.io.RandomAccess;
import org.apache.pdfbox.io.RandomAccessFile;
import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
/**
@@ -41,6 +46,69 @@ public final class CCITTFactory
}
/**
+ * Creates a new CCITT group 4 (T6) compressed image XObject from a b/w BufferedImage. This
+ * compression technique usually results in smaller images than those produced by {@link LosslessFactory#createFromImage(PDDocument, BufferedImage)
+ * }.
+ *
+ * @param document the document to create the image as part of.
+ * @param image the image.
+ * @return a new image XObject.
+ * @throws IOException if there is an error creating the image.
+ * @throws IllegalArgumentException if the BufferedImage is not a b/w image.
+ */
+ public static PDImageXObject createFromImage(PDDocument document, BufferedImage image)
+ throws IOException
+ {
+ if (image.getType() != BufferedImage.TYPE_BYTE_BINARY && image.getColorModel().getPixelSize() != 1)
+ {
+ throw new IllegalArgumentException("Only 1-bit b/w images supported");
+ }
+
+ int height = image.getHeight();
+ int width = image.getWidth();
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ MemoryCacheImageOutputStream mcios = new MemoryCacheImageOutputStream(bos);
+
+ for (int y = 0; y < height; ++y)
+ {
+ for (int x = 0; x < width; ++x)
+ {
+ // flip bit to avoid having to set /BlackIs1
+ mcios.writeBits(~(image.getRGB(x, y) & 1), 1);
+ }
+ while (mcios.getBitOffset() != 0)
+ {
+ mcios.writeBit(0);
+ }
+ }
+ mcios.flush();
+ mcios.close();
+
+ return prepareImageXObject(document, bos.toByteArray(), width, height, PDDeviceGray.INSTANCE);
+ }
+
+ private static PDImageXObject prepareImageXObject(PDDocument document,
+ byte[] byteArray, int width, int height,
+ PDColorSpace initColorSpace) throws IOException
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ Filter filter = FilterFactory.INSTANCE.getFilter(COSName.CCITTFAX_DECODE);
+ COSDictionary dict = new COSDictionary();
+ dict.setInt(COSName.COLUMNS, width);
+ dict.setInt(COSName.ROWS, height);
+ filter.encode(new ByteArrayInputStream(byteArray), baos, dict, 0);
+
+ ByteArrayInputStream encodedByteStream = new ByteArrayInputStream(baos.toByteArray());
+ PDImageXObject image = new PDImageXObject(document, encodedByteStream, COSName.CCITTFAX_DECODE,
+ width, height, 1, initColorSpace);
+ dict.setInt(COSName.K, -1);
+ image.getCOSObject().setItem(COSName.DECODE_PARMS, dict);
+ return image;
+ }
+
+ /**
* Creates a new CCITT Fax compressed image XObject from the first image of a TIFF file.
*
* @param document the document to create the image as part of.
@@ -82,6 +150,7 @@ public final class CCITTFactory
* single-strip CCITT T4 or T6 compressed TIFF files are supported. If you're not sure what TIFF
* files you have, use
* {@link LosslessFactory#createFromImage(org.apache.pdfbox.pdmodel.PDDocument, java.awt.image.BufferedImage)}
+ * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
* instead.
*
* @param document the document to create the image as part of.
@@ -99,7 +168,8 @@ public final class CCITTFactory
* Creates a new CCITT Fax compressed image XObject from a specific image of a TIFF file. Only
* single-strip CCITT T4 or T6 compressed TIFF files are supported. If you're not sure what TIFF
* files you have, use
- * {@link LosslessFactory#createFromImage(org.apache.pdfbox.pdmodel.PDDocument, java.awt.image.BufferedImage)}
+ * {@link LosslessFactory#createFromImage(PDDocument, BufferedImage) }
+ * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
* instead.
*
* @param document the document to create the image as part of.
@@ -298,6 +368,15 @@ public final class CCITTFactory
}
break;
}
+ case 274:
+ {
+ // http://www.awaresystems.be/imaging/tiff/tifftags/orientation.html
+ if (val != 1)
+ {
+ throw new IOException("Orientation " + val + " is not supported");
+ }
+ break;
+ }
case 279:
{
if (count == 1)
@@ -310,7 +389,8 @@ public final class CCITTFactory
{
if ((val & 1) != 0)
{
- k = 50; // T4 2D - arbitary positive K value
+ // T4 2D - arbitary positive K value
+ k = 50;
}
// http://www.awaresystems.be/imaging/tiff/tifftags/t4options.html
if ((val & 4) != 0)
Modified: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java?rev=1749936&r1=1749935&r2=1749936&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java (original)
+++ pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactoryTest.java Thu Jun 23 16:29:33 2016
@@ -136,4 +136,63 @@ public class CCITTFactoryTest extends Te
document.close();
imageReader.dispose();
}
+
+ public void testCreateFromBufferedImage() throws IOException
+ {
+ String tiffG4Path = "src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/ccittg4.tif";
+
+ PDDocument document = new PDDocument();
+ BufferedImage bim = ImageIO.read(new File(tiffG4Path));
+ PDImageXObject ximage3 = CCITTFactory.createFromImage(document, bim);
+ validate(ximage3, 1, 344, 287, "tiff", PDDeviceGray.INSTANCE.getName());
+ checkIdent(bim, ximage3.getOpaqueImage());
+
+ PDPage page = new PDPage(PDRectangle.A4);
+ document.addPage(page);
+ PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, false);
+ contentStream.drawImage(ximage3, 0, 0, ximage3.getWidth(), ximage3.getHeight());
+ contentStream.close();
+
+ document.save(testResultsDir + "/singletifffrombi.pdf");
+ document.close();
+
+ document = PDDocument.load(new File(testResultsDir, "singletifffrombi.pdf"));
+ assertEquals(1, document.getNumberOfPages());
+
+ document.close();
+ }
+
+ public void testCreateFromBufferedChessImage() throws IOException
+ {
+ PDDocument document = new PDDocument();
+ BufferedImage bim = new BufferedImage(343, 287, BufferedImage.TYPE_BYTE_BINARY);
+ assertTrue((bim.getWidth() / 8) * 8 != bim.getWidth()); // not mult of 8
+ int col = 0;
+ for (int x = 0; x < bim.getWidth(); ++x)
+ {
+ for (int y = 0; y < bim.getHeight(); ++y)
+ {
+ bim.setRGB(x, y, col & 0xFFFFFF);
+ col = ~col;
+ }
+ }
+
+ PDImageXObject ximage3 = CCITTFactory.createFromImage(document, bim);
+ validate(ximage3, 1, 343, 287, "tiff", PDDeviceGray.INSTANCE.getName());
+ checkIdent(bim, ximage3.getOpaqueImage());
+
+ PDPage page = new PDPage(PDRectangle.A4);
+ document.addPage(page);
+ PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, false);
+ contentStream.drawImage(ximage3, 0, 0, ximage3.getWidth(), ximage3.getHeight());
+ contentStream.close();
+
+ document.save(testResultsDir + "/singletifffromchessbi.pdf");
+ document.close();
+
+ document = PDDocument.load(new File(testResultsDir, "singletifffromchessbi.pdf"));
+ assertEquals(1, document.getNumberOfPages());
+
+ document.close();
+ }
}