FileDocCategorySizeDatePackage
Picture.javaAPI DocApache Poi 3.0.114322Thu May 31 18:45:28 BST 2007org.apache.poi.hwpf.usermodel

Picture

public class Picture extends Object
Represents embedded picture extracted from Word Document
author
Dmitry Romanov

Fields Summary
private static final POILogger
log
static final int
MFPMM_OFFSET
static final int
BLOCK_TYPE_OFFSET
static final int
PICT_HEADER_OFFSET
static final int
UNKNOWN_HEADER_SIZE
public static final byte[]
GIF
public static final byte[]
PNG
public static final byte[]
JPG
public static final byte[]
BMP
public static final byte[]
TIFF
public static final byte[]
TIFF1
public static final byte[]
EMF
public static final byte[]
WMF1
public static final byte[]
WMF2
public static final byte[]
IHDR
public static final byte[]
COMPRESSED1
public static final byte[]
COMPRESSED2
private int
dataBlockStartOfsset
private int
pictureBytesStartOffset
private int
dataBlockSize
private int
size
private byte[]
rawContent
private byte[]
content
private byte[]
_dataStream
private int
aspectRatioX
private int
aspectRatioY
private int
height
private int
width
Constructors Summary
public Picture(int dataBlockStartOfsset, byte[] _dataStream, boolean fillBytes)



        
  
    this._dataStream = _dataStream;
    this.dataBlockStartOfsset = dataBlockStartOfsset;
    this.dataBlockSize = LittleEndian.getInt(_dataStream, dataBlockStartOfsset);
    this.pictureBytesStartOffset = getPictureBytesStartOffset(dataBlockStartOfsset, _dataStream, dataBlockSize);
    this.size = dataBlockSize - (pictureBytesStartOffset - dataBlockStartOfsset);

    if (size<0) {

    }

    this.aspectRatioX = extractAspectRatioX(_dataStream, dataBlockStartOfsset);
    this.aspectRatioY = extractAspectRatioY(_dataStream, dataBlockStartOfsset);
//    this.fileName = extractFileName(dataBlockStartOfsset, _dataStream);
//    if (fileName==null || fileName.length()==0) {
//      fileName = "clipboard";
//    }

    if (fillBytes)
    {
      fillImageContent();
    }
  
Methods Summary
private static intextractAspectRatioX(byte[] _dataStream, int dataBlockStartOffset)

    return LittleEndian.getShort(_dataStream, dataBlockStartOffset+0x20)/10;
  
private static intextractAspectRatioY(byte[] _dataStream, int dataBlockStartOffset)

    return LittleEndian.getShort(_dataStream, dataBlockStartOffset+0x22)/10;
  
private voidfillImageContent()

    byte[] rawContent = getRawContent();

    // HACK: Detect compressed images.  In reality there should be some way to determine
    //       this from the first 32 bytes, but I can't see any similarity between all the
    //       samples I have obtained, nor any similarity in the data block contents.
    if (matchSignature(rawContent, COMPRESSED1, 32) || matchSignature(rawContent, COMPRESSED2, 32))
    {
      try
      {
        InflaterInputStream in = new InflaterInputStream(
          new ByteArrayInputStream(rawContent, 33, rawContent.length - 33));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buf = new byte[4096];
        int readBytes;
        while ((readBytes = in.read(buf)) > 0)
        {
          out.write(buf, 0, readBytes);
        }
        content = out.toByteArray();
      }
      catch (IOException e)
      {
        // Problems reading from the actual ByteArrayInputStream should never happen
        // so this will only ever be a ZipException.
        log.log(POILogger.INFO, "Possibly corrupt compression or non-compressed data", e);
      }
    } else {
      // Raw data is not compressed.
      content = rawContent;
    }
  
private voidfillJPGWidthHeight()

    /*
    http://www.codecomments.com/archive281-2004-3-158083.html

    Algorhitm proposed by Patrick TJ McPhee:

    read 2 bytes
    make sure they are 'ffd8'x
    repeatedly:
    read 2 bytes
    make sure the first one is 'ff'x
    if the second one is 'd9'x stop
    else if the second one is c0 or c2 (or possibly other values ...)
    skip 2 bytes
    read one byte into depth
    read two bytes into height
    read two bytes into width
    else
    read two bytes into length
    skip forward length-2 bytes

    Also used Ruby code snippet from: http://www.bigbold.com/snippets/posts/show/805 for reference
    */
    int pointer = pictureBytesStartOffset+2;
    int firstByte = _dataStream[pointer];
    int secondByte = _dataStream[pointer+1];

    int endOfPicture = pictureBytesStartOffset + size;
    while(pointer<endOfPicture-1) {
      do {
        firstByte = _dataStream[pointer];
        secondByte = _dataStream[pointer+1];
      } while (!(firstByte==(byte)0xFF) && pointer<endOfPicture-1);

      if (firstByte==((byte)0xFF) && pointer<endOfPicture-1) {
        if (secondByte==(byte)0xD9 || secondByte==(byte)0xDA) {
          break;
        } else if ( (secondByte & 0xF0) == 0xC0 && secondByte!=(byte)0xC4 && secondByte!=(byte)0xC8 && secondByte!=(byte)0xCC) {
          pointer += 5;
          this.height = getBigEndianShort(_dataStream, pointer);
          this.width = getBigEndianShort(_dataStream, pointer+2);
          break;
        } else {
          pointer++;
          pointer++;
          int length = getBigEndianShort(_dataStream, pointer);
          pointer+=length;
        }
      } else {
        pointer++;
      }
    }
  
private voidfillPNGWidthHeight()

    /*
     Used PNG file format description from http://www.wotsit.org/download.asp?f=png
    */
    int HEADER_START = pictureBytesStartOffset + PNG.length + 4;
    if (matchSignature(_dataStream, IHDR, HEADER_START)) {
      int IHDR_CHUNK_WIDTH = HEADER_START + 4;
      this.width = getBigEndianInt(_dataStream, IHDR_CHUNK_WIDTH);
      this.height = getBigEndianInt(_dataStream, IHDR_CHUNK_WIDTH + 4);
    }
  
private voidfillRawImageContent()

    this.rawContent = new byte[size];
    System.arraycopy(_dataStream, pictureBytesStartOffset, rawContent, 0, size);
  
private voidfillWidthHeight()

    String ext = suggestFileExtension();
    // trying to extract width and height from pictures content:
    if ("jpg".equalsIgnoreCase(ext)) {
      fillJPGWidthHeight();
    } else if ("png".equalsIgnoreCase(ext)) {
      fillPNGWidthHeight();
    }
  
public intgetAspectRatioX()
returns horizontal aspect ratio for picture provided by user

    return aspectRatioX;
  
public intgetAspectRatioY()
returns vertical aspect ratio for picture provided by user

    return aspectRatioY;
  
private static intgetBigEndianInt(byte[] data, int offset)

    return (((data[offset] & 0xFF)<< 24) + ((data[offset +1] & 0xFF) << 16) + ((data[offset + 2] & 0xFF) << 8) + (data[offset +3] & 0xFF));
  
private static intgetBigEndianShort(byte[] data, int offset)

    return (((data[offset] & 0xFF)<< 8) + (data[offset +1] & 0xFF));
  
public byte[]getContent()

return
picture's content as byte array

    if (content == null || content.length<=0)
    {
      fillImageContent();
    }
    return content;
  
public intgetHeight()
returns pixel height of the picture or -1 if dimensions determining was failed

    if (height == -1)
    {
      fillWidthHeight();
    }
    return height;
  
private static intgetPictureBytesStartOffset(int dataBlockStartOffset, byte[] _dataStream, int dataBlockSize)

    final int dataBlockEndOffset = dataBlockSize + dataBlockStartOffset;
    int realPicoffset = dataBlockStartOffset;

    int PICTFBlockSize = LittleEndian.getShort(_dataStream, dataBlockStartOffset +PICT_HEADER_OFFSET);
    int PICTF1BlockOffset = PICTFBlockSize + PICT_HEADER_OFFSET;
    int PICTF1BlockSize = LittleEndian.getShort(_dataStream, dataBlockStartOffset +PICTF1BlockOffset);

    int unknownHeaderOffset = (PICTF1BlockSize + PICTF1BlockOffset) < dataBlockEndOffset ?  (PICTF1BlockSize + PICTF1BlockOffset) : PICTF1BlockOffset;
    realPicoffset += (unknownHeaderOffset + UNKNOWN_HEADER_SIZE);
    if (realPicoffset>=dataBlockEndOffset) {
        realPicoffset -= UNKNOWN_HEADER_SIZE;
    }
    return realPicoffset;
  
public byte[]getRawContent()

    if (rawContent == null || rawContent.length <= 0)
    {
      fillRawImageContent();
    }
    return rawContent;
  
public intgetSize()

return
size in bytes of the picture

    return size;
  
public intgetWidth()
returns pixel width of the picture or -1 if dimensions determining was failed

    if (width == -1)
    {
      fillWidthHeight();
    }
    return width;
  
private static booleanmatchSignature(byte[] dataStream, byte[] signature, int pictureBytesOffset)

    boolean matched = pictureBytesOffset < dataStream.length;
    for (int i = 0; (i+pictureBytesOffset) < dataStream.length && i < signature.length; i++)
    {
      if (dataStream[i+pictureBytesOffset] != signature[i])
      {
        matched = false;
        break;
      }
    }
    return matched;
  
public java.lang.StringsuggestFileExtension()
tries to suggest extension for picture's file by matching signatures of popular image formats to first bytes of picture's contents

return
suggested file extension

    String extension = suggestFileExtension(_dataStream, pictureBytesStartOffset);
    if ("".equals(extension)) {
      // May be compressed.  Get the uncompressed content and inspect that.
      extension = suggestFileExtension(getContent(), 0);
    }
    return extension;
  
private java.lang.StringsuggestFileExtension(byte[] _dataStream, int pictureBytesStartOffset)

    if (matchSignature(_dataStream, JPG, pictureBytesStartOffset)) {
      return "jpg";
    } else if (matchSignature(_dataStream, PNG, pictureBytesStartOffset)) {
      return "png";
    } else if (matchSignature(_dataStream, GIF, pictureBytesStartOffset)) {
      return "gif";
    } else if (matchSignature(_dataStream, BMP, pictureBytesStartOffset)) {
      return "bmp";
    } else if (matchSignature(_dataStream, TIFF, pictureBytesStartOffset) ||
               matchSignature(_dataStream, TIFF1, pictureBytesStartOffset)) {
      return "tiff";
	} else {
        // Need to load the image content before we can try the following tests
        fillImageContent();

        if (matchSignature(content, WMF1, 0) || matchSignature(content, WMF2, 0)) {
            return "wmf";
        } else if (matchSignature(content, EMF, 0)) {
            return "emf";
        }
    }
    // TODO: DIB, PICT
    return "";
  
public java.lang.StringsuggestFullFileName()
Tries to suggest a filename: hex representation of picture structure offset in "Data" stream plus extension that is tried to determine from first byte of picture's content.

return
suggested file name

    String fileExt = suggestFileExtension();
    return Integer.toHexString(dataBlockStartOfsset) + (fileExt.length()>0 ? "."+fileExt : "");
  
public voidwriteImageContent(java.io.OutputStream out)
Writes Picture's content bytes to specified OutputStream. Is useful when there is need to write picture bytes directly to stream, omitting its representation in memory as distinct byte array.

param
out a stream to write to
throws
IOException if some exception is occured while writing to specified out

    if (rawContent!=null && rawContent.length>0) {
      out.write(rawContent, 0, size);
    } else {
      out.write(_dataStream, pictureBytesStartOffset, size);
    }