\r
package org.apache.poi.sl.draw;\r
\r
+import java.awt.AlphaComposite;\r
import java.awt.Dimension;\r
import java.awt.Graphics;\r
import java.awt.Graphics2D;\r
\r
/**\r
* Read the image data via ImageIO and optionally try to workaround metadata errors.\r
- * The resulting image is of image image type {@link BufferedImage#TYPE_INT_ARGB}\r
+ * The resulting image is of image type {@link BufferedImage#TYPE_INT_ARGB}\r
*\r
* @param data the data stream\r
* @param contentType the content type\r
private static BufferedImage readImage(InputStream data, String contentType) throws IOException {\r
IOException lastException = null;\r
BufferedImage img = null;\r
+ if (data.markSupported()) {\r
+ data.mark(data.available());\r
+ }\r
+ \r
// currently don't use FileCacheImageInputStream,\r
// because of the risk of filling the file handles (see #59166)\r
ImageInputStream iis = new MemoryCacheImageInputStream(data);\r
ImageReader reader = iter.next();\r
ImageReadParam param = reader.getDefaultReadParam();\r
// 0:default mode, 1:fallback mode\r
- for (int mode=0; img==null && mode<2; mode++) {\r
- iis.reset();\r
+ for (int mode=0; img==null && mode<3; mode++) {\r
+ lastException = null;\r
+ try {\r
+ iis.reset();\r
+ } catch (IOException e) {\r
+ if (data.markSupported()) {\r
+ data.reset();\r
+ data.mark(data.available());\r
+ iis.close();\r
+ iis = new MemoryCacheImageInputStream(data);\r
+ } else {\r
+ // can't restore the input stream, so we need to stop processing here\r
+ lastException = e;\r
+ break;\r
+ }\r
+ }\r
iis.mark();\r
\r
- if (mode == 1) {\r
- // fallback mode for invalid image band metadata\r
- // see http://stackoverflow.com/questions/10416378\r
- Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);\r
- while (imageTypes.hasNext()) {\r
- ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();\r
- int bufferedImageType = imageTypeSpecifier.getBufferedImageType();\r
- if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) {\r
- param.setDestinationType(imageTypeSpecifier);\r
+ try {\r
+ \r
+ switch (mode) {\r
+ case 0:\r
+ reader.setInput(iis, false, true);\r
+ img = reader.read(0, param);\r
+ break;\r
+ case 1: {\r
+ // try to load picture in gray scale mode\r
+ // fallback mode for invalid image band metadata\r
+ // see http://stackoverflow.com/questions/10416378\r
+ Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);\r
+ while (imageTypes.hasNext()) {\r
+ ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();\r
+ int bufferedImageType = imageTypeSpecifier.getBufferedImageType();\r
+ if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) {\r
+ param.setDestinationType(imageTypeSpecifier);\r
+ break;\r
+ }\r
+ }\r
+ reader.setInput(iis, false, true);\r
+ img = reader.read(0, param);\r
+ break;\r
+ }\r
+ case 2: {\r
+ // try to load truncated pictures by supplying a BufferedImage\r
+ // and use the processed data up till the point of error\r
+ reader.setInput(iis, false, true);\r
+ int height = reader.getHeight(0);\r
+ int width = reader.getWidth(0);\r
+ \r
+ Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);\r
+ if (imageTypes.hasNext()) {\r
+ ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();\r
+ img = imageTypeSpecifier.createBufferedImage(width, height);\r
+ param.setDestination(img);\r
+ } else {\r
+ lastException = new IOException("unable to load even a truncated version of the image.");\r
+ break;\r
+ }\r
+\r
+ try {\r
+ reader.read(0, param);\r
+ } finally {\r
+ if (img.getType() != BufferedImage.TYPE_INT_ARGB) {\r
+ int y = findTruncatedBlackBox(img, width, height);\r
+ if (y < height) {\r
+ BufferedImage argbImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\r
+ Graphics2D g = argbImg.createGraphics();\r
+ g.clipRect(0, 0, width, y);\r
+ g.drawImage(img, 0, 0, null);\r
+ g.dispose();\r
+ img.flush();\r
+ img = argbImg;\r
+ }\r
+ }\r
+ } \r
break;\r
}\r
}\r
- }\r
\r
- try {\r
- reader.setInput(iis, false, true);\r
- img = reader.read(0, param);\r
} catch (IOException e) {\r
- lastException = e;\r
+ if (mode < 2) {\r
+ lastException = e;\r
+ }\r
} catch (RuntimeException e) {\r
- lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e);\r
+ if (mode < 2) {\r
+ lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e);\r
+ }\r
}\r
}\r
reader.dispose();\r
return img;\r
}\r
\r
+ private static int findTruncatedBlackBox(BufferedImage img, int width, int height) {\r
+ // scan through the image to find the black box after the truncated data\r
+ int h = height-1;\r
+ for (; h > 0; h--) {\r
+ for (int w = width-1; w > 0; w-=width/10) {\r
+ int p = img.getRGB(w, h);\r
+ if (p != 0xff000000) {\r
+ return h+1;\r
+ }\r
+ }\r
+ }\r
+ return 0;\r
+ }\r
+ \r
+ \r
@Override\r
public BufferedImage getImage() {\r
return img;\r
import java.util.zip.InflaterInputStream;
import org.apache.poi.hslf.exceptions.HSLFException;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
import org.apache.poi.util.Units;
/**
* Represents Macintosh PICT picture data.
*/
public final class PICT extends Metafile {
+ private static POILogger LOG = POILogFactory.getLogger(PICT.class);
+
public static class NativeHeader {
/**
* skip the first 512 bytes - they are MAC specific crap
}
private byte[] read(byte[] data, int pos) throws IOException {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream bis = new ByteArrayInputStream(data);
Header header = new Header();
header.read(data, pos);
bis.skip(pos + header.getSize());
- InflaterInputStream inflater = new InflaterInputStream( bis );
byte[] chunk = new byte[4096];
- int count;
- while ((count = inflater.read(chunk)) >=0 ) {
- out.write(chunk,0,count);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(header.getWmfSize());
+ InflaterInputStream inflater = new InflaterInputStream( bis );
+ try {
+ int count;
+ while ((count = inflater.read(chunk)) >=0 ) {
+ out.write(chunk,0,count);
+ // PICT zip-stream can be erroneous, so we clear the array to determine
+ // the maximum of read bytes, after the inflater crashed
+ bytefill(chunk, (byte)0);
+ }
+ } catch (Exception e) {
+ int lastLen;
+ for (lastLen=chunk.length-1; lastLen>=0 && chunk[lastLen] == 0; lastLen--);
+ if (++lastLen > 0) {
+ if (header.getWmfSize() > out.size()) {
+ // sometimes the wmfsize is smaller than the amount of already successfully read bytes
+ // in this case we take the lastLen as-is, otherwise we truncate it to the given size
+ lastLen = Math.min(lastLen, header.getWmfSize() - out.size());
+ }
+ out.write(chunk,0,lastLen);
+ }
+ // End of picture marker for PICT is 0x00 0xFF
+ LOG.log(POILogger.ERROR, "PICT zip-stream is invalid, read as much as possible. Uncompressed length of header: "+header.getWmfSize()+" / Read bytes: "+out.size(), e);
+ } finally {
+ inflater.close();
}
- inflater.close();
return out.toByteArray();
}
throw new IllegalArgumentException(signature+" is not a valid instance/signature value for PICT");
}
}
+
+
+ /*
+ * initialize a smaller piece of the array and use the System.arraycopy
+ * call to fill in the rest of the array in an expanding binary fashion
+ */
+ private static void bytefill(byte[] array, byte value) {
+ // http://stackoverflow.com/questions/9128737/fastest-way-to-set-all-values-of-an-array
+ int len = array.length;
+
+ if (len > 0){
+ array[0] = value;
+ }
+
+ for (int i = 1; i < len; i += i) {
+ System.arraycopy(array, 0, array, i, ((len - i) < i) ? (len - i) : i);
+ }
+ }
}