diff --git a/poishadow/build.gradle b/poishadow/build.gradle index 8ea75acee..a627c9c96 100644 --- a/poishadow/build.gradle +++ b/poishadow/build.gradle @@ -13,7 +13,6 @@ repositories { dependencies { implementation 'org.apache.poi:poi-ooxml:5.2.5' implementation 'org.apache.poi:poi-scratchpad:5.2.5' - implementation 'com.fasterxml:aalto-xml:1.3.0' implementation 'stax:stax-api:1.0.1' implementation 'org.apache.logging.log4j:log4j-api:2.21.1' // newer log4j fails @@ -45,6 +44,14 @@ shadowJar { // java.awt is not available, but class Color is used in APIs in POI, therefore // relocate this class to another one where we can include a rewrite relocate 'java.awt.Color', 'org.apache.poi.java.awt.Color' + relocate 'java.awt.geom.Dimension2D', 'org.apache.poi.java.awt.geom.Dimension2D' + relocate 'java.awt.Dimension', 'org.apache.poi.java.awt.Dimension' + relocate 'java.awt.image.BufferedImage', 'org.apache.poi.java.awt.image.BufferedImage' + + relocate 'javax.imageio.ImageIO', 'org.apache.poi.javax.imageio.ImageIO' + relocate 'javax.imageio.ImageReader', 'org.apache.poi.javax.imageio.ImageReader' + relocate 'javax.imageio.stream.ImageInputStream', 'org.apache.poi.javax.imageio.stream.ImageInputStream' + relocate 'javax.imageio.metadata.IIOMetadata', 'org.apache.poi.javax.imageio.metadata.IIOMetadata' } jar.dependsOn shadowJar diff --git a/poishadow/src/main/java/org/apache/poi/java/awt/Dimension.java b/poishadow/src/main/java/org/apache/poi/java/awt/Dimension.java new file mode 100644 index 000000000..c5f8ef34e --- /dev/null +++ b/poishadow/src/main/java/org/apache/poi/java/awt/Dimension.java @@ -0,0 +1,202 @@ +package org.apache.poi.java.awt; + +import java.awt.geom.Dimension2D; +import java.beans.Transient; + +/** + * The {@code Dimension} class encapsulates the width and + * height of a component (in integer precision) in a single object. + * The class is + * associated with certain properties of components. Several methods + * defined by the {@code Component} class and the + * {@code LayoutManager} interface return a + * {@code Dimension} object. + *

+ * Normally the values of {@code width} + * and {@code height} are non-negative integers. + * The constructors that allow you to create a dimension do + * not prevent you from setting a negative value for these properties. + * If the value of {@code width} or {@code height} is + * negative, the behavior of some methods defined by other objects is + * undefined. + * + * @author Sami Shaio + * @author Arthur van Hoff + * @see java.awt.Component + * @see java.awt.LayoutManager + * @since 1.0 + */ +public class Dimension extends Dimension2D implements java.io.Serializable { + + /** + * The width dimension; negative values can be used. + * + * @serial + * @see #getSize + * @see #setSize + * @since 1.0 + */ + public int width; + + /** + * The height dimension; negative values can be used. + * + * @serial + * @see #getSize + * @see #setSize + * @since 1.0 + */ + public int height; + + /* + * JDK 1.1 serialVersionUID + */ + private static final long serialVersionUID = 4723952579491349524L; + + + /** + * Creates an instance of {@code Dimension} with a width + * of zero and a height of zero. + */ + public Dimension() { + this(0, 0); + } + + /** + * Creates an instance of {@code Dimension} whose width + * and height are the same as for the specified dimension. + * + * @param d the specified dimension for the + * {@code width} and + * {@code height} values + */ + public Dimension(java.awt.Dimension d) { + this(d.width, d.height); + } + + /** + * Constructs a {@code Dimension} and initializes + * it to the specified width and specified height. + * + * @param width the specified width + * @param height the specified height + */ + public Dimension(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * {@inheritDoc} + * @since 1.2 + */ + public double getWidth() { + return width; + } + + /** + * {@inheritDoc} + * @since 1.2 + */ + public double getHeight() { + return height; + } + + /** + * Sets the size of this {@code Dimension} object to + * the specified width and height in double precision. + * Note that if {@code width} or {@code height} + * are larger than {@code Integer.MAX_VALUE}, they will + * be reset to {@code Integer.MAX_VALUE}. + * + * @param width the new width for the {@code Dimension} object + * @param height the new height for the {@code Dimension} object + * @since 1.2 + */ + public void setSize(double width, double height) { + this.width = (int) Math.ceil(width); + this.height = (int) Math.ceil(height); + } + + /** + * Gets the size of this {@code Dimension} object. + * This method is included for completeness, to parallel the + * {@code getSize} method defined by {@code Component}. + * + * @return the size of this dimension, a new instance of + * {@code Dimension} with the same width and height + * @see java.awt.Dimension#setSize + * @see java.awt.Component#getSize + * @since 1.1 + */ + @Transient + public java.awt.Dimension getSize() { + return new java.awt.Dimension(width, height); + } + + /** + * Sets the size of this {@code Dimension} object to the specified size. + * This method is included for completeness, to parallel the + * {@code setSize} method defined by {@code Component}. + * @param d the new size for this {@code Dimension} object + * @see java.awt.Dimension#getSize + * @see java.awt.Component#setSize + * @since 1.1 + */ + public void setSize(java.awt.Dimension d) { + setSize(d.width, d.height); + } + + /** + * Sets the size of this {@code Dimension} object + * to the specified width and height. + * This method is included for completeness, to parallel the + * {@code setSize} method defined by {@code Component}. + * + * @param width the new width for this {@code Dimension} object + * @param height the new height for this {@code Dimension} object + * @see java.awt.Dimension#getSize + * @see java.awt.Component#setSize + * @since 1.1 + */ + public void setSize(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * Checks whether two dimension objects have equal values. + */ + public boolean equals(Object obj) { + if (obj instanceof java.awt.Dimension) { + java.awt.Dimension d = (java.awt.Dimension)obj; + return (width == d.width) && (height == d.height); + } + return false; + } + + /** + * Returns the hash code for this {@code Dimension}. + * + * @return a hash code for this {@code Dimension} + */ + public int hashCode() { + int sum = width + height; + return sum * (sum + 1)/2 + width; + } + + /** + * Returns a string representation of the values of this + * {@code Dimension} object's {@code height} and + * {@code width} fields. This method is intended to be used only + * for debugging purposes, and the content and format of the returned + * string may vary between implementations. The returned string may be + * empty but may not be {@code null}. + * + * @return a string representation of this {@code Dimension} + * object + */ + public String toString() { + return getClass().getName() + "[width=" + width + ",height=" + height + "]"; + } +} \ No newline at end of file diff --git a/poishadow/src/main/java/org/apache/poi/java/awt/geom/Dimension2D.java b/poishadow/src/main/java/org/apache/poi/java/awt/geom/Dimension2D.java new file mode 100644 index 000000000..9860e530e --- /dev/null +++ b/poishadow/src/main/java/org/apache/poi/java/awt/geom/Dimension2D.java @@ -0,0 +1,90 @@ +package org.apache.poi.java.awt.geom; + + +/** + * The {@code Dimension2D} class is to encapsulate a width + * and a height dimension. + *

+ * This class is only the abstract superclass for all objects that + * store a 2D dimension. + * The actual storage representation of the sizes is left to + * the subclass. + * + * @author Jim Graham + * @since 1.2 + */ +public abstract class Dimension2D implements Cloneable { + + /** + * This is an abstract class that cannot be instantiated directly. + * Type-specific implementation subclasses are available for + * instantiation and provide a number of formats for storing + * the information necessary to satisfy the various accessor + * methods below. + * + * @see java.awt.Dimension + * @since 1.2 + */ + protected Dimension2D() { + } + + /** + * Returns the width of this {@code Dimension} in double + * precision. + * @return the width of this {@code Dimension}. + * @since 1.2 + */ + public abstract double getWidth(); + + /** + * Returns the height of this {@code Dimension} in double + * precision. + * @return the height of this {@code Dimension}. + * @since 1.2 + */ + public abstract double getHeight(); + + /** + * Sets the size of this {@code Dimension} object to the + * specified width and height. + * This method is included for completeness, to parallel the + * {@link java.awt.Component#getSize getSize} method of + * {@link java.awt.Component}. + * @param width the new width for the {@code Dimension} + * object + * @param height the new height for the {@code Dimension} + * object + * @since 1.2 + */ + public abstract void setSize(double width, double height); + + /** + * Sets the size of this {@code Dimension2D} object to + * match the specified size. + * This method is included for completeness, to parallel the + * {@code getSize} method of {@code Component}. + * @param d the new size for the {@code Dimension2D} + * object + * @since 1.2 + */ + public void setSize(java.awt.geom.Dimension2D d) { + setSize(d.getWidth(), d.getHeight()); + } + + /** + * Creates a new object of the same class as this object. + * + * @return a clone of this instance. + * @exception OutOfMemoryError if there is not enough memory. + * @see java.lang.Cloneable + * @since 1.2 + */ + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(e); + } + } +} diff --git a/poishadow/src/main/java/org/apache/poi/java/awt/image/BufferedImage.java b/poishadow/src/main/java/org/apache/poi/java/awt/image/BufferedImage.java new file mode 100644 index 000000000..f1d9d4de4 --- /dev/null +++ b/poishadow/src/main/java/org/apache/poi/java/awt/image/BufferedImage.java @@ -0,0 +1,47 @@ +package org.apache.poi.java.awt.image; + +/** + * Shadow class for java.awt.image.BufferedImage. Adds some compatibility to systems that do + * not have access to javax.awt. + */ +public class BufferedImage { + /** + * The pixel width of the image. + */ + private final int width; + + /** + * The pixel height of the image. + */ + private final int height; + + /** + * Constructor + * + * @param width The pixel width of the image. + * @param height The pixel height of the image. + */ + public BufferedImage(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * Returns The pixel width of the image. + * + * @return The pixel width of the image. + */ + public int getWidth() { + return width; + } + + /** + * Returns The pixel height of the image. + * + * @return The pixel height of the image. + */ + public int getHeight() { + return height; + } + +} diff --git a/poishadow/src/main/java/org/apache/poi/javax/imageio/ImageIO.java b/poishadow/src/main/java/org/apache/poi/javax/imageio/ImageIO.java new file mode 100644 index 000000000..d74788516 --- /dev/null +++ b/poishadow/src/main/java/org/apache/poi/javax/imageio/ImageIO.java @@ -0,0 +1,71 @@ +package org.apache.poi.javax.imageio; + + +import org.apache.poi.javax.imageio.stream.ImageInputStream; +import org.dstadler.compat.BmpReader; +import org.dstadler.compat.ImageInputStreamImpl; +import org.dstadler.compat.JpgReader; +import org.dstadler.compat.PngReader; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Shadow class for {@link javax.imageio.ImageIO}. Adds some compatibility to systems that do + * not have access to javax.imageio. + */ +public final class ImageIO { + + /** + * List of available {@link ImageReader}s. + */ + private static final List imageReaders = new ArrayList<>(); + + static { + //Initializes the image readers. + imageReaders.add(new PngReader()); + imageReaders.add(new JpgReader()); + imageReaders.add(new BmpReader()); + } + + /** + * Creates an {@link ImageInputStreamImpl} that wraps the given inputStream into + * a {@link java.io.BufferedInputStream}. + * + * @param input must be a {@link InputStream}. + * @return an {@link ImageInputStreamImpl} that wraps the given inputStream into + * a {@link java.io.BufferedInputStream}. + * @throws IOException If an error occurs while reading. + */ + public static ImageInputStream createImageInputStream(Object input) throws IOException { + if (!(input instanceof InputStream)) { + throw new IllegalArgumentException("input is not an instance of InputStream"); + } + return new ImageInputStreamImpl((InputStream) input); + } + + /** + * Returns a iterator of usable {@link ImageReader}s based on the given input. + * @param input Must be a {@link ImageInputStream}. + * @return a iterator of usable {@link ImageReader}s based on the given input. + */ + public static Iterator getImageReaders(Object input) { + if (!(input instanceof ImageInputStream)) { + throw new IllegalArgumentException("input is not an instance of ImageInputStream"); + } + + for (ImageReader reader : imageReaders) { + if (reader.canRead(((ImageInputStream) input).getInputStream())) { + ArrayList returnList = new ArrayList<>(); + returnList.add(reader); + return returnList.iterator(); + } + } + + return Collections.emptyIterator(); + } +} diff --git a/poishadow/src/main/java/org/apache/poi/javax/imageio/ImageReader.java b/poishadow/src/main/java/org/apache/poi/javax/imageio/ImageReader.java new file mode 100644 index 000000000..fe468d8b0 --- /dev/null +++ b/poishadow/src/main/java/org/apache/poi/javax/imageio/ImageReader.java @@ -0,0 +1,131 @@ +package org.apache.poi.javax.imageio; + +import org.apache.commons.io.IOUtils; +import org.apache.poi.java.awt.image.BufferedImage; +import org.apache.poi.javax.imageio.metadata.IIOMetadata; +import org.apache.poi.javax.imageio.stream.ImageInputStream; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.OptionalInt; +import java.util.stream.IntStream; + +/** + * Shadow class for {@link javax.imageio.ImageReader}. Adds some compatibility to systems that do + * not have access to javax.imageio. + */ +public abstract class ImageReader { + /** + * The buffer used when reading. + */ + protected ByteBuffer buffer; + + /** + * Sets the input stream. + * + * @param input Must be a {@link ImageInputStream}. + */ + public void setInput(Object input) { + if (!(input instanceof ImageInputStream)) { + throw new IllegalArgumentException("input is not an instance of InputStream"); + } + ImageInputStream imageInputStream = (ImageInputStream) input; + + try { + if (!canRead(imageInputStream.getInputStream())) { + throw new IllegalArgumentException("Incorrect input type!"); + } + + this.buffer = ByteBuffer.wrap(IOUtils.toByteArray(imageInputStream.getInputStream())); + } catch (IOException e) { + throw new IllegalArgumentException("Incorrect input type!"); + } + } + + /** + * Gets the first index of the given byte[] within the {@link ByteBuffer}. + * + * @param buf The {@link ByteBuffer} to search. + * @param b the byte array to search for. + * @return empty if not found, otherwise the first index found. + */ + protected static OptionalInt indexOf(ByteBuffer buf, byte[] b) { + if (b.length == 0) { + return OptionalInt.empty(); + } + return IntStream.rangeClosed(buf.position(), buf.limit() - b.length) + .filter(i -> IntStream.range(0, b.length).allMatch(j -> buf.get(i + j) == b[j])) + .findFirst(); + } + + /** + * Gets the indices of the given byte[] within the {@link ByteBuffer}. + * + * @param buf The {@link ByteBuffer} to search. + * @param b the byte array to search for. + * @return empty int array if not found, otherwise an int array of all found indices. + */ + protected static int[] indicesOf(ByteBuffer buf, byte[] b) { + if (b.length == 0) { + return new int[0]; + } + return IntStream.rangeClosed(buf.position(), buf.limit() - b.length) + .filter(i -> IntStream.range(0, b.length).allMatch(j -> buf.get(i + j) == b[j])) + .toArray(); + } + + /** + * Returns true if the magic bytes given match the same starting bytes in the given inputStream. + * + * @param magic the magic bytes to match. + * @param inputStream The input stream to use. + * @return true if the magic bytes given match the same starting bytes in the given inputStream. + */ + protected boolean magicBytesPresent(byte[] magic, BufferedInputStream inputStream) { + byte[] bytes = new byte[magic.length]; + inputStream.mark(magic.length); + + try { + inputStream.read(bytes, 0, magic.length); + inputStream.reset(); + } catch (IOException e) { + return false; + } + + return Arrays.equals(bytes, magic); + } + + /** + * Returns a {@link BufferedImage} by reading the buffer. + * + * @param imageIndex this is ignored. + * @return a {@link BufferedImage} by reading the buffer. + */ + public abstract BufferedImage read(int imageIndex); + + /** + * Returns true if the inherited class can read this input stream as a specific image. + * + * @param inputStream The input stream to use. + * @return true if the inherited class can read this input stream as a specific image. + */ + protected abstract boolean canRead(BufferedInputStream inputStream); + + /** + * Returns a {@link IIOMetadata} by reading the buffer. + * + * @param imageIndex ignored. + * @return a {@link IIOMetadata} by reading the buffer. + * @throws IOException If an error occurs during reading. + */ + public abstract IIOMetadata getImageMetadata(int imageIndex) throws IOException; + + /** + * Disposes of any used resources. + */ + public void dispose() { + buffer = null; + } +} diff --git a/poishadow/src/main/java/org/apache/poi/javax/imageio/metadata/IIOMetadata.java b/poishadow/src/main/java/org/apache/poi/javax/imageio/metadata/IIOMetadata.java new file mode 100644 index 000000000..8a3fa4e13 --- /dev/null +++ b/poishadow/src/main/java/org/apache/poi/javax/imageio/metadata/IIOMetadata.java @@ -0,0 +1,17 @@ +package org.apache.poi.javax.imageio.metadata; + +import org.w3c.dom.Node; + +/** + * Shadow class for {@link javax.imageio.metadata.IIOMetadata}. Adds some compatibility to systems + * that do not have access to javax.imageio. + */ +public abstract class IIOMetadata { + /** + * Returns an {@link org.w3c.dom.Element} describing metadata for images. + * + * @param formatName the desired metadata format. + * @return an {@link org.w3c.dom.Element} describing metadata for images. + */ + public abstract Node getAsTree(String formatName); +} diff --git a/poishadow/src/main/java/org/apache/poi/javax/imageio/stream/ImageInputStream.java b/poishadow/src/main/java/org/apache/poi/javax/imageio/stream/ImageInputStream.java new file mode 100644 index 000000000..6f09f01d2 --- /dev/null +++ b/poishadow/src/main/java/org/apache/poi/javax/imageio/stream/ImageInputStream.java @@ -0,0 +1,18 @@ +package org.apache.poi.javax.imageio.stream; + +import java.io.BufferedInputStream; +import java.io.Closeable; + +/** + * The shadow interface for {@link javax.imageio.stream.ImageInputStream}. Adds some compatibility + * to systems that do not have access to javax.imageio. + */ +public interface ImageInputStream extends Closeable { + + /** + * Returns the input stream to use + * + * @return the input stream to use + */ + BufferedInputStream getInputStream(); +} diff --git a/poishadow/src/main/java/org/dstadler/compat/BmpReader.java b/poishadow/src/main/java/org/dstadler/compat/BmpReader.java new file mode 100644 index 000000000..edd17a574 --- /dev/null +++ b/poishadow/src/main/java/org/dstadler/compat/BmpReader.java @@ -0,0 +1,61 @@ +package org.dstadler.compat; + +import org.apache.poi.java.awt.image.BufferedImage; +import org.apache.poi.javax.imageio.ImageReader; +import org.apache.poi.javax.imageio.metadata.IIOMetadata; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.nio.ByteOrder; + +/** + * Image reader for BMP files. + */ +public class BmpReader extends ImageReader { + /** + * The BMP's magic bytes + */ + private static final byte[] BMP_MAGIC_BYTES = new byte[]{0x42, 0x4D}; + + /** + * {@inheritDoc} + */ + @Override + public BufferedImage read(int imageIndex) { + buffer.order(ByteOrder.LITTLE_ENDIAN); + + //set to with offset which is followed by height + buffer.position(0x12); + int width = buffer.getInt(); + int height = buffer.getInt(); + buffer.rewind(); + + return new BufferedImage(width, height); + } + + /** + * Specific for BMP image files.

+ * {@inheritDoc} + */ + @Override + protected boolean canRead(BufferedInputStream inputStream) { + return magicBytesPresent(BMP_MAGIC_BYTES, inputStream); + } + + /** + * {@inheritDoc} + */ + @Override + public IIOMetadata getImageMetadata(int imageIndex) throws IOException { + buffer.order(ByteOrder.LITTLE_ENDIAN); + + //set to XpixelsPerM offset which is followed by YpixelsPerM + buffer.position(0x26); + int yPixelsPerMeter = buffer.getInt(); + int xPixelsPerMeter = buffer.getInt(); + + buffer.rewind(); + return new IOMetadataImpl(1000.0F / xPixelsPerMeter, + 1000.0F / yPixelsPerMeter); + } +} diff --git a/poishadow/src/main/java/org/dstadler/compat/IOMetadataImpl.java b/poishadow/src/main/java/org/dstadler/compat/IOMetadataImpl.java new file mode 100644 index 000000000..ff592e75e --- /dev/null +++ b/poishadow/src/main/java/org/dstadler/compat/IOMetadataImpl.java @@ -0,0 +1,67 @@ +package org.dstadler.compat; + +import org.apache.poi.javax.imageio.metadata.IIOMetadata; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** + * The compatibility implementation for {@link IIOMetadata}. + */ +public class IOMetadataImpl extends IIOMetadata { + /** + * The horizontal pixel size + */ + private final Float horizontalPixelSize; + + /** + * The vertical pixel size + */ + private final Float verticalPixelSize; + + /** + * Constructor + * + * @param horizontalPixelSize The horizontal pixel size + * @param verticalPixelSize The vertical pixel size + */ + protected IOMetadataImpl(Float horizontalPixelSize, Float verticalPixelSize) { + this.horizontalPixelSize = horizontalPixelSize; + this.verticalPixelSize = verticalPixelSize; + } + + /** + * Currently only returns horizontal and vertical pixles sizes.

+ * {@inheritDoc} + */ + @Override + public Node getAsTree(String formatName) { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = null; + try { + docBuilder = docFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } + Document doc = docBuilder.newDocument(); + Element rootElement = doc.createElement("Dimension"); + + if (horizontalPixelSize != null) { + Element horizontalElement = doc.createElement("HorizontalPixelSize"); + rootElement.appendChild(horizontalElement); + horizontalElement.setAttribute("value", String.valueOf(horizontalPixelSize)); + } + + if (verticalPixelSize != null) { + Element verticalElement = doc.createElement("VerticalPixelSize"); + rootElement.appendChild(verticalElement); + verticalElement.setAttribute("value", String.valueOf(verticalPixelSize)); + } + + return rootElement; + } +} diff --git a/poishadow/src/main/java/org/dstadler/compat/ImageInputStreamImpl.java b/poishadow/src/main/java/org/dstadler/compat/ImageInputStreamImpl.java new file mode 100644 index 000000000..3dd402806 --- /dev/null +++ b/poishadow/src/main/java/org/dstadler/compat/ImageInputStreamImpl.java @@ -0,0 +1,41 @@ +package org.dstadler.compat; + +import org.apache.poi.javax.imageio.stream.ImageInputStream; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * The compatibility implementation for {@link ImageInputStream}. + */ +public class ImageInputStreamImpl implements ImageInputStream { + /** + * The input stream. + */ + private final BufferedInputStream stream; + + /** + * Constructor + * + * @param inputStream the input stream to use. + */ + public ImageInputStreamImpl(InputStream inputStream){ + stream = new BufferedInputStream(inputStream); + } + + /** + * {@inheritDoc} + */ + public BufferedInputStream getInputStream() { + return stream; + } + + /** + * {@inheritDoc} + */ + @Override + public void close() throws IOException { + stream.close(); + } +} diff --git a/poishadow/src/main/java/org/dstadler/compat/JpgReader.java b/poishadow/src/main/java/org/dstadler/compat/JpgReader.java new file mode 100644 index 000000000..1e151056a --- /dev/null +++ b/poishadow/src/main/java/org/dstadler/compat/JpgReader.java @@ -0,0 +1,85 @@ +package org.dstadler.compat; + +import org.apache.poi.java.awt.image.BufferedImage; +import org.apache.poi.javax.imageio.ImageReader; +import org.apache.poi.javax.imageio.metadata.IIOMetadata; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.util.OptionalInt; + +/** + * Image reader for JPG files. + */ +public class JpgReader extends ImageReader { + /** + * Magic bytes for JFIF JPG files. + */ + private static final byte[] JFIF_MAGIC_BYTES = new byte[]{(byte) 0xFF, (byte) 0xD8, + (byte) 0xFF, (byte) 0xE0}; + + /** + * Magic bytes for EXIF JPG files. + */ + private static final byte[] EXIF_MAGIC_BYTES = new byte[]{(byte) 0xFF, (byte) 0xD8, + (byte) 0xFF, (byte) 0xE1}; + + /** + * {@inheritDoc} + */ + @Override + public BufferedImage read(int imageIndex) { + //Start Of Frame (Baseline DCT), SOFn segment + //Get the last SOF segment since that is what has the size + byte[] sofn = {(byte) 0xFF, (byte) 0xC0}; + int[] indicesOfSof = indicesOf(buffer, sofn); + int lastSofn = indicesOfSof[indicesOfSof.length - 1]; + + buffer.position(lastSofn + 5); + int height = buffer.getShort(); + int width = buffer.getShort(); + buffer.rewind(); + + return new BufferedImage(width, height); + } + + /** + * Specific for JPG image files.

+ * {@inheritDoc} + */ + @Override + protected boolean canRead(BufferedInputStream inputStream) { + return magicBytesPresent(JFIF_MAGIC_BYTES, inputStream) + || magicBytesPresent(EXIF_MAGIC_BYTES, inputStream); + } + + /** + * {@inheritDoc} + */ + @Override + public IIOMetadata getImageMetadata(int var1) throws IOException { + //JFIF extension APP0 segment + byte[] app0 = {(byte) 0xFF, (byte) 0xE0}; + OptionalInt indexOfApp0 = indexOf(buffer, app0); + Float horizontal = null; + Float vertical = null; + + if (indexOfApp0.isPresent()) { + buffer.position(indexOfApp0.getAsInt() + 11); + int unit = Byte.toUnsignedInt(buffer.get()); + int xDensity = buffer.getShort(); + int yDensity = buffer.getShort(); + + // Pixel sizes + if (unit != 0) { + // 1 == dpi, 2 == dpc + float scale = (unit == 1) ? 25.4F : 10.0F; + horizontal = scale / xDensity; + vertical = scale / yDensity; + } + } + + buffer.rewind(); + return new IOMetadataImpl(horizontal, vertical); + } +} diff --git a/poishadow/src/main/java/org/dstadler/compat/PngReader.java b/poishadow/src/main/java/org/dstadler/compat/PngReader.java new file mode 100644 index 000000000..a489fc61b --- /dev/null +++ b/poishadow/src/main/java/org/dstadler/compat/PngReader.java @@ -0,0 +1,77 @@ +package org.dstadler.compat; + +import org.apache.poi.java.awt.image.BufferedImage; +import org.apache.poi.javax.imageio.ImageReader; +import org.apache.poi.javax.imageio.metadata.IIOMetadata; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.util.OptionalInt; + +/** + * Image reader for PNG files. + */ +public class PngReader extends ImageReader { + /** + * Magic bytes for a PNG file. + */ + private final static byte[] PNG_MAGIC_BYTES = new byte[]{(byte) (137 & 0xFF), + (byte) (80 & 0xFF), (byte) (78 & 0xFF), (byte) (71 & 0xFF), (byte) (13 & 0xFF), + (byte) (10 & 0xFF), (byte) (26 & 0xFF), (byte) (10 & 0xFF)}; + + /** + * {@inheritDoc} + */ + @Override + public BufferedImage read(int imageIndex) { + //Move position to PNG header; 11.2.2 IHDR Image header + buffer.position(16); + int width = buffer.getInt(); + int height = buffer.getInt(); + buffer.rewind(); + + return new BufferedImage(width, height); + } + + /** + * Specific for PNG image files.

+ * {@inheritDoc} + */ + @Override + protected boolean canRead(BufferedInputStream inputStream) { + return magicBytesPresent(PNG_MAGIC_BYTES, inputStream); + } + + /** + * {@inheritDoc} + */ + @Override + public IIOMetadata getImageMetadata(int var1) throws IOException { + //Find the pHYs chunk; 11.3.5.3 pHYs Physical pixel dimensions + byte[] physChunk = {'p', 'H', 'Y', 's'}; + OptionalInt indexOfPhys = indexOf(buffer, physChunk); + + int ppuX = 0; + int ppuY = 0; + //If the pHYs chunk is present, use the pixel sizes + if (indexOfPhys.isPresent()) { + buffer.position(indexOfPhys.getAsInt() + physChunk.length); + int ppuXValue = buffer.getInt(); + int ppuYValue = buffer.getInt(); + + //The following values are defined for the unit specifier: + // 0 = unit is unknown + // 1 = unit is the meter + int unit = Byte.toUnsignedInt(buffer.get()); + if (unit == 1) { + ppuX = ppuXValue; + ppuY = ppuYValue; + } + } + + buffer.rewind(); + Float horizontal = ppuX == 0 ? null : 1000.0F / ppuX; + Float vertical = ppuY == 0 ? null : 1000.0F / ppuY; + return new IOMetadataImpl(horizontal, vertical); + } +} diff --git a/poitest/src/main/java/org/dstadler/poiandroidtest/poitest/MainActivity.java b/poitest/src/main/java/org/dstadler/poiandroidtest/poitest/MainActivity.java index ca82e0bb6..95b398330 100644 --- a/poitest/src/main/java/org/dstadler/poiandroidtest/poitest/MainActivity.java +++ b/poitest/src/main/java/org/dstadler/poiandroidtest/poitest/MainActivity.java @@ -230,10 +230,28 @@ private void setupContent() throws IOException { DummyContent.addItem(new DummyItemWithCode("c" + (idCount++), "Test Issue 75 - Crashes!!", () -> { try (InputStream pictureStream = getResources().openRawResource(R.raw.logo); OutputStream outputStream = openFileOutput("issue75.xlsx", Context.MODE_PRIVATE)) { - TestIssue75.saveExcelFile(pictureStream, outputStream); + TestIssue75.saveExcelFile(pictureStream, outputStream, Workbook.PICTURE_TYPE_JPEG); } - return "Issue 75 - XMLSlideShow constructed successfully"; + return "Issue 75 - XSSFWorkbook constructed successfully"; + })); + + DummyContent.addItem(new DummyItemWithCode("c" + (idCount++), "Test resizing PNG file", () -> { + try (InputStream pictureStream = getResources().openRawResource(R.raw.logo_png); + OutputStream outputStream = openFileOutput("resizePng.xlsx", Context.MODE_PRIVATE)) { + TestIssue75.saveExcelFile(pictureStream, outputStream, Workbook.PICTURE_TYPE_PNG); + } + + return "Test resizing PNG file - XSSFWorkbook constructed successfully"; + })); + + DummyContent.addItem(new DummyItemWithCode("c" + (idCount++), "Test resizing BMP file", () -> { + try (InputStream pictureStream = getResources().openRawResource(R.raw.logo_bmp); + OutputStream outputStream = openFileOutput("resizeBmp.xlsx", Context.MODE_PRIVATE)) { + TestIssue75.saveExcelFile(pictureStream, outputStream, Workbook.PICTURE_TYPE_DIB); + } + + return "Test resizing BMP file - XSSFWorkbook constructed successfully"; })); // reproducer for https://github.com/centic9/poi-on-android/issues/84 diff --git a/poitest/src/main/java/org/dstadler/poiandroidtest/poitest/test/TestIssue75.java b/poitest/src/main/java/org/dstadler/poiandroidtest/poitest/test/TestIssue75.java index 69e3d7afe..9afa60067 100644 --- a/poitest/src/main/java/org/dstadler/poiandroidtest/poitest/test/TestIssue75.java +++ b/poitest/src/main/java/org/dstadler/poiandroidtest/poitest/test/TestIssue75.java @@ -15,7 +15,7 @@ import java.io.OutputStream; public class TestIssue75 { - public static void saveExcelFile(InputStream picture, OutputStream outputStream) throws IOException { + public static void saveExcelFile(InputStream picture, OutputStream outputStream, int workBookPictureType) throws IOException { XSSFWorkbook workbook = new XSSFWorkbook(); XSSFCreationHelper helper = workbook.getCreationHelper(); @@ -24,7 +24,7 @@ public static void saveExcelFile(InputStream picture, OutputStream outputStream) XSSFClientAnchor anchor = helper.createClientAnchor(); anchor.setAnchorType( ClientAnchor.AnchorType.MOVE_AND_RESIZE ); - int pictureIndex = workbook.addPicture(IOUtils.toByteArray(picture), Workbook.PICTURE_TYPE_JPEG); + int pictureIndex = workbook.addPicture(IOUtils.toByteArray(picture), workBookPictureType); anchor.setCol1( 0 ); anchor.setRow1( 2); diff --git a/poitest/src/main/res/raw/logo_bmp.bmp b/poitest/src/main/res/raw/logo_bmp.bmp new file mode 100644 index 000000000..ffac7479f Binary files /dev/null and b/poitest/src/main/res/raw/logo_bmp.bmp differ diff --git a/poitest/src/main/res/raw/logo_png.png b/poitest/src/main/res/raw/logo_png.png new file mode 100644 index 000000000..94fcc99dc Binary files /dev/null and b/poitest/src/main/res/raw/logo_png.png differ