Author: tilman
Date: Mon Apr 28 08:27:31 2025
New Revision: 1925302
URL: http://svn.apache.org/viewvc?rev=1925302&view=rev
Log:
PDFBOX-5996: Set size for ByteArrayOutputStreams, as suggested by Axel Howind
Modified:
pdfbox/branches/3.0/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
pdfbox/branches/3.0/tools/src/main/java/org/apache/pdfbox/tools/imageio/ImageIOUtil.java
Modified:
pdfbox/branches/3.0/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
URL:
http://svn.apache.org/viewvc/pdfbox/branches/3.0/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java?rev=1925302&r1=1925301&r2=1925302&view=diff
==============================================================================
---
pdfbox/branches/3.0/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
(original)
+++
pdfbox/branches/3.0/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
Mon Apr 28 08:27:31 2025
@@ -235,7 +235,7 @@ public final class TTFSubsetter
private byte[] buildHeadTable() throws IOException
{
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(54);
DataOutputStream out = new DataOutputStream(bos);
HeaderTable h = ttf.getHeader();
@@ -264,7 +264,7 @@ public final class TTFSubsetter
private byte[] buildHheaTable() throws IOException
{
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(36);
DataOutputStream out = new DataOutputStream(bos);
HorizontalHeaderTable h = ttf.getHorizontalHeader();
@@ -308,7 +308,7 @@ public final class TTFSubsetter
private byte[] buildNameTable() throws IOException
{
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(512);
DataOutputStream out = new DataOutputStream(bos);
NamingTable name = ttf.getNaming();
@@ -393,7 +393,7 @@ public final class TTFSubsetter
private byte[] buildMaxpTable() throws IOException
{
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(32);
DataOutputStream out = new DataOutputStream(bos);
MaximumProfileTable p = ttf.getMaximumProfile();
@@ -428,7 +428,7 @@ public final class TTFSubsetter
return null;
}
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(78);
DataOutputStream out = new DataOutputStream(bos);
writeUint16(out, os2.getVersion());
@@ -476,7 +476,7 @@ public final class TTFSubsetter
// never returns null
private byte[] buildLocaTable(long[] newOffsets) throws IOException
{
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteArrayOutputStream bos = new
ByteArrayOutputStream(newOffsets.length * 4);
DataOutputStream out = new DataOutputStream(bos);
for (long offset : newOffsets)
@@ -596,7 +596,7 @@ public final class TTFSubsetter
// never returns null
private byte[] buildGlyfTable(long[] newOffsets) throws IOException
{
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(512);
GlyphTable g = ttf.getGlyph();
long[] offsets = ttf.getIndexToLocation().getOffsets();
@@ -745,7 +745,7 @@ public final class TTFSubsetter
return null;
}
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(64);
DataOutputStream out = new DataOutputStream(bos);
// cmap header
@@ -865,7 +865,7 @@ public final class TTFSubsetter
return null;
}
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(64);
DataOutputStream out = new DataOutputStream(bos);
writeFixed(out, 2.0); // version
Modified:
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java
URL:
http://svn.apache.org/viewvc/pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java?rev=1925302&r1=1925301&r2=1925302&view=diff
==============================================================================
---
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java
(original)
+++
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSString.java
Mon Apr 28 08:27:31 2025
@@ -135,7 +135,6 @@ public final class COSString extends COS
*/
public static COSString parseHex(String hex) throws IOException
{
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
StringBuilder hexBuffer = new StringBuilder(hex.trim());
// if odd number then the last hex digit is assumed to be 0
@@ -145,6 +144,7 @@ public final class COSString extends COS
}
int length = hexBuffer.length();
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream((length + 1) /
2);
for (int i = 0; i < length; i += 2)
{
try
Modified:
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java
URL:
http://svn.apache.org/viewvc/pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java?rev=1925302&r1=1925301&r2=1925302&view=diff
==============================================================================
---
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java
(original)
+++
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDFont.java
Mon Apr 28 08:27:31 2025
@@ -328,7 +328,7 @@ public abstract class PDFont implements
*/
public final byte[] encode(String text) throws IOException
{
- ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(32,
text.length()));
int offset = 0;
while (offset < text.length())
{
Modified:
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
URL:
http://svn.apache.org/viewvc/pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java?rev=1925302&r1=1925301&r2=1925302&view=diff
==============================================================================
---
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
(original)
+++
pdfbox/branches/3.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/image/CCITTFactory.java
Mon Apr 28 08:27:31 2025
@@ -1,478 +1,478 @@
-/*
- * 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.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.RandomAccessReadBuffer;
-import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
-import org.apache.pdfbox.io.RandomAccessRead;
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
-import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
-
-/**
- * Factory for creating a PDImageXObject containing a CCITT Fax compressed
TIFF image.
- *
- * @author Ben Litchfield
- * @author Paul King
- */
-public final class CCITTFactory
-{
- private 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();
- try (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);
- }
- int bitOffset = mcios.getBitOffset();
- if (bitOffset != 0)
- {
- mcios.writeBits(0, 8 - bitOffset);
- }
- }
- mcios.flush();
- }
-
- return prepareImageXObject(document, bos.toByteArray(), width, height,
PDDeviceGray.INSTANCE);
- }
-
- /**
- * Creates a new CCITT Fax compressed image XObject from a specific image
of a TIFF file stored
- * in a byte array. 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(PDDocument, BufferedImage) }
- * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
- * instead.
- *
- * @param document the document to create the image as part of.
- * @param byteArray the TIFF file in a byte array which contains a
suitable CCITT compressed
- * image
- * @return a new Image XObject
- * @throws IOException if there is an error reading the TIFF data.
- */
- public static PDImageXObject createFromByteArray(PDDocument document,
byte[] byteArray)
- throws IOException
- {
- return createFromByteArray(document, byteArray, 0);
- }
-
- /**
- * Creates a new CCITT Fax compressed image XObject from a specific image
of a TIFF file stored
- * in a byte array. 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(PDDocument, BufferedImage) }
- * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
- * instead.
- *
- * @param document the document to create the image as part of.
- * @param byteArray the TIFF file in a byte array which contains a
suitable CCITT compressed
- * image
- * @param number TIFF image number, starting from 0
- * @return a new Image XObject
- * @throws IOException if there is an error reading the TIFF data.
- */
- public static PDImageXObject createFromByteArray(PDDocument document,
byte[] byteArray, int number)
- throws IOException
- {
- try (RandomAccessRead raf = new RandomAccessReadBuffer(byteArray))
- {
- return createFromRandomAccessImpl(document, raf, number);
- }
- }
-
- 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. 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)}
- * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
- * instead.
- *
- * @param document the document to create the image as part of.
- * @param file the TIFF file which contains a suitable CCITT compressed
image
- * @return a new Image XObject
- * @throws IOException if there is an error reading the TIFF data.
- */
- public static PDImageXObject createFromFile(PDDocument document, File file)
- throws IOException
- {
- return createFromFile(document, file, 0);
- }
-
- /**
- * 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(PDDocument, BufferedImage) }
- * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
- * instead.
- *
- * @param document the document to create the image as part of.
- * @param file the TIFF file which contains a suitable CCITT compressed
image
- * @param number TIFF image number, starting from 0
- * @return a new Image XObject
- * @throws IOException if there is an error reading the TIFF data.
- */
- public static PDImageXObject createFromFile(PDDocument document, File
file, int number)
- throws IOException
- {
- try (RandomAccessRead raf = new RandomAccessReadBufferedFile(file))
- {
- return createFromRandomAccessImpl(document, raf, number);
- }
- }
-
- /**
- * Creates a new CCITT Fax compressed image XObject from a TIFF file.
- *
- * @param document the document to create the image as part of.
- * @param reader the random access TIFF file which contains a suitable
CCITT
- * compressed image
- * @param number TIFF image number, starting from 0
- * @return a new Image XObject, or null if no such page
- * @throws IOException if there is an error reading the TIFF data.
- */
- private static PDImageXObject createFromRandomAccessImpl(PDDocument
document,
- RandomAccessRead reader,
- int number)
throws IOException
- {
- COSDictionary decodeParms = new COSDictionary();
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- extractFromTiff(reader, bos, decodeParms, number);
- if (bos.size() == 0)
- {
- return null;
- }
- ByteArrayInputStream encodedByteStream = new
ByteArrayInputStream(bos.toByteArray());
- PDImageXObject pdImage = new PDImageXObject(document,
- encodedByteStream,
- COSName.CCITTFAX_DECODE,
- decodeParms.getInt(COSName.COLUMNS),
- decodeParms.getInt(COSName.ROWS),
- 1,
- PDDeviceGray.INSTANCE);
-
- COSDictionary dict = pdImage.getCOSObject();
- dict.setItem(COSName.DECODE_PARMS, decodeParms);
- return pdImage;
- }
-
- // extracts the CCITT stream from the TIFF file
- private static void extractFromTiff(RandomAccessRead reader,
- OutputStream os,
- COSDictionary params, int number) throws IOException
- {
- try
- {
- // First check the basic tiff header
- reader.seek(0);
- char endianess = (char) reader.read();
- if ((char) reader.read() != endianess)
- {
- throw new IOException("Not a valid tiff file");
- }
- // ensure that endianess is either M or I
- if (endianess != 'M' && endianess != 'I')
- {
- throw new IOException("Not a valid tiff file");
- }
- int magicNumber = readshort(endianess, reader);
- if (magicNumber != 42)
- {
- throw new IOException("Not a valid tiff file");
- }
-
- // Relocate to the first set of tags
- long address = readlong(endianess, reader);
- reader.seek(address);
-
- // If some higher page number is required, skip this page's tags,
- // then read the next page's address
- for (int i = 0; i < number; i++)
- {
- int numtags = readshort(endianess, reader);
- if (numtags > 50)
- {
- throw new IOException("Not a valid tiff file");
- }
- reader.seek(address + 2 + numtags * 12L);
- address = readlong(endianess, reader);
- if (address == 0)
- {
- return;
- }
- reader.seek(address);
- }
-
- int numtags = readshort(endianess, reader);
-
- // The number 50 is somewhat arbitrary, it just stops us load up
junk from somewhere
- // and tramping on
- if (numtags > 50)
- {
- throw new IOException("Not a valid tiff file");
- }
-
- // Loop through the tags, some will convert to items in the params
dictionary
- // Other point us to where to find the data stream.
- // The only param which might change as a result of other TIFF
tags is K, so
- // we'll deal with that differently.
-
- // Default value to detect error
- int k = -1000;
-
- int dataoffset = 0;
- int datalength = 0;
-
- for (int i = 0; i < numtags; i++)
- {
- int tag = readshort(endianess, reader);
- int type = readshort(endianess, reader);
- int count = readlong(endianess, reader);
- int val;
- // Note that when the type is shorter than 4 bytes, the rest
can be garbage
- // and must be ignored. E.g. short (2 bytes) from "01 00 38
32" (little endian)
- // is 1, not 842530817 (seen in a real-life TIFF image).
- switch (type)
- {
- case 1: // byte value
- val = reader.read();
- reader.read();
- reader.read();
- reader.read();
- break;
- case 3: // short value
- val = readshort(endianess, reader);
- reader.read();
- reader.read();
- break;
- default: // long and other types
- val = readlong(endianess, reader);
- break;
- }
- switch (tag)
- {
- case 256:
- {
- params.setInt(COSName.COLUMNS, val);
- break;
- }
- case 257:
- {
- params.setInt(COSName.ROWS, val);
- break;
- }
- case 259:
- {
- if (val == 4)
- {
- k = -1;
- }
- if (val == 3)
- {
- k = 0;
- }
- break; // T6/T4 Compression
- }
- case 262:
- {
- if (val == 1)
- {
- params.setBoolean(COSName.BLACK_IS_1, true);
- }
- break;
- }
- case 266:
- {
- if (val != 1)
- {
- throw new IOException("FillOrder " + val + " is
not supported");
- }
- break;
- }
- case 273:
- {
- if (count == 1)
- {
- dataoffset = val;
- }
- 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)
- {
- datalength = val;
- }
- break;
- }
- case 292:
- {
- if ((val & 1) != 0)
- {
- // T4 2D - arbitrary positive K value
- k = 50;
- }
- //
http://www.awaresystems.be/imaging/tiff/tifftags/t4options.html
- if ((val & 4) != 0)
- {
- throw new IOException("CCITT Group 3 'uncompressed
mode' is not supported");
- }
- if ((val & 2) != 0)
- {
- throw new IOException("CCITT Group 3 'fill bits
before EOL' is not supported");
- }
- break;
- }
- case 324:
- {
- if (count == 1)
- {
- dataoffset = val;
- }
- break;
- }
- case 325:
- {
- if (count == 1)
- {
- datalength = val;
- }
- break;
- }
- default:
- {
- // do nothing
- }
- }
- }
-
- if (k == -1000)
- {
- throw new IOException("First image in tiff is not CCITT T4 or
T6 compressed");
- }
- if (dataoffset == 0)
- {
- throw new IOException("First image in tiff is not a single
tile/strip");
- }
-
- params.setInt(COSName.K, k);
-
- reader.seek(dataoffset);
-
- byte[] buf = new byte[8192];
- int amountRead;
- while ((amountRead = reader.read(buf, 0, Math.min(8192,
datalength))) > 0)
- {
- datalength -= amountRead;
- os.write(buf, 0, amountRead);
- }
-
- }
- finally
- {
- os.close();
- }
- }
-
- private static int readshort(char endianess, RandomAccessRead raf) throws
IOException
- {
- if (endianess == 'I')
- {
- return raf.read() | (raf.read() << 8);
- }
- return (raf.read() << 8) | raf.read();
- }
-
- private static int readlong(char endianess, RandomAccessRead raf) throws
IOException
- {
- if (endianess == 'I')
- {
- return raf.read() | (raf.read() << 8) | (raf.read() << 16) |
(raf.read() << 24);
- }
- return (raf.read() << 24) | (raf.read() << 16) | (raf.read() << 8) |
raf.read();
- }
-}
+/*
+ * 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.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.RandomAccessReadBuffer;
+import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
+import org.apache.pdfbox.io.RandomAccessRead;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+
+/**
+ * Factory for creating a PDImageXObject containing a CCITT Fax compressed
TIFF image.
+ *
+ * @author Ben Litchfield
+ * @author Paul King
+ */
+public final class CCITTFactory
+{
+ private 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(Math.max(32,
(width + 1) * height));
+ try (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);
+ }
+ int bitOffset = mcios.getBitOffset();
+ if (bitOffset != 0)
+ {
+ mcios.writeBits(0, 8 - bitOffset);
+ }
+ }
+ mcios.flush();
+ }
+
+ return prepareImageXObject(document, bos.toByteArray(), width, height,
PDDeviceGray.INSTANCE);
+ }
+
+ /**
+ * Creates a new CCITT Fax compressed image XObject from a specific image
of a TIFF file stored
+ * in a byte array. 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(PDDocument, BufferedImage) }
+ * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
+ * instead.
+ *
+ * @param document the document to create the image as part of.
+ * @param byteArray the TIFF file in a byte array which contains a
suitable CCITT compressed
+ * image
+ * @return a new Image XObject
+ * @throws IOException if there is an error reading the TIFF data.
+ */
+ public static PDImageXObject createFromByteArray(PDDocument document,
byte[] byteArray)
+ throws IOException
+ {
+ return createFromByteArray(document, byteArray, 0);
+ }
+
+ /**
+ * Creates a new CCITT Fax compressed image XObject from a specific image
of a TIFF file stored
+ * in a byte array. 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(PDDocument, BufferedImage) }
+ * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
+ * instead.
+ *
+ * @param document the document to create the image as part of.
+ * @param byteArray the TIFF file in a byte array which contains a
suitable CCITT compressed
+ * image
+ * @param number TIFF image number, starting from 0
+ * @return a new Image XObject
+ * @throws IOException if there is an error reading the TIFF data.
+ */
+ public static PDImageXObject createFromByteArray(PDDocument document,
byte[] byteArray, int number)
+ throws IOException
+ {
+ try (RandomAccessRead raf = new RandomAccessReadBuffer(byteArray))
+ {
+ return createFromRandomAccessImpl(document, raf, number);
+ }
+ }
+
+ 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. 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)}
+ * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
+ * instead.
+ *
+ * @param document the document to create the image as part of.
+ * @param file the TIFF file which contains a suitable CCITT compressed
image
+ * @return a new Image XObject
+ * @throws IOException if there is an error reading the TIFF data.
+ */
+ public static PDImageXObject createFromFile(PDDocument document, File file)
+ throws IOException
+ {
+ return createFromFile(document, file, 0);
+ }
+
+ /**
+ * 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(PDDocument, BufferedImage) }
+ * or {@link CCITTFactory#createFromImage(PDDocument, BufferedImage) }
+ * instead.
+ *
+ * @param document the document to create the image as part of.
+ * @param file the TIFF file which contains a suitable CCITT compressed
image
+ * @param number TIFF image number, starting from 0
+ * @return a new Image XObject
+ * @throws IOException if there is an error reading the TIFF data.
+ */
+ public static PDImageXObject createFromFile(PDDocument document, File
file, int number)
+ throws IOException
+ {
+ try (RandomAccessRead raf = new RandomAccessReadBufferedFile(file))
+ {
+ return createFromRandomAccessImpl(document, raf, number);
+ }
+ }
+
+ /**
+ * Creates a new CCITT Fax compressed image XObject from a TIFF file.
+ *
+ * @param document the document to create the image as part of.
+ * @param reader the random access TIFF file which contains a suitable
CCITT
+ * compressed image
+ * @param number TIFF image number, starting from 0
+ * @return a new Image XObject, or null if no such page
+ * @throws IOException if there is an error reading the TIFF data.
+ */
+ private static PDImageXObject createFromRandomAccessImpl(PDDocument
document,
+ RandomAccessRead reader,
+ int number)
throws IOException
+ {
+ COSDictionary decodeParms = new COSDictionary();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ extractFromTiff(reader, bos, decodeParms, number);
+ if (bos.size() == 0)
+ {
+ return null;
+ }
+ ByteArrayInputStream encodedByteStream = new
ByteArrayInputStream(bos.toByteArray());
+ PDImageXObject pdImage = new PDImageXObject(document,
+ encodedByteStream,
+ COSName.CCITTFAX_DECODE,
+ decodeParms.getInt(COSName.COLUMNS),
+ decodeParms.getInt(COSName.ROWS),
+ 1,
+ PDDeviceGray.INSTANCE);
+
+ COSDictionary dict = pdImage.getCOSObject();
+ dict.setItem(COSName.DECODE_PARMS, decodeParms);
+ return pdImage;
+ }
+
+ // extracts the CCITT stream from the TIFF file
+ private static void extractFromTiff(RandomAccessRead reader,
+ OutputStream os,
+ COSDictionary params, int number) throws IOException
+ {
+ try
+ {
+ // First check the basic tiff header
+ reader.seek(0);
+ char endianess = (char) reader.read();
+ if ((char) reader.read() != endianess)
+ {
+ throw new IOException("Not a valid tiff file");
+ }
+ // ensure that endianess is either M or I
+ if (endianess != 'M' && endianess != 'I')
+ {
+ throw new IOException("Not a valid tiff file");
+ }
+ int magicNumber = readshort(endianess, reader);
+ if (magicNumber != 42)
+ {
+ throw new IOException("Not a valid tiff file");
+ }
+
+ // Relocate to the first set of tags
+ long address = readlong(endianess, reader);
+ reader.seek(address);
+
+ // If some higher page number is required, skip this page's tags,
+ // then read the next page's address
+ for (int i = 0; i < number; i++)
+ {
+ int numtags = readshort(endianess, reader);
+ if (numtags > 50)
+ {
+ throw new IOException("Not a valid tiff file");
+ }
+ reader.seek(address + 2 + numtags * 12L);
+ address = readlong(endianess, reader);
+ if (address == 0)
+ {
+ return;
+ }
+ reader.seek(address);
+ }
+
+ int numtags = readshort(endianess, reader);
+
+ // The number 50 is somewhat arbitrary, it just stops us load up
junk from somewhere
+ // and tramping on
+ if (numtags > 50)
+ {
+ throw new IOException("Not a valid tiff file");
+ }
+
+ // Loop through the tags, some will convert to items in the params
dictionary
+ // Other point us to where to find the data stream.
+ // The only param which might change as a result of other TIFF
tags is K, so
+ // we'll deal with that differently.
+
+ // Default value to detect error
+ int k = -1000;
+
+ int dataoffset = 0;
+ int datalength = 0;
+
+ for (int i = 0; i < numtags; i++)
+ {
+ int tag = readshort(endianess, reader);
+ int type = readshort(endianess, reader);
+ int count = readlong(endianess, reader);
+ int val;
+ // Note that when the type is shorter than 4 bytes, the rest
can be garbage
+ // and must be ignored. E.g. short (2 bytes) from "01 00 38
32" (little endian)
+ // is 1, not 842530817 (seen in a real-life TIFF image).
+ switch (type)
+ {
+ case 1: // byte value
+ val = reader.read();
+ reader.read();
+ reader.read();
+ reader.read();
+ break;
+ case 3: // short value
+ val = readshort(endianess, reader);
+ reader.read();
+ reader.read();
+ break;
+ default: // long and other types
+ val = readlong(endianess, reader);
+ break;
+ }
+ switch (tag)
+ {
+ case 256:
+ {
+ params.setInt(COSName.COLUMNS, val);
+ break;
+ }
+ case 257:
+ {
+ params.setInt(COSName.ROWS, val);
+ break;
+ }
+ case 259:
+ {
+ if (val == 4)
+ {
+ k = -1;
+ }
+ if (val == 3)
+ {
+ k = 0;
+ }
+ break; // T6/T4 Compression
+ }
+ case 262:
+ {
+ if (val == 1)
+ {
+ params.setBoolean(COSName.BLACK_IS_1, true);
+ }
+ break;
+ }
+ case 266:
+ {
+ if (val != 1)
+ {
+ throw new IOException("FillOrder " + val + " is
not supported");
+ }
+ break;
+ }
+ case 273:
+ {
+ if (count == 1)
+ {
+ dataoffset = val;
+ }
+ 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)
+ {
+ datalength = val;
+ }
+ break;
+ }
+ case 292:
+ {
+ if ((val & 1) != 0)
+ {
+ // T4 2D - arbitrary positive K value
+ k = 50;
+ }
+ //
http://www.awaresystems.be/imaging/tiff/tifftags/t4options.html
+ if ((val & 4) != 0)
+ {
+ throw new IOException("CCITT Group 3 'uncompressed
mode' is not supported");
+ }
+ if ((val & 2) != 0)
+ {
+ throw new IOException("CCITT Group 3 'fill bits
before EOL' is not supported");
+ }
+ break;
+ }
+ case 324:
+ {
+ if (count == 1)
+ {
+ dataoffset = val;
+ }
+ break;
+ }
+ case 325:
+ {
+ if (count == 1)
+ {
+ datalength = val;
+ }
+ break;
+ }
+ default:
+ {
+ // do nothing
+ }
+ }
+ }
+
+ if (k == -1000)
+ {
+ throw new IOException("First image in tiff is not CCITT T4 or
T6 compressed");
+ }
+ if (dataoffset == 0)
+ {
+ throw new IOException("First image in tiff is not a single
tile/strip");
+ }
+
+ params.setInt(COSName.K, k);
+
+ reader.seek(dataoffset);
+
+ byte[] buf = new byte[8192];
+ int amountRead;
+ while ((amountRead = reader.read(buf, 0, Math.min(8192,
datalength))) > 0)
+ {
+ datalength -= amountRead;
+ os.write(buf, 0, amountRead);
+ }
+
+ }
+ finally
+ {
+ os.close();
+ }
+ }
+
+ private static int readshort(char endianess, RandomAccessRead raf) throws
IOException
+ {
+ if (endianess == 'I')
+ {
+ return raf.read() | (raf.read() << 8);
+ }
+ return (raf.read() << 8) | raf.read();
+ }
+
+ private static int readlong(char endianess, RandomAccessRead raf) throws
IOException
+ {
+ if (endianess == 'I')
+ {
+ return raf.read() | (raf.read() << 8) | (raf.read() << 16) |
(raf.read() << 24);
+ }
+ return (raf.read() << 24) | (raf.read() << 16) | (raf.read() << 8) |
raf.read();
+ }
+}
Modified:
pdfbox/branches/3.0/tools/src/main/java/org/apache/pdfbox/tools/imageio/ImageIOUtil.java
URL:
http://svn.apache.org/viewvc/pdfbox/branches/3.0/tools/src/main/java/org/apache/pdfbox/tools/imageio/ImageIOUtil.java?rev=1925302&r1=1925301&r2=1925302&view=diff
==============================================================================
---
pdfbox/branches/3.0/tools/src/main/java/org/apache/pdfbox/tools/imageio/ImageIOUtil.java
(original)
+++
pdfbox/branches/3.0/tools/src/main/java/org/apache/pdfbox/tools/imageio/ImageIOUtil.java
Mon Apr 28 08:27:31 2025
@@ -1,407 +1,406 @@
-/*
- * 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.apache.pdfbox.tools.imageio;
-
-import java.awt.color.ColorSpace;
-import java.awt.color.ICC_ColorSpace;
-import java.awt.color.ICC_Profile;
-import java.awt.image.BufferedImage;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.zip.DeflaterOutputStream;
-
-import javax.imageio.IIOImage;
-import javax.imageio.ImageIO;
-import javax.imageio.ImageTypeSpecifier;
-import javax.imageio.ImageWriteParam;
-import javax.imageio.ImageWriter;
-import javax.imageio.metadata.IIOInvalidTreeException;
-import javax.imageio.metadata.IIOMetadata;
-import javax.imageio.metadata.IIOMetadataNode;
-import javax.imageio.stream.ImageOutputStream;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-/**
- * Handles some ImageIO operations.
- */
-public final class ImageIOUtil
-{
- /**
- * Log instance
- */
- private static final Log LOG = LogFactory.getLog(ImageIOUtil.class);
-
- private ImageIOUtil()
- {
- }
-
- /**
- * Writes a buffered image to a file using the given image format. The
compression is set for
- * maximum compression for PNG and maximum quality for all other file
formats. See
- * {@link #writeImage(BufferedImage image, String formatName, OutputStream
output, int dpi, float compressionQuality)}
- * for more details.
- *
- * @param image the image to be written
- * @param filename used to construct the filename for the individual image.
- * Its suffix will be used as the image format.
- * @param dpi the resolution in dpi (dots per inch) to be used in metadata
- * @return true if the image file was produced, false if there was an
error.
- * @throws IOException if an I/O error occurs
- */
- public static boolean writeImage(BufferedImage image, String filename,
- int dpi) throws IOException
- {
- float compressionQuality = 1f;
- String formatName = filename.substring(filename.lastIndexOf('.') + 1);
- if ("png".equalsIgnoreCase(formatName))
- {
- // PDFBOX-4655: prevent huge PNG files on jdk11 / jdk12 / jdk13
- compressionQuality = 0f;
- }
- return writeImage(image, filename, dpi, compressionQuality);
- }
-
- /**
- * Writes a buffered image to a file using the given image format.
- * See {@link #writeImage(BufferedImage image, String formatName,
- * OutputStream output, int dpi, float compressionQuality)} for more
details.
- *
- * @param image the image to be written
- * @param filename used to construct the filename for the individual
image. Its suffix will be
- * used as the image format.
- * @param dpi the resolution in dpi (dots per inch) to be used in metadata
- * @param compressionQuality quality to be used when compressing the image
(0 <
- * compressionQuality < 1.0f). See {@link
ImageWriteParam#setCompressionQuality(float)} for
- * more details.
- * @return true if the image file was produced, false if there was an
error.
- * @throws IOException if an I/O error occurs
- */
- public static boolean writeImage(BufferedImage image, String filename,
- int dpi, float compressionQuality) throws IOException
- {
- try (OutputStream output = new BufferedOutputStream(new
FileOutputStream(filename)))
- {
- String formatName = filename.substring(filename.lastIndexOf('.') +
1);
- return writeImage(image, formatName, output, dpi,
compressionQuality);
- }
- }
-
- /**
- * Writes a buffered image to a file using the given image format. The
compression is set for
- * maximum compression for PNG and maximum quality for all other file
formats. See
- * {@link #writeImage(BufferedImage image, String formatName, OutputStream
output, int dpi, float compressionQuality)}
- * for more details.
- *
- * @param image the image to be written
- * @param formatName the target format (ex. "png")
- * @param output the output stream to be used for writing
- * @return true if the image file was produced, false if there was an
error.
- * @throws IOException if an I/O error occurs
- */
- public static boolean writeImage(BufferedImage image, String formatName,
OutputStream output)
- throws IOException
- {
- return writeImage(image, formatName, output, 72);
- }
-
- /**
- * Writes a buffered image to a file using the given image format. The
compression is set for
- * maximum compression for PNG and maximum quality for all other file
formats. See
- * {@link #writeImage(BufferedImage image, String formatName, OutputStream
output, int dpi, float compressionQuality)}
- * for more details.
- *
- * @param image the image to be written
- * @param formatName the target format (ex. "png")
- * @param output the output stream to be used for writing
- * @param dpi the resolution in dpi (dots per inch) to be used in metadata
- * @return true if the image file was produced, false if there was an
error.
- * @throws IOException if an I/O error occurs
- */
- public static boolean writeImage(BufferedImage image, String formatName,
OutputStream output,
- int dpi) throws IOException
- {
- float compressionQuality = 1f;
- if ("png".equalsIgnoreCase(formatName))
- {
- // PDFBOX-4655: prevent huge PNG files on jdk11 / jdk12 / jdk13
- compressionQuality = 0f;
- }
- return writeImage(image, formatName, output, dpi, compressionQuality);
- }
-
- /**
- * Writes a buffered image to a file using the given image format.
- * Compression is fixed for PNG, GIF, BMP and WBMP, dependent of the
compressionQuality
- * parameter for JPG, and dependent of bit count for TIFF (a bitonal image
- * will be compressed with CCITT G4, a color image with LZW). Creating a
- * TIFF image is only supported if the jai_imageio library (or equivalent)
- * is in the class path.
- *
- * @param image the image to be written
- * @param formatName the target format (ex. "png")
- * @param output the output stream to be used for writing
- * @param dpi the resolution in dpi (dots per inch) to be used in metadata
- * @param compressionQuality quality to be used when compressing the image
(0 <
- * compressionQuality < 1.0f). See {@link
ImageWriteParam#setCompressionQuality(float)} for
- * more details.
- * @return true if the image file was produced, false if there was an
error.
- * @throws IOException if an I/O error occurs
- */
- public static boolean writeImage(BufferedImage image, String formatName,
OutputStream output,
- int dpi, float compressionQuality) throws IOException
- {
- return writeImage(image, formatName, output, dpi, compressionQuality,
"");
- }
-
- /**
- * Writes a buffered image to a file using the given image format.
- * Compression is fixed for PNG, GIF, BMP and WBMP, dependent of the
compressionQuality
- * parameter for JPG, and dependent of bit count for TIFF (a bitonal image
- * will be compressed with CCITT G4, a color image with LZW). Creating a
- * TIFF image is only supported if the jai_imageio library is in the class
- * path.
- *
- * @param image the image to be written
- * @param formatName the target format (ex. "png")
- * @param output the output stream to be used for writing
- * @param dpi the resolution in dpi (dots per inch) to be used in metadata
- * @param compressionQuality quality to be used when compressing the image
(0 <
- * compressionQuality < 1.0f). See {@link
ImageWriteParam#setCompressionQuality(float)} for
- * more details.
- * @param compressionType Advanced users only, and only relevant for TIFF
- * files: If null, save uncompressed; if empty string, use logic explained
- * above; other valid values are found in the javadoc of
- * <a
href="https://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/TIFFImageWriteParam.html">TIFFImageWriteParam</a>.
- * @return true if the image file was produced, false if there was an
error.
- * @throws IOException if an I/O error occurs
- */
- public static boolean writeImage(BufferedImage image, String formatName,
OutputStream output,
- int dpi, float compressionQuality, String compressionType) throws
IOException
- {
- ImageOutputStream imageOutput = null;
- ImageWriter writer = null;
- try
- {
- // find suitable image writer
- Iterator<ImageWriter> writers =
ImageIO.getImageWritersByFormatName(formatName);
- ImageWriteParam param = null;
- IIOMetadata metadata = null;
- // Loop until we get the best driver, i.e. one that supports
- // setting dpi in the standard metadata format; however we'd also
- // accept a driver that can't, if a better one can't be found
- while (writers.hasNext())
- {
- if (writer != null)
- {
- writer.dispose();
- }
- writer = writers.next();
- if (writer != null)
- {
- param = writer.getDefaultWriteParam();
- metadata = writer.getDefaultImageMetadata(new
ImageTypeSpecifier(image), param);
- if (metadata != null
- && !metadata.isReadOnly()
- && metadata.isStandardMetadataFormatSupported())
- {
- break;
- }
- }
- }
- if (writer == null)
- {
- LOG.error("No ImageWriter found for '" + formatName + "'
format");
- LOG.error("Supported formats: " +
Arrays.toString(ImageIO.getWriterFormatNames()));
- return false;
- }
-
- boolean isTifFormat = formatName.toLowerCase().startsWith("tif");
-
- // compression
- if (param != null && param.canWriteCompressed())
- {
- param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
- if (isTifFormat)
- {
- if ("".equals(compressionType))
- {
- // default logic
- TIFFUtil.setCompressionType(param, image);
- }
- else
- {
- param.setCompressionType(compressionType);
- if (compressionType != null)
- {
- param.setCompressionQuality(compressionQuality);
- }
- }
- }
- else
- {
- param.setCompressionType(param.getCompressionTypes()[0]);
- param.setCompressionQuality(compressionQuality);
- }
- }
-
- if (metadata != null)
- {
- if (isTifFormat)
- {
- // TIFF metadata
- TIFFUtil.updateMetadata(metadata, image, dpi);
- }
- else if ("jpeg".equalsIgnoreCase(formatName) ||
"jpg".equalsIgnoreCase(formatName))
- {
- // This segment must be run before other meta operations,
- // or else "IIOInvalidTreeException: Invalid node:
app0JFIF"
- // The other (general) "meta" methods may not be used,
because
- // this will break the reading of the meta data in tests
- JPEGUtil.updateMetadata(metadata, dpi);
- }
- else
- {
- // write metadata is possible
- if (!metadata.isReadOnly() &&
metadata.isStandardMetadataFormatSupported())
- {
- setDPI(metadata, dpi, formatName);
- }
- }
- }
-
- if (metadata != null && formatName.equalsIgnoreCase("png") &&
hasICCProfile(image))
- {
- // add ICC profile
- IIOMetadataNode iccp = new IIOMetadataNode("iCCP");
- ICC_Profile profile = ((ICC_ColorSpace)
image.getColorModel().getColorSpace())
- .getProfile();
- iccp.setUserObject(getAsDeflatedBytes(profile));
- iccp.setAttribute("profileName", "unknown");
- iccp.setAttribute("compressionMethod", "deflate");
- Node nativeTree =
metadata.getAsTree(metadata.getNativeMetadataFormatName());
- nativeTree.appendChild(iccp);
- metadata.mergeTree(metadata.getNativeMetadataFormatName(),
nativeTree);
- }
-
- // write
- imageOutput = ImageIO.createImageOutputStream(output);
- writer.setOutput(imageOutput);
- writer.write(null, new IIOImage(image, null, metadata), param);
- }
- finally
- {
- if (writer != null)
- {
- writer.dispose();
- }
- if (imageOutput != null)
- {
- imageOutput.close();
- }
- }
- return true;
- }
-
- /**
- * Determine if the given image has a ICC profile that should be embedded.
- * @param image the image to analyse
- * @return true if this image has an ICC profile, that is different from
sRGB.
- */
- private static boolean hasICCProfile(BufferedImage image)
- {
- ColorSpace colorSpace = image.getColorModel().getColorSpace();
- // We can only export ICC color spaces
- if (!(colorSpace instanceof ICC_ColorSpace))
- {
- return false;
- }
-
- // The colorspace should not be sRGB and not be the builtin gray
colorspace
- return !colorSpace.isCS_sRGB() && colorSpace !=
ColorSpace.getInstance(ColorSpace.CS_GRAY);
- }
-
- private static byte[] getAsDeflatedBytes(ICC_Profile profile) throws
IOException
- {
- byte[] data = profile.getData();
-
- ByteArrayOutputStream deflated = new ByteArrayOutputStream();
- try (DeflaterOutputStream deflater = new
DeflaterOutputStream(deflated))
- {
- deflater.write(data);
- }
-
- return deflated.toByteArray();
- }
-
- /**
- * Gets the named child node, or creates and attaches it.
- *
- * @param parentNode the parent node
- * @param name name of the child node
- *
- * @return the existing or just created child node
- */
- private static IIOMetadataNode getOrCreateChildNode(IIOMetadataNode
parentNode, String name)
- {
- NodeList nodeList = parentNode.getElementsByTagName(name);
- if (nodeList.getLength() > 0)
- {
- return (IIOMetadataNode) nodeList.item(0);
- }
- IIOMetadataNode childNode = new IIOMetadataNode(name);
- parentNode.appendChild(childNode);
- return childNode;
- }
-
- // sets the DPI metadata
- private static void setDPI(IIOMetadata metadata, int dpi, String
formatName)
- throws IIOInvalidTreeException
- {
- IIOMetadataNode root = (IIOMetadataNode)
metadata.getAsTree(MetaUtil.STANDARD_METADATA_FORMAT);
-
- IIOMetadataNode dimension = getOrCreateChildNode(root, "Dimension");
-
- // PNG writer doesn't conform to the spec which is
- // "The width of a pixel, in millimeters"
- // but instead counts the pixels per millimeter
- float res = "PNG".equalsIgnoreCase(formatName)
- ? dpi / 25.4f
- : 25.4f / dpi;
-
- IIOMetadataNode child;
-
- child = getOrCreateChildNode(dimension, "HorizontalPixelSize");
- child.setAttribute("value", Double.toString(res));
-
- child = getOrCreateChildNode(dimension, "VerticalPixelSize");
- child.setAttribute("value", Double.toString(res));
-
- metadata.mergeTree(MetaUtil.STANDARD_METADATA_FORMAT, root);
- }
-}
+/*
+ * 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.apache.pdfbox.tools.imageio;
+
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.awt.image.BufferedImage;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.zip.DeflaterOutputStream;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.ImageOutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Handles some ImageIO operations.
+ */
+public final class ImageIOUtil
+{
+ /**
+ * Log instance
+ */
+ private static final Log LOG = LogFactory.getLog(ImageIOUtil.class);
+
+ private ImageIOUtil()
+ {
+ }
+
+ /**
+ * Writes a buffered image to a file using the given image format. The
compression is set for
+ * maximum compression for PNG and maximum quality for all other file
formats. See
+ * {@link #writeImage(BufferedImage image, String formatName, OutputStream
output, int dpi, float compressionQuality)}
+ * for more details.
+ *
+ * @param image the image to be written
+ * @param filename used to construct the filename for the individual image.
+ * Its suffix will be used as the image format.
+ * @param dpi the resolution in dpi (dots per inch) to be used in metadata
+ * @return true if the image file was produced, false if there was an
error.
+ * @throws IOException if an I/O error occurs
+ */
+ public static boolean writeImage(BufferedImage image, String filename,
+ int dpi) throws IOException
+ {
+ float compressionQuality = 1f;
+ String formatName = filename.substring(filename.lastIndexOf('.') + 1);
+ if ("png".equalsIgnoreCase(formatName))
+ {
+ // PDFBOX-4655: prevent huge PNG files on jdk11 / jdk12 / jdk13
+ compressionQuality = 0f;
+ }
+ return writeImage(image, filename, dpi, compressionQuality);
+ }
+
+ /**
+ * Writes a buffered image to a file using the given image format.
+ * See {@link #writeImage(BufferedImage image, String formatName,
+ * OutputStream output, int dpi, float compressionQuality)} for more
details.
+ *
+ * @param image the image to be written
+ * @param filename used to construct the filename for the individual
image. Its suffix will be
+ * used as the image format.
+ * @param dpi the resolution in dpi (dots per inch) to be used in metadata
+ * @param compressionQuality quality to be used when compressing the image
(0 <
+ * compressionQuality < 1.0f). See {@link
ImageWriteParam#setCompressionQuality(float)} for
+ * more details.
+ * @return true if the image file was produced, false if there was an
error.
+ * @throws IOException if an I/O error occurs
+ */
+ public static boolean writeImage(BufferedImage image, String filename,
+ int dpi, float compressionQuality) throws IOException
+ {
+ try (OutputStream output = new BufferedOutputStream(new
FileOutputStream(filename)))
+ {
+ String formatName = filename.substring(filename.lastIndexOf('.') +
1);
+ return writeImage(image, formatName, output, dpi,
compressionQuality);
+ }
+ }
+
+ /**
+ * Writes a buffered image to a file using the given image format. The
compression is set for
+ * maximum compression for PNG and maximum quality for all other file
formats. See
+ * {@link #writeImage(BufferedImage image, String formatName, OutputStream
output, int dpi, float compressionQuality)}
+ * for more details.
+ *
+ * @param image the image to be written
+ * @param formatName the target format (ex. "png")
+ * @param output the output stream to be used for writing
+ * @return true if the image file was produced, false if there was an
error.
+ * @throws IOException if an I/O error occurs
+ */
+ public static boolean writeImage(BufferedImage image, String formatName,
OutputStream output)
+ throws IOException
+ {
+ return writeImage(image, formatName, output, 72);
+ }
+
+ /**
+ * Writes a buffered image to a file using the given image format. The
compression is set for
+ * maximum compression for PNG and maximum quality for all other file
formats. See
+ * {@link #writeImage(BufferedImage image, String formatName, OutputStream
output, int dpi, float compressionQuality)}
+ * for more details.
+ *
+ * @param image the image to be written
+ * @param formatName the target format (ex. "png")
+ * @param output the output stream to be used for writing
+ * @param dpi the resolution in dpi (dots per inch) to be used in metadata
+ * @return true if the image file was produced, false if there was an
error.
+ * @throws IOException if an I/O error occurs
+ */
+ public static boolean writeImage(BufferedImage image, String formatName,
OutputStream output,
+ int dpi) throws IOException
+ {
+ float compressionQuality = 1f;
+ if ("png".equalsIgnoreCase(formatName))
+ {
+ // PDFBOX-4655: prevent huge PNG files on jdk11 / jdk12 / jdk13
+ compressionQuality = 0f;
+ }
+ return writeImage(image, formatName, output, dpi, compressionQuality);
+ }
+
+ /**
+ * Writes a buffered image to a file using the given image format.
+ * Compression is fixed for PNG, GIF, BMP and WBMP, dependent of the
compressionQuality
+ * parameter for JPG, and dependent of bit count for TIFF (a bitonal image
+ * will be compressed with CCITT G4, a color image with LZW). Creating a
+ * TIFF image is only supported if the jai_imageio library (or equivalent)
+ * is in the class path.
+ *
+ * @param image the image to be written
+ * @param formatName the target format (ex. "png")
+ * @param output the output stream to be used for writing
+ * @param dpi the resolution in dpi (dots per inch) to be used in metadata
+ * @param compressionQuality quality to be used when compressing the image
(0 <
+ * compressionQuality < 1.0f). See {@link
ImageWriteParam#setCompressionQuality(float)} for
+ * more details.
+ * @return true if the image file was produced, false if there was an
error.
+ * @throws IOException if an I/O error occurs
+ */
+ public static boolean writeImage(BufferedImage image, String formatName,
OutputStream output,
+ int dpi, float compressionQuality) throws IOException
+ {
+ return writeImage(image, formatName, output, dpi, compressionQuality,
"");
+ }
+
+ /**
+ * Writes a buffered image to a file using the given image format.
+ * Compression is fixed for PNG, GIF, BMP and WBMP, dependent of the
compressionQuality
+ * parameter for JPG, and dependent of bit count for TIFF (a bitonal image
+ * will be compressed with CCITT G4, a color image with LZW). Creating a
+ * TIFF image is only supported if the jai_imageio library is in the class
+ * path.
+ *
+ * @param image the image to be written
+ * @param formatName the target format (ex. "png")
+ * @param output the output stream to be used for writing
+ * @param dpi the resolution in dpi (dots per inch) to be used in metadata
+ * @param compressionQuality quality to be used when compressing the image
(0 <
+ * compressionQuality < 1.0f). See {@link
ImageWriteParam#setCompressionQuality(float)} for
+ * more details.
+ * @param compressionType Advanced users only, and only relevant for TIFF
+ * files: If null, save uncompressed; if empty string, use logic explained
+ * above; other valid values are found in the javadoc of
+ * <a
href="https://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/TIFFImageWriteParam.html">TIFFImageWriteParam</a>.
+ * @return true if the image file was produced, false if there was an
error.
+ * @throws IOException if an I/O error occurs
+ */
+ public static boolean writeImage(BufferedImage image, String formatName,
OutputStream output,
+ int dpi, float compressionQuality, String compressionType) throws
IOException
+ {
+ ImageOutputStream imageOutput = null;
+ ImageWriter writer = null;
+ try
+ {
+ // find suitable image writer
+ Iterator<ImageWriter> writers =
ImageIO.getImageWritersByFormatName(formatName);
+ ImageWriteParam param = null;
+ IIOMetadata metadata = null;
+ // Loop until we get the best driver, i.e. one that supports
+ // setting dpi in the standard metadata format; however we'd also
+ // accept a driver that can't, if a better one can't be found
+ while (writers.hasNext())
+ {
+ if (writer != null)
+ {
+ writer.dispose();
+ }
+ writer = writers.next();
+ if (writer != null)
+ {
+ param = writer.getDefaultWriteParam();
+ metadata = writer.getDefaultImageMetadata(new
ImageTypeSpecifier(image), param);
+ if (metadata != null
+ && !metadata.isReadOnly()
+ && metadata.isStandardMetadataFormatSupported())
+ {
+ break;
+ }
+ }
+ }
+ if (writer == null)
+ {
+ LOG.error("No ImageWriter found for '" + formatName + "'
format");
+ LOG.error("Supported formats: " +
Arrays.toString(ImageIO.getWriterFormatNames()));
+ return false;
+ }
+
+ boolean isTifFormat = formatName.toLowerCase().startsWith("tif");
+
+ // compression
+ if (param != null && param.canWriteCompressed())
+ {
+ param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+ if (isTifFormat)
+ {
+ if ("".equals(compressionType))
+ {
+ // default logic
+ TIFFUtil.setCompressionType(param, image);
+ }
+ else
+ {
+ param.setCompressionType(compressionType);
+ if (compressionType != null)
+ {
+ param.setCompressionQuality(compressionQuality);
+ }
+ }
+ }
+ else
+ {
+ param.setCompressionType(param.getCompressionTypes()[0]);
+ param.setCompressionQuality(compressionQuality);
+ }
+ }
+
+ if (metadata != null)
+ {
+ if (isTifFormat)
+ {
+ // TIFF metadata
+ TIFFUtil.updateMetadata(metadata, image, dpi);
+ }
+ else if ("jpeg".equalsIgnoreCase(formatName) ||
"jpg".equalsIgnoreCase(formatName))
+ {
+ // This segment must be run before other meta operations,
+ // or else "IIOInvalidTreeException: Invalid node:
app0JFIF"
+ // The other (general) "meta" methods may not be used,
because
+ // this will break the reading of the meta data in tests
+ JPEGUtil.updateMetadata(metadata, dpi);
+ }
+ else
+ {
+ // write metadata is possible
+ if (!metadata.isReadOnly() &&
metadata.isStandardMetadataFormatSupported())
+ {
+ setDPI(metadata, dpi, formatName);
+ }
+ }
+ }
+
+ if (metadata != null && formatName.equalsIgnoreCase("png") &&
hasICCProfile(image))
+ {
+ // add ICC profile
+ IIOMetadataNode iccp = new IIOMetadataNode("iCCP");
+ ICC_Profile profile = ((ICC_ColorSpace)
image.getColorModel().getColorSpace())
+ .getProfile();
+ iccp.setUserObject(getAsDeflatedBytes(profile));
+ iccp.setAttribute("profileName", "unknown");
+ iccp.setAttribute("compressionMethod", "deflate");
+ Node nativeTree =
metadata.getAsTree(metadata.getNativeMetadataFormatName());
+ nativeTree.appendChild(iccp);
+ metadata.mergeTree(metadata.getNativeMetadataFormatName(),
nativeTree);
+ }
+
+ // write
+ imageOutput = ImageIO.createImageOutputStream(output);
+ writer.setOutput(imageOutput);
+ writer.write(null, new IIOImage(image, null, metadata), param);
+ }
+ finally
+ {
+ if (writer != null)
+ {
+ writer.dispose();
+ }
+ if (imageOutput != null)
+ {
+ imageOutput.close();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Determine if the given image has a ICC profile that should be embedded.
+ * @param image the image to analyse
+ * @return true if this image has an ICC profile, that is different from
sRGB.
+ */
+ private static boolean hasICCProfile(BufferedImage image)
+ {
+ ColorSpace colorSpace = image.getColorModel().getColorSpace();
+ // We can only export ICC color spaces
+ if (!(colorSpace instanceof ICC_ColorSpace))
+ {
+ return false;
+ }
+
+ // The colorspace should not be sRGB and not be the builtin gray
colorspace
+ return !colorSpace.isCS_sRGB() && colorSpace !=
ColorSpace.getInstance(ColorSpace.CS_GRAY);
+ }
+
+ private static byte[] getAsDeflatedBytes(ICC_Profile profile) throws
IOException
+ {
+ byte[] data = profile.getData();
+
+ ByteArrayOutputStream deflated = new
ByteArrayOutputStream(Math.max(32, 2 * data.length));
+ try (DeflaterOutputStream deflater = new
DeflaterOutputStream(deflated))
+ {
+ deflater.write(data);
+ }
+ return deflated.toByteArray();
+ }
+
+ /**
+ * Gets the named child node, or creates and attaches it.
+ *
+ * @param parentNode the parent node
+ * @param name name of the child node
+ *
+ * @return the existing or just created child node
+ */
+ private static IIOMetadataNode getOrCreateChildNode(IIOMetadataNode
parentNode, String name)
+ {
+ NodeList nodeList = parentNode.getElementsByTagName(name);
+ if (nodeList.getLength() > 0)
+ {
+ return (IIOMetadataNode) nodeList.item(0);
+ }
+ IIOMetadataNode childNode = new IIOMetadataNode(name);
+ parentNode.appendChild(childNode);
+ return childNode;
+ }
+
+ // sets the DPI metadata
+ private static void setDPI(IIOMetadata metadata, int dpi, String
formatName)
+ throws IIOInvalidTreeException
+ {
+ IIOMetadataNode root = (IIOMetadataNode)
metadata.getAsTree(MetaUtil.STANDARD_METADATA_FORMAT);
+
+ IIOMetadataNode dimension = getOrCreateChildNode(root, "Dimension");
+
+ // PNG writer doesn't conform to the spec which is
+ // "The width of a pixel, in millimeters"
+ // but instead counts the pixels per millimeter
+ float res = "PNG".equalsIgnoreCase(formatName)
+ ? dpi / 25.4f
+ : 25.4f / dpi;
+
+ IIOMetadataNode child;
+
+ child = getOrCreateChildNode(dimension, "HorizontalPixelSize");
+ child.setAttribute("value", Double.toString(res));
+
+ child = getOrCreateChildNode(dimension, "VerticalPixelSize");
+ child.setAttribute("value", Double.toString(res));
+
+ metadata.mergeTree(MetaUtil.STANDARD_METADATA_FORMAT, root);
+ }
+}