598 lines
21 KiB
C#
598 lines
21 KiB
C#
using System;
|
|
using System.Net;
|
|
using System.IO;
|
|
using System.Collections;
|
|
using iTextSharp.text;
|
|
using iTextSharp.text.pdf;
|
|
/*
|
|
* Copyright 2003 by Paulo Soares.
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version 1.1
|
|
* (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.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License isp distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the License.
|
|
*
|
|
* The Original Code isp 'iText, a free JAVA-PDF library'.
|
|
*
|
|
* The Initial Developer of the Original Code isp Bruno Lowagie. Portions created by
|
|
* the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
|
|
* All Rights Reserved.
|
|
* Co-Developer of the code isp Paulo Soares. Portions created by the Co-Developer
|
|
* are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
|
|
*
|
|
* Contributor(s): all the names of the contributors are added in the source code
|
|
* where applicable.
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of the
|
|
* LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
|
|
* provisions of LGPL are applicable instead of those above. If you wish to
|
|
* allow use of your version of this file only under the terms of the LGPL
|
|
* License and not to allow others to use your version of this file under
|
|
* the MPL, indicate your decision by deleting the provisions above and
|
|
* replace them with the notice and other provisions required by the LGPL.
|
|
* If you do not delete the provisions above, a recipient may use your version
|
|
* of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
|
|
*
|
|
* This library isp free software; you can redistribute it and/or modify it
|
|
* under the terms of the MPL as stated above or under the terms of the GNU
|
|
* Library General Public License as published by the Free Software Foundation;
|
|
* either version 2 of the License, or any later version.
|
|
*
|
|
* This library isp distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
|
|
* details.
|
|
*
|
|
* If you didn't download this code from the following link, you should check if
|
|
* you aren't using an obsolete version:
|
|
* http://www.lowagie.com/iText/
|
|
*/
|
|
|
|
namespace iTextSharp.text.pdf.codec {
|
|
/** Reads gif images of all types. All the images in a gif are read in the constructors
|
|
* and can be retrieved with other methods.
|
|
* @author Paulo Soares (psoares@consiste.pt)
|
|
*/
|
|
public class GifImage {
|
|
|
|
protected Stream inp;
|
|
protected int width; // full image width
|
|
protected int height; // full image height
|
|
protected bool gctFlag; // global color table used
|
|
|
|
protected int bgIndex; // background color index
|
|
protected int bgColor; // background color
|
|
protected int pixelAspect; // pixel aspect ratio
|
|
|
|
protected bool lctFlag; // local color table flag
|
|
protected bool interlace; // interlace flag
|
|
protected int lctSize; // local color table size
|
|
|
|
protected int ix, iy, iw, ih; // current image rectangle
|
|
|
|
protected byte[] block = new byte[256]; // current data block
|
|
protected int blockSize = 0; // block size
|
|
|
|
// last graphic control extension info
|
|
protected int dispose = 0; // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
|
|
protected bool transparency = false; // use transparent color
|
|
protected int delay = 0; // delay in milliseconds
|
|
protected int transIndex; // transparent color index
|
|
|
|
protected const int MaxStackSize = 4096; // max decoder pixel stack size
|
|
|
|
// LZW decoder working arrays
|
|
protected short[] prefix;
|
|
protected byte[] suffix;
|
|
protected byte[] pixelStack;
|
|
protected byte[] pixels;
|
|
|
|
protected byte[] m_out;
|
|
protected int m_bpc;
|
|
protected int m_gbpc;
|
|
protected byte[] m_global_table;
|
|
protected byte[] m_local_table;
|
|
protected byte[] m_curr_table;
|
|
protected int m_line_stride;
|
|
protected byte[] fromData;
|
|
protected Uri fromUrl;
|
|
|
|
|
|
protected ArrayList frames = new ArrayList(); // frames read from current file
|
|
|
|
/** Reads gif images from an URL.
|
|
* @param url the URL
|
|
* @throws IOException on error
|
|
*/
|
|
public GifImage(Uri url) {
|
|
fromUrl = url;
|
|
Stream isp = null;
|
|
try {
|
|
isp = WebRequest.Create(url).GetResponse().GetResponseStream();
|
|
Process(isp);
|
|
}
|
|
finally {
|
|
if (isp != null) {
|
|
isp.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Reads gif images from a file.
|
|
* @param file the file
|
|
* @throws IOException on error
|
|
*/
|
|
public GifImage(String file) : this(Utilities.ToURL(file)) {
|
|
}
|
|
|
|
/** Reads gif images from a byte array.
|
|
* @param data the byte array
|
|
* @throws IOException on error
|
|
*/
|
|
public GifImage(byte[] data) {
|
|
fromData = data;
|
|
Stream isp = null;
|
|
try {
|
|
isp = new MemoryStream(data);
|
|
Process(isp);
|
|
}
|
|
finally {
|
|
if (isp != null) {
|
|
isp.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Reads gif images from a stream. The stream isp not closed.
|
|
* @param isp the stream
|
|
* @throws IOException on error
|
|
*/
|
|
public GifImage(Stream isp) {
|
|
Process(isp);
|
|
}
|
|
|
|
/** Gets the number of frames the gif has.
|
|
* @return the number of frames the gif has
|
|
*/
|
|
public int GetFrameCount() {
|
|
return frames.Count;
|
|
}
|
|
|
|
/** Gets the image from a frame. The first frame isp 1.
|
|
* @param frame the frame to get the image from
|
|
* @return the image
|
|
*/
|
|
public Image GetImage(int frame) {
|
|
GifFrame gf = (GifFrame)frames[frame - 1];
|
|
return gf.image;
|
|
}
|
|
|
|
/** Gets the [x,y] position of the frame in reference to the
|
|
* logical screen.
|
|
* @param frame the frame
|
|
* @return the [x,y] position of the frame
|
|
*/
|
|
public int[] GetFramePosition(int frame) {
|
|
GifFrame gf = (GifFrame)frames[frame - 1];
|
|
return new int[]{gf.ix, gf.iy};
|
|
|
|
}
|
|
|
|
/** Gets the logical screen. The images may be smaller and placed
|
|
* in some position in this screen to playback some animation.
|
|
* No image will be be bigger that this.
|
|
* @return the logical screen dimensions as [x,y]
|
|
*/
|
|
public int[] GetLogicalScreen() {
|
|
return new int[]{width, height};
|
|
}
|
|
|
|
internal void Process(Stream isp) {
|
|
inp = new BufferedStream(isp);
|
|
ReadHeader();
|
|
ReadContents();
|
|
if (frames.Count == 0)
|
|
throw new IOException("The file does not contain any valid image.");
|
|
}
|
|
|
|
/**
|
|
* Reads GIF file header information.
|
|
*/
|
|
protected void ReadHeader() {
|
|
String id = "";
|
|
for (int i = 0; i < 6; i++)
|
|
id += (char)inp.ReadByte();
|
|
if (!id.StartsWith("GIF8")) {
|
|
throw new IOException("Gif signature nor found.");
|
|
}
|
|
|
|
ReadLSD();
|
|
if (gctFlag) {
|
|
m_global_table = ReadColorTable(m_gbpc);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads Logical Screen Descriptor
|
|
*/
|
|
protected void ReadLSD() {
|
|
|
|
// logical screen size
|
|
width = ReadShort();
|
|
height = ReadShort();
|
|
|
|
// packed fields
|
|
int packed = inp.ReadByte();
|
|
gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
|
|
m_gbpc = (packed & 7) + 1;
|
|
bgIndex = inp.ReadByte(); // background color index
|
|
pixelAspect = inp.ReadByte(); // pixel aspect ratio
|
|
}
|
|
|
|
/**
|
|
* Reads next 16-bit value, LSB first
|
|
*/
|
|
protected int ReadShort() {
|
|
// read 16-bit value, LSB first
|
|
return inp.ReadByte() | (inp.ReadByte() << 8);
|
|
}
|
|
|
|
/**
|
|
* Reads next variable length block from input.
|
|
*
|
|
* @return number of bytes stored in "buffer"
|
|
*/
|
|
protected int ReadBlock() {
|
|
blockSize = inp.ReadByte();
|
|
if (blockSize <= 0)
|
|
return blockSize = 0;
|
|
for (int k = 0; k < blockSize; ++k) {
|
|
int v = inp.ReadByte();
|
|
if (v < 0) {
|
|
return blockSize = k;
|
|
}
|
|
block[k] = (byte)v;
|
|
}
|
|
return blockSize;
|
|
}
|
|
|
|
protected byte[] ReadColorTable(int bpc) {
|
|
int ncolors = 1 << bpc;
|
|
int nbytes = 3*ncolors;
|
|
bpc = NewBpc(bpc);
|
|
byte[] table = new byte[(1 << bpc) * 3];
|
|
ReadFully(table, 0, nbytes);
|
|
return table;
|
|
}
|
|
|
|
|
|
static protected int NewBpc(int bpc) {
|
|
switch (bpc) {
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
break;
|
|
case 3:
|
|
return 4;
|
|
default:
|
|
return 8;
|
|
}
|
|
return bpc;
|
|
}
|
|
|
|
protected void ReadContents() {
|
|
// read GIF file content blocks
|
|
bool done = false;
|
|
while (!done) {
|
|
int code = inp.ReadByte();
|
|
switch (code) {
|
|
|
|
case 0x2C: // image separator
|
|
ReadImage();
|
|
break;
|
|
|
|
case 0x21: // extension
|
|
code = inp.ReadByte();
|
|
switch (code) {
|
|
|
|
case 0xf9: // graphics control extension
|
|
ReadGraphicControlExt();
|
|
break;
|
|
|
|
case 0xff: // application extension
|
|
ReadBlock();
|
|
Skip(); // don't care
|
|
break;
|
|
|
|
default: // uninteresting extension
|
|
Skip();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads next frame image
|
|
*/
|
|
protected void ReadImage() {
|
|
ix = ReadShort(); // (sub)image position & size
|
|
iy = ReadShort();
|
|
iw = ReadShort();
|
|
ih = ReadShort();
|
|
|
|
int packed = inp.ReadByte();
|
|
lctFlag = (packed & 0x80) != 0; // 1 - local color table flag
|
|
interlace = (packed & 0x40) != 0; // 2 - interlace flag
|
|
// 3 - sort flag
|
|
// 4-5 - reserved
|
|
lctSize = 2 << (packed & 7); // 6-8 - local color table size
|
|
m_bpc = NewBpc(m_gbpc);
|
|
if (lctFlag) {
|
|
m_curr_table = ReadColorTable((packed & 7) + 1); // read table
|
|
m_bpc = NewBpc((packed & 7) + 1);
|
|
}
|
|
else {
|
|
m_curr_table = m_global_table;
|
|
}
|
|
if (transparency && transIndex >= m_curr_table.Length / 3)
|
|
transparency = false;
|
|
if (transparency && m_bpc == 1) { // Acrobat 5.05 doesn't like this combination
|
|
byte[] tp = new byte[12];
|
|
Array.Copy(m_curr_table, 0, tp, 0, 6);
|
|
m_curr_table = tp;
|
|
m_bpc = 2;
|
|
}
|
|
bool skipZero = DecodeImageData(); // decode pixel data
|
|
if (!skipZero)
|
|
Skip();
|
|
|
|
Image img = null;
|
|
img = new ImgRaw(iw, ih, 1, m_bpc, m_out);
|
|
PdfArray colorspace = new PdfArray();
|
|
colorspace.Add(PdfName.INDEXED);
|
|
colorspace.Add(PdfName.DEVICERGB);
|
|
int len = m_curr_table.Length;
|
|
colorspace.Add(new PdfNumber(len / 3 - 1));
|
|
colorspace.Add(new PdfString(m_curr_table));
|
|
PdfDictionary ad = new PdfDictionary();
|
|
ad.Put(PdfName.COLORSPACE, colorspace);
|
|
img.Additional = ad;
|
|
if (transparency) {
|
|
img.Transparency = new int[]{transIndex, transIndex};
|
|
}
|
|
img.OriginalType = Image.ORIGINAL_GIF;
|
|
img.OriginalData = fromData;
|
|
img.Url = fromUrl;
|
|
GifFrame gf = new GifFrame();
|
|
gf.image = img;
|
|
gf.ix = ix;
|
|
gf.iy = iy;
|
|
frames.Add(gf); // add image to frame list
|
|
|
|
//ResetFrame();
|
|
|
|
}
|
|
|
|
protected bool DecodeImageData() {
|
|
int NullCode = -1;
|
|
int npix = iw * ih;
|
|
int available, clear, code_mask, code_size, end_of_information, in_code, old_code,
|
|
bits, code, count, i, datum, data_size, first, top, bi;
|
|
bool skipZero = false;
|
|
|
|
if (prefix == null)
|
|
prefix = new short[MaxStackSize];
|
|
if (suffix == null)
|
|
suffix = new byte[MaxStackSize];
|
|
if (pixelStack == null)
|
|
pixelStack = new byte[MaxStackSize+1];
|
|
|
|
m_line_stride = (iw * m_bpc + 7) / 8;
|
|
m_out = new byte[m_line_stride * ih];
|
|
int pass = 1;
|
|
int inc = interlace ? 8 : 1;
|
|
int line = 0;
|
|
int xpos = 0;
|
|
|
|
// Initialize GIF data stream decoder.
|
|
|
|
data_size = inp.ReadByte();
|
|
clear = 1 << data_size;
|
|
end_of_information = clear + 1;
|
|
available = clear + 2;
|
|
old_code = NullCode;
|
|
code_size = data_size + 1;
|
|
code_mask = (1 << code_size) - 1;
|
|
for (code = 0; code < clear; code++) {
|
|
prefix[code] = 0;
|
|
suffix[code] = (byte) code;
|
|
}
|
|
|
|
// Decode GIF pixel stream.
|
|
|
|
datum = bits = count = first = top = bi = 0;
|
|
|
|
for (i = 0; i < npix; ) {
|
|
if (top == 0) {
|
|
if (bits < code_size) {
|
|
// Load bytes until there are enough bits for a code.
|
|
if (count == 0) {
|
|
// Read a new data block.
|
|
count = ReadBlock();
|
|
if (count <= 0) {
|
|
skipZero = true;
|
|
break;
|
|
}
|
|
bi = 0;
|
|
}
|
|
datum += (((int) block[bi]) & 0xff) << bits;
|
|
bits += 8;
|
|
bi++;
|
|
count--;
|
|
continue;
|
|
}
|
|
|
|
// Get the next code.
|
|
|
|
code = datum & code_mask;
|
|
datum >>= code_size;
|
|
bits -= code_size;
|
|
|
|
// Interpret the code
|
|
|
|
if ((code > available) || (code == end_of_information))
|
|
break;
|
|
if (code == clear) {
|
|
// Reset decoder.
|
|
code_size = data_size + 1;
|
|
code_mask = (1 << code_size) - 1;
|
|
available = clear + 2;
|
|
old_code = NullCode;
|
|
continue;
|
|
}
|
|
if (old_code == NullCode) {
|
|
pixelStack[top++] = suffix[code];
|
|
old_code = code;
|
|
first = code;
|
|
continue;
|
|
}
|
|
in_code = code;
|
|
if (code == available) {
|
|
pixelStack[top++] = (byte) first;
|
|
code = old_code;
|
|
}
|
|
while (code > clear) {
|
|
pixelStack[top++] = suffix[code];
|
|
code = prefix[code];
|
|
}
|
|
first = ((int) suffix[code]) & 0xff;
|
|
|
|
// Add a new string to the string table,
|
|
|
|
if (available >= MaxStackSize)
|
|
break;
|
|
pixelStack[top++] = (byte) first;
|
|
prefix[available] = (short) old_code;
|
|
suffix[available] = (byte) first;
|
|
available++;
|
|
if (((available & code_mask) == 0) && (available < MaxStackSize)) {
|
|
code_size++;
|
|
code_mask += available;
|
|
}
|
|
old_code = in_code;
|
|
}
|
|
|
|
// Pop a pixel off the pixel stack.
|
|
|
|
top--;
|
|
i++;
|
|
|
|
SetPixel(xpos, line, pixelStack[top]);
|
|
++xpos;
|
|
if (xpos >= iw) {
|
|
xpos = 0;
|
|
line += inc;
|
|
if (line >= ih) {
|
|
if (interlace) {
|
|
do {
|
|
pass++;
|
|
switch (pass) {
|
|
case 2:
|
|
line = 4;
|
|
break;
|
|
case 3:
|
|
line = 2;
|
|
inc = 4;
|
|
break;
|
|
case 4:
|
|
line = 1;
|
|
inc = 2;
|
|
break;
|
|
default: // this shouldn't happen
|
|
line = ih - 1;
|
|
inc = 0;
|
|
break;
|
|
}
|
|
} while (line >= ih);
|
|
}
|
|
else {
|
|
line = ih - 1; // this shouldn't happen
|
|
inc = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return skipZero;
|
|
}
|
|
|
|
|
|
protected void SetPixel(int x, int y, int v) {
|
|
if (m_bpc == 8) {
|
|
int pos = x + iw * y;
|
|
m_out[pos] = (byte)v;
|
|
}
|
|
else {
|
|
int pos = m_line_stride * y + x / (8 / m_bpc);
|
|
int vout = v << (8 - m_bpc * (x % (8 / m_bpc))- m_bpc);
|
|
m_out[pos] |= (byte)vout;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets frame state for reading next image.
|
|
*/
|
|
protected void ResetFrame() {
|
|
}
|
|
|
|
/**
|
|
* Reads Graphics Control Extension values
|
|
*/
|
|
protected void ReadGraphicControlExt() {
|
|
inp.ReadByte(); // block size
|
|
int packed = inp.ReadByte(); // packed fields
|
|
dispose = (packed & 0x1c) >> 2; // disposal method
|
|
if (dispose == 0)
|
|
dispose = 1; // elect to keep old image if discretionary
|
|
transparency = (packed & 1) != 0;
|
|
delay = ReadShort() * 10; // delay inp milliseconds
|
|
transIndex = inp.ReadByte(); // transparent color index
|
|
inp.ReadByte(); // block terminator
|
|
}
|
|
|
|
/**
|
|
* Skips variable length blocks up to and including
|
|
* next zero length block.
|
|
*/
|
|
protected void Skip() {
|
|
do {
|
|
ReadBlock();
|
|
} while (blockSize > 0);
|
|
}
|
|
|
|
private void ReadFully(byte[] b, int offset, int count) {
|
|
while (count > 0) {
|
|
int n = inp.Read(b, offset, count);
|
|
if (n <= 0)
|
|
throw new IOException("Insufficient data.");
|
|
count -= n;
|
|
offset += n;
|
|
}
|
|
}
|
|
|
|
internal class GifFrame {
|
|
internal Image image;
|
|
internal int ix;
|
|
internal int iy;
|
|
}
|
|
}
|
|
}
|