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
+ * {@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
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.