diff --git a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java index c2df75f3b..818b10b10 100644 --- a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java +++ b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java @@ -17,6 +17,7 @@ package org.apache.poi.sl.draw; +import java.awt.AlphaComposite; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; @@ -62,7 +63,7 @@ public class BitmapImageRenderer implements ImageRenderer { /** * Read the image data via ImageIO and optionally try to workaround metadata errors. - * The resulting image is of image image type {@link BufferedImage#TYPE_INT_ARGB} + * The resulting image is of image type {@link BufferedImage#TYPE_INT_ARGB} * * @param data the data stream * @param contentType the content type @@ -72,6 +73,10 @@ public class BitmapImageRenderer implements ImageRenderer { private static BufferedImage readImage(InputStream data, String contentType) throws IOException { IOException lastException = null; BufferedImage img = null; + if (data.markSupported()) { + data.mark(data.available()); + } + // currently don't use FileCacheImageInputStream, // because of the risk of filling the file handles (see #59166) ImageInputStream iis = new MemoryCacheImageInputStream(data); @@ -84,31 +89,93 @@ public class BitmapImageRenderer implements ImageRenderer { ImageReader reader = iter.next(); ImageReadParam param = reader.getDefaultReadParam(); // 0:default mode, 1:fallback mode - for (int mode=0; img==null && mode<2; mode++) { - iis.reset(); + for (int mode=0; img==null && mode<3; mode++) { + lastException = null; + try { + iis.reset(); + } catch (IOException e) { + if (data.markSupported()) { + data.reset(); + data.mark(data.available()); + iis.close(); + iis = new MemoryCacheImageInputStream(data); + } else { + // can't restore the input stream, so we need to stop processing here + lastException = e; + break; + } + } iis.mark(); - if (mode == 1) { - // fallback mode for invalid image band metadata - // see http://stackoverflow.com/questions/10416378 - Iterator imageTypes = reader.getImageTypes(0); - while (imageTypes.hasNext()) { - ImageTypeSpecifier imageTypeSpecifier = imageTypes.next(); - int bufferedImageType = imageTypeSpecifier.getBufferedImageType(); - if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) { - param.setDestinationType(imageTypeSpecifier); + try { + + switch (mode) { + case 0: + reader.setInput(iis, false, true); + img = reader.read(0, param); + break; + case 1: { + // try to load picture in gray scale mode + // fallback mode for invalid image band metadata + // see http://stackoverflow.com/questions/10416378 + Iterator imageTypes = reader.getImageTypes(0); + while (imageTypes.hasNext()) { + ImageTypeSpecifier imageTypeSpecifier = imageTypes.next(); + int bufferedImageType = imageTypeSpecifier.getBufferedImageType(); + if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) { + param.setDestinationType(imageTypeSpecifier); + break; + } + } + reader.setInput(iis, false, true); + img = reader.read(0, param); + break; + } + case 2: { + // try to load truncated pictures by supplying a BufferedImage + // and use the processed data up till the point of error + reader.setInput(iis, false, true); + int height = reader.getHeight(0); + int width = reader.getWidth(0); + + Iterator imageTypes = reader.getImageTypes(0); + if (imageTypes.hasNext()) { + ImageTypeSpecifier imageTypeSpecifier = imageTypes.next(); + img = imageTypeSpecifier.createBufferedImage(width, height); + param.setDestination(img); + } else { + lastException = new IOException("unable to load even a truncated version of the image."); + break; + } + + try { + reader.read(0, param); + } finally { + if (img.getType() != BufferedImage.TYPE_INT_ARGB) { + int y = findTruncatedBlackBox(img, width, height); + if (y < height) { + BufferedImage argbImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = argbImg.createGraphics(); + g.clipRect(0, 0, width, y); + g.drawImage(img, 0, 0, null); + g.dispose(); + img.flush(); + img = argbImg; + } + } + } break; } } - } - try { - reader.setInput(iis, false, true); - img = reader.read(0, param); } catch (IOException e) { - lastException = e; + if (mode < 2) { + lastException = e; + } } catch (RuntimeException e) { - lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e); + if (mode < 2) { + lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e); + } } } reader.dispose(); @@ -140,6 +207,21 @@ public class BitmapImageRenderer implements ImageRenderer { return img; } + private static int findTruncatedBlackBox(BufferedImage img, int width, int height) { + // scan through the image to find the black box after the truncated data + int h = height-1; + for (; h > 0; h--) { + for (int w = width-1; w > 0; w-=width/10) { + int p = img.getRGB(w, h); + if (p != 0xff000000) { + return h+1; + } + } + } + return 0; + } + + @Override public BufferedImage getImage() { return img; diff --git a/src/scratchpad/src/org/apache/poi/hslf/blip/Metafile.java b/src/scratchpad/src/org/apache/poi/hslf/blip/Metafile.java index a04ddc198..4a6c3839a 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/blip/Metafile.java +++ b/src/scratchpad/src/org/apache/poi/hslf/blip/Metafile.java @@ -116,6 +116,10 @@ public abstract class Metafile extends HSLFPictureData { public int getSize(){ return 34; } + + public int getWmfSize() { + return wmfsize; + } } protected static byte[] compress(byte[] bytes, int offset, int length) throws IOException { diff --git a/src/scratchpad/src/org/apache/poi/hslf/blip/PICT.java b/src/scratchpad/src/org/apache/poi/hslf/blip/PICT.java index a5670a126..b0ce968e6 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/blip/PICT.java +++ b/src/scratchpad/src/org/apache/poi/hslf/blip/PICT.java @@ -25,12 +25,16 @@ import java.io.IOException; 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 @@ -122,18 +126,37 @@ public final class PICT extends Metafile { } 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(); } @@ -192,4 +215,22 @@ public final class PICT extends Metafile { 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); + } + } } diff --git a/test-data/slideshow/bug60345_Jankovic_final_Retreat_2002.ppt b/test-data/slideshow/bug60345_Jankovic_final_Retreat_2002.ppt new file mode 100644 index 000000000..5bb52fc5d Binary files /dev/null and b/test-data/slideshow/bug60345_Jankovic_final_Retreat_2002.ppt differ diff --git a/test-data/slideshow/bug60345_paperfigures.ppt b/test-data/slideshow/bug60345_paperfigures.ppt new file mode 100644 index 000000000..6a0455631 Binary files /dev/null and b/test-data/slideshow/bug60345_paperfigures.ppt differ diff --git a/test-data/slideshow/bug60345_suba.ppt b/test-data/slideshow/bug60345_suba.ppt new file mode 100644 index 000000000..9ffea08ae Binary files /dev/null and b/test-data/slideshow/bug60345_suba.ppt differ