Index: src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java =================================================================== --- src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java (revision 2701) +++ src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java (working copy) @@ -18,6 +18,7 @@ package org.apache.poi.hssf.usermodel; import org.apache.poi.ddf.EscherBitmapBlip; +import org.apache.poi.ddf.EscherBlipRecord; /** * Represents binary data stored in the file. Eg. A GIF, JPEG etc... @@ -39,14 +40,14 @@ /** * Underlying escher blip record containing the bitmap data. */ - private EscherBitmapBlip blip; + private EscherBlipRecord blip; /** * Constructs a picture object. * * @param blip the underlying blip record containing the bitmap data. */ - HSSFPictureData( EscherBitmapBlip blip ) + HSSFPictureData( EscherBlipRecord blip ) { this.blip = blip; } Index: src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java =================================================================== --- src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java (revision 2701) +++ src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java (working copy) @@ -1361,10 +1361,10 @@ if (escherRecord instanceof EscherBSERecord) { EscherBlipRecord blip = ((EscherBSERecord) escherRecord).getBlipRecord(); - if (blip instanceof EscherBitmapBlip) + if (blip != null) { // TODO: Some kind of structure. - pictures.add(new HSSFPictureData((EscherBitmapBlip) blip)); + pictures.add(new HSSFPictureData(blip)); } } Index: src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java =================================================================== --- src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java (revision 2701) +++ src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java (working copy) @@ -81,6 +81,12 @@ { r = new EscherBitmapBlip(); } + else if (header.getRecordId() == EscherMetafileBlip.RECORD_ID_EMF || + header.getRecordId() == EscherMetafileBlip.RECORD_ID_WMF || + header.getRecordId() == EscherMetafileBlip.RECORD_ID_PICT) + { + r = new EscherMetafileBlip(); + } else { r = new EscherBlipRecord(); Index: src/java/org/apache/poi/ddf/EscherBitmapBlip.java =================================================================== --- src/java/org/apache/poi/ddf/EscherBitmapBlip.java (revision 2701) +++ src/java/org/apache/poi/ddf/EscherBitmapBlip.java (working copy) @@ -119,16 +119,6 @@ this.field_2_marker = field_2_marker; } - public byte[] getPicturedata() - { - return field_pictureData; - } - - public void setPictureData(byte[] pictureData) - { - field_pictureData = pictureData; - } - public String toString() { String nl = System.getProperty( "line.separator" ); Index: src/java/org/apache/poi/ddf/EscherBlipRecord.java =================================================================== --- src/java/org/apache/poi/ddf/EscherBlipRecord.java (revision 2701) +++ src/java/org/apache/poi/ddf/EscherBlipRecord.java (working copy) @@ -103,6 +103,16 @@ return "Blip"; } + public byte[] getPicturedata() + { + return field_pictureData; + } + + public void setPictureData(byte[] pictureData) + { + field_pictureData = pictureData; + } + public String toString() { String nl = System.getProperty( "line.separator" ); Index: src/java/org/apache/poi/ddf/EscherMetafileBlip.java =================================================================== --- src/java/org/apache/poi/ddf/EscherMetafileBlip.java (revision 0) +++ src/java/org/apache/poi/ddf/EscherMetafileBlip.java (revision 2703) @@ -0,0 +1,277 @@ +/* +* 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.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.InflaterInputStream; + +/** + * @author Daniel Noll + * @version $Id$ + */ +public class EscherMetafileBlip + extends EscherBlipRecord +{ + private static final POILogger log = POILogFactory.getLogger(EscherMetafileBlip.class); + + // TODO: What are the other two types? + public static final short RECORD_ID_EMF = (short) 0xF018 + 2; + public static final short RECORD_ID_WMF = (short) 0xF018 + 2; + public static final short RECORD_ID_PICT = (short) 0xF018 + 2; + + private static final int HEADER_SIZE = 8; + + private byte[] field_1_UID; + private int field_2_cb; + private int field_3_rcBounds_x1; + private int field_3_rcBounds_y1; + private int field_3_rcBounds_x2; + private int field_3_rcBounds_y2; + private int field_4_ptSize_w; + private int field_4_ptSize_h; + private int field_5_cbSave; + private byte field_6_fCompression; + private byte field_7_fFilter; + + private byte[] raw_pictureData; + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into data. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesAfterHeader = readHeader( data, offset ); + int pos = offset + HEADER_SIZE; + + field_1_UID = new byte[16]; + System.arraycopy( data, pos, field_1_UID, 0, 16 ); pos += 16; + field_2_cb = LittleEndian.getInt( data, pos ); pos += 4; + field_3_rcBounds_x1 = LittleEndian.getInt( data, pos ); pos += 4; + field_3_rcBounds_y1 = LittleEndian.getInt( data, pos ); pos += 4; + field_3_rcBounds_x2 = LittleEndian.getInt( data, pos ); pos += 4; + field_3_rcBounds_y2 = LittleEndian.getInt( data, pos ); pos += 4; + field_4_ptSize_w = LittleEndian.getInt( data, pos ); pos += 4; + field_4_ptSize_h = LittleEndian.getInt( data, pos ); pos += 4; + field_5_cbSave = LittleEndian.getInt( data, pos ); pos += 4; + field_6_fCompression = data[pos]; pos++; + field_7_fFilter = data[pos]; pos++; + + raw_pictureData = new byte[field_5_cbSave]; + System.arraycopy( data, pos, raw_pictureData, 0, field_5_cbSave ); + + // 0 means DEFLATE compression + // 0xFE means no compression + if (field_6_fCompression == 0) + { + field_pictureData = inflatePictureData(raw_pictureData); + } + else + { + field_pictureData = raw_pictureData; + } + + return bytesAfterHeader + HEADER_SIZE; + } + + /** + * Serializes the record to an existing byte array. + * + * @param offset the offset within the byte array + * @param data the data array to serialize to + * @param listener a listener for begin and end serialization events. This + * is useful because the serialization is + * hierarchical/recursive and sometimes you need to be able + * break into that. + * @return the number of bytes written. + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize(offset, getRecordId(), this); + + int pos = offset; + LittleEndian.putShort( data, pos, getOptions() ); pos += 2; + LittleEndian.putShort( data, pos, getRecordId() ); pos += 2; + LittleEndian.putInt( data, getRecordSize() - HEADER_SIZE ); pos += 4; + + System.arraycopy( field_1_UID, 0, data, pos, 16 ); pos += 16; + LittleEndian.putInt( data, pos, field_2_cb ); pos += 4; + LittleEndian.putInt( data, pos, field_3_rcBounds_x1 ); pos += 4; + LittleEndian.putInt( data, pos, field_3_rcBounds_y1 ); pos += 4; + LittleEndian.putInt( data, pos, field_3_rcBounds_x2 ); pos += 4; + LittleEndian.putInt( data, pos, field_3_rcBounds_y2 ); pos += 4; + LittleEndian.putInt( data, pos, field_4_ptSize_w ); pos += 4; + LittleEndian.putInt( data, pos, field_4_ptSize_h ); pos += 4; + LittleEndian.putInt( data, pos, field_5_cbSave ); pos += 4; + data[pos] = field_6_fCompression; pos++; + data[pos] = field_7_fFilter; pos++; + + System.arraycopy( raw_pictureData, 0, data, pos, raw_pictureData.length ); + + listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this); + return HEADER_SIZE + 16 + 1 + raw_pictureData.length; + } + + /** + * Decompresses the provided data, returning the inflated result. + * + * @param data the deflated picture data. + * @return the inflated picture data. + */ + private static byte[] inflatePictureData(byte[] data) + { + try + { + InflaterInputStream in = new InflaterInputStream( + new ByteArrayInputStream( data ) ); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int readBytes; + while ((readBytes = in.read(buf)) > 0) + { + out.write(buf, 0, readBytes); + } + return out.toByteArray(); + } + catch ( IOException e ) + { + log.log(POILogger.INFO, "Possibly corrupt compression or non-compressed data", e); + return data; + } + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + 50 + raw_pictureData.length; + } + + public byte[] getUID() + { + return field_1_UID; + } + + public void setUID( byte[] field_1_UID ) + { + this.field_1_UID = field_1_UID; + } + + public int getUncompressedSize() + { + return field_2_cb; + } + + public void setUncompressedSize(int uncompressedSize) + { + field_2_cb = uncompressedSize; + } + + public Rectangle getBounds() + { + return new Rectangle(field_3_rcBounds_x1, + field_3_rcBounds_y1, + field_3_rcBounds_x2 - field_3_rcBounds_x1, + field_3_rcBounds_y2 - field_3_rcBounds_y1); + } + + public void setBounds(Rectangle bounds) + { + field_3_rcBounds_x1 = bounds.x; + field_3_rcBounds_y1 = bounds.y; + field_3_rcBounds_x2 = bounds.x + bounds.width; + field_3_rcBounds_y2 = bounds.y + bounds.height; + } + + public Dimension getSizeEMU() + { + return new Dimension(field_4_ptSize_w, field_4_ptSize_h); + } + + public void setSizeEMU(Dimension sizeEMU) + { + field_4_ptSize_w = sizeEMU.width; + field_4_ptSize_h = sizeEMU.height; + } + + public int getCompressedSize() + { + return field_5_cbSave; + } + + public void setCompressedSize(int compressedSize) + { + field_5_cbSave = compressedSize; + } + + public boolean isCompressed() + { + return (field_6_fCompression == 0); + } + + public void setCompressed(boolean compressed) + { + field_6_fCompression = compressed ? 0 : (byte)0xFE; + } + + // filtering is always 254 according to available docs, so no point giving it a setter method. + + public String toString() + { + String nl = System.getProperty( "line.separator" ); + + String extraData; + ByteArrayOutputStream b = new ByteArrayOutputStream(); + try + { + HexDump.dump( this.field_pictureData, 0, b, 0 ); + extraData = b.toString(); + } + catch ( Exception e ) + { + extraData = e.toString(); + } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex( getRecordId() ) + nl + + " Options: 0x" + HexDump.toHex( getOptions() ) + nl + + " UID: 0x" + HexDump.toHex( field_1_UID ) + nl + + " Uncompressed Size: " + HexDump.toHex( field_2_cb ) + nl + + " Bounds: " + getBounds() + nl + + " Size in EMU: " + getSizeEMU() + nl + + " Compressed Size: " + HexDump.toHex( field_5_cbSave ) + nl + + " Compression: " + HexDump.toHex( field_6_fCompression ) + nl + + " Filter: " + HexDump.toHex( field_7_fFilter ) + nl + + " Extra Data:" + nl + extraData; + } + +}