#region PDFsharp - A .NET library for processing PDF
//
// Authors:
// Stefan Lange
// Thomas Hövel
//
// Copyright (c) 2005-2016 empira Software GmbH, Cologne Area (Germany)
//
// http://www.PdfSharpCore.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#endregion
using System;
using System.Diagnostics;
using System.IO;
using PdfSharpCore.Drawing;
using PdfSharpCore.Drawing.Internal;
using PdfSharpCore.Pdf.Filters;
namespace PdfSharpCore.Pdf.Advanced
{
///
/// Represents an image.
///
public sealed partial class PdfImage : PdfXObject
{
///
/// Initializes a new instance of PdfImage from an XImage.
///
public PdfImage(PdfDocument document, XImage image)
: base(document)
{
Elements.SetName(Keys.Type, "/XObject");
Elements.SetName(Keys.Subtype, "/Image");
_image = image;
////// TODO: identify multiple used images. If the image already exists use the same XRef.
////_defaultName = PdfImageTable.NextImageName;
switch (_image.Format.Guid.ToString("B").ToUpper())
{
// Pdf supports Jpeg, therefore we can write what we've read:
case "{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Jpeg
InitializeJpeg();
break;
// All other image formats are converted to PDF bitmaps:
case "{B96B3CAF-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Png
case "{B96B3CB0-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Gif
case "{B96B3CB1-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Tiff
case "{B96B3CB5-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Icon
// TODO: possible optimization for PNG (do not decompress/recompress)???
// TODO: try Jpeg for size optimization???
InitializeNonJpeg();
break;
case "{84570158-DBF0-4C6B-8368-62D6A3CA76E0}": //XImageFormat.Pdf:
Debug.Assert(false, "XPdfForm not expected here.");
break;
default:
Debug.Assert(false, "Unexpected image type.");
break;
}
}
///
/// Gets the underlying XImage object.
///
public XImage Image
{
get { return _image; }
}
readonly XImage _image;
///
/// Returns 'Image'.
///
public override string ToString()
{
return "Image";
}
///
/// Creates the keys for a JPEG image.
///
void InitializeJpeg()
{
byte[] imageBits = null;
using (MemoryStream memory = _image.AsJpeg())
{
imageBits = memory.ToArray();
}
bool tryFlateDecode = _document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Automatic;
bool useFlateDecode = _document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Always;
FlateDecode fd = new FlateDecode();
byte[] imageDataCompressed = (useFlateDecode || tryFlateDecode) ? fd.Encode(imageBits, _document.Options.FlateEncodeMode) : null;
if (useFlateDecode || tryFlateDecode && imageDataCompressed.Length < imageBits.Length)
{
Stream = new PdfStream(imageDataCompressed, this);
Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataCompressed.Length);
PdfArray arrayFilters = new PdfArray(_document);
arrayFilters.Elements.Add(new PdfName("/FlateDecode"));
arrayFilters.Elements.Add(new PdfName("/DCTDecode"));
Elements[PdfStream.Keys.Filter] = arrayFilters;
}
else
{
Stream = new PdfStream(imageBits, this);
Elements[PdfStream.Keys.Length] = new PdfInteger(imageBits.Length);
Elements[PdfStream.Keys.Filter] = new PdfName("/DCTDecode");
}
if (_image.Interpolate)
Elements[Keys.Interpolate] = PdfBoolean.True;
Elements[Keys.Width] = new PdfInteger(_image.PixelWidth);
Elements[Keys.Height] = new PdfInteger(_image.PixelHeight);
Elements[Keys.BitsPerComponent] = new PdfInteger(8);
Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB");
}
///
/// Creates the keys for a FLATE image.
///
void InitializeNonJpeg()
{
ReadTrueColorMemoryBitmap(3, 8, true);
}
private static int ReadWord(byte[] ab, int offset)
{
return ab[offset] + 256 * ab[offset + 1];
}
private static int ReadDWord(byte[] ab, int offset)
{
return ReadWord(ab, offset) + 0x10000 * ReadWord(ab, offset + 2);
}
///
/// Reads images that are returned from GDI+ without color palette.
///
/// 4 (32bpp RGB), 3 (24bpp RGB, 32bpp ARGB)
/// 8
/// true (ARGB), false (RGB)
private void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha)
{
int pdfVersion = Owner.Version;
MemoryStream memory = new MemoryStream();
memory = _image.AsBitmap();
// THHO4THHO Use ImageImporterBMP here to avoid redundant code.
int streamLength = (int)memory.Length;
Debug.Assert(streamLength > 0, "Bitmap image encoding failed.");
if (streamLength > 0)
{
byte[] imageBits = new byte[streamLength];
memory.Seek(0, SeekOrigin.Begin);
memory.Read(imageBits, 0, streamLength);
memory.Dispose();
int height = _image.PixelHeight;
int width = _image.PixelWidth;
// TODO: we could define structures for
// BITMAPFILEHEADER
// { BITMAPINFO }
// BITMAPINFOHEADER
// to avoid ReadWord and ReadDWord ... (but w/o pointers this doesn't help much)
bool bigHeader = false;
if (ReadWord(imageBits, 0) != 0x4d42 || // "BM"
ReadDWord(imageBits, 2) != streamLength ||
ReadDWord(imageBits, 18) != width ||
ReadDWord(imageBits, 22) != height)
{
throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format");
}
int infoHeaderSize = ReadDWord(imageBits, 14); // sizeof BITMAPINFOHEADER
if (infoHeaderSize != 40 && infoHeaderSize != 108)
{
throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format #2");
}
bigHeader = infoHeaderSize == 108;
if (ReadWord(imageBits, 26) != 1 ||
(!hasAlpha && ReadWord(imageBits, bigHeader?30:28) != components * bits ||
hasAlpha && ReadWord(imageBits, bigHeader?30:28) != (components + 1) * bits) ||
bigHeader ? ReadWord(imageBits, 32) != 0 : ReadDWord(imageBits, 30) != 0)
{
throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format #3");
}
int nFileOffset = ReadDWord(imageBits, 10);
int logicalComponents = components;
if (components == 4)
logicalComponents = 3;
byte[] imageData = new byte[components * width * height];
bool hasMask = false;
bool hasAlphaMask = false;
byte[] alphaMask = hasAlpha ? new byte[width * height] : null;
MonochromeMask mask = hasAlpha ?
new MonochromeMask(width, height) : null;
int nOffsetRead = 0;
if (logicalComponents == 3)
{
for (int y = 0; y < height; ++y)
{
int nOffsetWrite = 3 * (height - 1 - y) * width;
int nOffsetWriteAlpha = 0;
if (hasAlpha)
{
mask.StartLine(y);
nOffsetWriteAlpha = (height - 1 - y) * width;
}
for (int x = 0; x < width; ++x)
{
imageData[nOffsetWrite] = imageBits[nFileOffset + nOffsetRead + 2];
imageData[nOffsetWrite + 1] = imageBits[nFileOffset + nOffsetRead + 1];
imageData[nOffsetWrite + 2] = imageBits[nFileOffset + nOffsetRead];
if (hasAlpha)
{
mask.AddPel(imageBits[nFileOffset + nOffsetRead + 3]);
alphaMask[nOffsetWriteAlpha] = imageBits[nFileOffset + nOffsetRead + 3];
if (!hasMask || !hasAlphaMask)
{
if (imageBits[nFileOffset + nOffsetRead + 3] != 255)
{
hasMask = true;
if (imageBits[nFileOffset + nOffsetRead + 3] != 0)
hasAlphaMask = true;
}
}
++nOffsetWriteAlpha;
}
nOffsetRead += hasAlpha ? 4 : components;
nOffsetWrite += 3;
}
nOffsetRead = 4 * ((nOffsetRead + 3) / 4); // Align to 32 bit boundary
}
}
else if (components == 1)
{
// Grayscale
throw new NotImplementedException("Image format not supported (grayscales).");
}
FlateDecode fd = new FlateDecode();
if (hasMask)
{
// monochrome mask is either sufficient or
// provided for compatibility with older reader versions
byte[] maskDataCompressed = fd.Encode(mask.MaskData, _document.Options.FlateEncodeMode);
PdfDictionary pdfMask = new PdfDictionary(_document);
pdfMask.Elements.SetName(Keys.Type, "/XObject");
pdfMask.Elements.SetName(Keys.Subtype, "/Image");
Owner._irefTable.Add(pdfMask);
pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
pdfMask.Elements[PdfStream.Keys.Length] = new PdfInteger(maskDataCompressed.Length);
pdfMask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
pdfMask.Elements[Keys.Width] = new PdfInteger(width);
pdfMask.Elements[Keys.Height] = new PdfInteger(height);
pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true);
Elements[Keys.Mask] = pdfMask.Reference;
}
if (hasMask && hasAlphaMask && pdfVersion >= 14)
{
// The image provides an alpha mask (requires Arcrobat 5.0 or higher)
byte[] alphaMaskCompressed = fd.Encode(alphaMask, _document.Options.FlateEncodeMode);
PdfDictionary smask = new PdfDictionary(_document);
smask.Elements.SetName(Keys.Type, "/XObject");
smask.Elements.SetName(Keys.Subtype, "/Image");
Owner._irefTable.Add(smask);
smask.Stream = new PdfStream(alphaMaskCompressed, smask);
smask.Elements[PdfStream.Keys.Length] = new PdfInteger(alphaMaskCompressed.Length);
smask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
smask.Elements[Keys.Width] = new PdfInteger(width);
smask.Elements[Keys.Height] = new PdfInteger(height);
smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8);
smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
Elements[Keys.SMask] = smask.Reference;
}
byte[] imageDataCompressed = fd.Encode(imageData, _document.Options.FlateEncodeMode);
Stream = new PdfStream(imageDataCompressed, this);
Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataCompressed.Length);
Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
Elements[Keys.Width] = new PdfInteger(width);
Elements[Keys.Height] = new PdfInteger(height);
Elements[Keys.BitsPerComponent] = new PdfInteger(8);
// TODO: CMYK
Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB");
if (_image.Interpolate)
Elements[Keys.Interpolate] = PdfBoolean.True;
}
}
/* BITMAPINFOHEADER struct and byte offsets:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; // 14
LONG biWidth; // 18
LONG biHeight; // 22
WORD biPlanes; // 26
WORD biBitCount; // 28
DWORD biCompression; // 30
DWORD biSizeImage; // 34
LONG biXPelsPerMeter; // 38
LONG biYPelsPerMeter; // 42
DWORD biClrUsed; // 46
DWORD biClrImportant; // 50
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
*/
private void ReadIndexedMemoryBitmap(int bits/*, ref bool hasAlpha*/)
{
int pdfVersion = Owner.Version;
int firstMaskColor = -1, lastMaskColor = -1;
bool segmentedColorMask = false;
MemoryStream memory = new MemoryStream();
// THHO4THHO Use ImageImporterBMP here to avoid redundant code.
int streamLength = (int)memory.Length;
Debug.Assert(streamLength > 0, "Bitmap image encoding failed.");
if (streamLength > 0)
{
byte[] imageBits = new byte[streamLength];
memory.Seek(0, SeekOrigin.Begin);
memory.Read(imageBits, 0, streamLength);
memory.Dispose();
int height = _image.PixelHeight;
int width = _image.PixelWidth;
if (ReadWord(imageBits, 0) != 0x4d42 || // "BM"
ReadDWord(imageBits, 2) != streamLength ||
ReadDWord(imageBits, 14) != 40 || // sizeof BITMAPINFOHEADER
ReadDWord(imageBits, 18) != width ||
ReadDWord(imageBits, 22) != height)
{
throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format");
}
int fileBits = ReadWord(imageBits, 28);
if (fileBits != bits)
{
if (fileBits == 1 || fileBits == 4 || fileBits == 8)
bits = fileBits;
}
if (ReadWord(imageBits, 26) != 1 ||
ReadWord(imageBits, 28) != bits ||
ReadDWord(imageBits, 30) != 0)
{
throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #2");
}
int bytesFileOffset = ReadDWord(imageBits, 10);
const int bytesColorPaletteOffset = 0x36; // GDI+ always returns Windows bitmaps: sizeof BITMAPFILEHEADER + sizeof BITMAPINFOHEADER
int paletteColors = ReadDWord(imageBits, 46);
if ((bytesFileOffset - bytesColorPaletteOffset) / 4 != paletteColors)
{
throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3");
}
MonochromeMask mask = new MonochromeMask(width, height);
bool isGray = bits == 8 && (paletteColors == 256 || paletteColors == 0);
int isBitonal = 0; // 0: false; >0: true; <0: true (inverted)
byte[] paletteData = new byte[3 * paletteColors];
for (int color = 0; color < paletteColors; ++color)
{
paletteData[3 * color] = imageBits[bytesColorPaletteOffset + 4 * color + 2];
paletteData[3 * color + 1] = imageBits[bytesColorPaletteOffset + 4 * color + 1];
paletteData[3 * color + 2] = imageBits[bytesColorPaletteOffset + 4 * color + 0];
if (isGray)
isGray = paletteData[3 * color] == paletteData[3 * color + 1] &&
paletteData[3 * color] == paletteData[3 * color + 2];
if (imageBits[bytesColorPaletteOffset + 4 * color + 3] < 128)
{
// We treat this as transparency:
if (firstMaskColor == -1)
firstMaskColor = color;
if (lastMaskColor == -1 || lastMaskColor == color - 1)
lastMaskColor = color;
if (lastMaskColor != color)
segmentedColorMask = true;
}
//else
//{
// // We treat this as opacity:
//}
}
if (bits == 1)
{
if (paletteColors == 0)
isBitonal = 1;
if (paletteColors == 2)
{
if (paletteData[0] == 0 &&
paletteData[1] == 0 &&
paletteData[2] == 0 &&
paletteData[3] == 255 &&
paletteData[4] == 255 &&
paletteData[5] == 255)
isBitonal = 1; // Black on white
if (paletteData[5] == 0 &&
paletteData[4] == 0 &&
paletteData[3] == 0 &&
paletteData[2] == 255 &&
paletteData[1] == 255 &&
paletteData[0] == 255)
isBitonal = -1; // White on black
}
}
// NYI: (no sample found where this was required)
// if (segmentedColorMask = true)
// { ... }
bool isFaxEncoding = false;
byte[] imageData = new byte[((width * bits + 7) / 8) * height];
byte[] imageDataFax = null;
int k = 0;
if (bits == 1)
{
// TODO: flag/option?
// We try Group 3 1D and Group 4 (2D) encoding here and keep the smaller byte array.
//byte[] temp = new byte[imageData.Length];
//int ccittSize = DoFaxEncoding(ref temp, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height);
// It seems that Group 3 2D encoding never beats both other encodings, therefore we don't call it here.
//byte[] temp2D = new byte[imageData.Length];
//uint dpiY = (uint)image.VerticalResolution;
//uint kTmp = 0;
//int ccittSize2D = DoFaxEncoding2D((uint)bytesFileOffset, ref temp2D, imageBits, (uint)width, (uint)height, dpiY, out kTmp);
//k = (int) kTmp;
byte[] tempG4 = new byte[imageData.Length];
int ccittSizeG4 = DoFaxEncodingGroup4(ref tempG4, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height);
isFaxEncoding = /*ccittSize > 0 ||*/ ccittSizeG4 > 0;
if (isFaxEncoding)
{
//if (ccittSize == 0)
// ccittSize = 0x7fffffff;
if (ccittSizeG4 == 0)
ccittSizeG4 = 0x7fffffff;
//if (ccittSize <= ccittSizeG4)
//{
// Array.Resize(ref temp, ccittSize);
// imageDataFax = temp;
// k = 0;
//}
//else
{
Array.Resize(ref tempG4, ccittSizeG4);
imageDataFax = tempG4;
k = -1;
}
}
}
//if (!isFaxEncoding)
{
int bytesOffsetRead = 0;
if (bits == 8 || bits == 4 || bits == 1)
{
int bytesPerLine = (width * bits + 7) / 8;
for (int y = 0; y < height; ++y)
{
mask.StartLine(y);
int bytesOffsetWrite = (height - 1 - y) * ((width * bits + 7) / 8);
for (int x = 0; x < bytesPerLine; ++x)
{
if (isGray)
{
// Lookup the gray value from the palette:
imageData[bytesOffsetWrite] = paletteData[3 * imageBits[bytesFileOffset + bytesOffsetRead]];
}
else
{
// Store the palette index.
imageData[bytesOffsetWrite] = imageBits[bytesFileOffset + bytesOffsetRead];
}
if (firstMaskColor != -1)
{
int n = imageBits[bytesFileOffset + bytesOffsetRead];
if (bits == 8)
{
// TODO???: segmentedColorMask == true => bad mask NYI
mask.AddPel((n >= firstMaskColor) && (n <= lastMaskColor));
}
else if (bits == 4)
{
// TODO???: segmentedColorMask == true => bad mask NYI
int n1 = (n & 0xf0) / 16;
int n2 = (n & 0x0f);
mask.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor));
mask.AddPel((n2 >= firstMaskColor) && (n2 <= lastMaskColor));
}
else if (bits == 1)
{
// TODO???: segmentedColorMask == true => bad mask NYI
for (int bit = 1; bit <= 8; ++bit)
{
int n1 = (n & 0x80) / 128;
mask.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor));
n *= 2;
}
}
}
bytesOffsetRead += 1;
bytesOffsetWrite += 1;
}
bytesOffsetRead = 4 * ((bytesOffsetRead + 3) / 4); // Align to 32 bit boundary
}
}
else
{
throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3");
}
}
FlateDecode fd = new FlateDecode();
if (firstMaskColor != -1 &&
lastMaskColor != -1)
{
// Color mask requires Reader 4.0 or higher:
//if (!segmentedColorMask && pdfVersion >= 13)
if (!segmentedColorMask && pdfVersion >= 13 && !isGray)
{
PdfArray array = new PdfArray(_document);
array.Elements.Add(new PdfInteger(firstMaskColor));
array.Elements.Add(new PdfInteger(lastMaskColor));
Elements[Keys.Mask] = array;
}
else
{
// Monochrome mask
byte[] maskDataCompressed = fd.Encode(mask.MaskData, _document.Options.FlateEncodeMode);
PdfDictionary pdfMask = new PdfDictionary(_document);
pdfMask.Elements.SetName(Keys.Type, "/XObject");
pdfMask.Elements.SetName(Keys.Subtype, "/Image");
Owner._irefTable.Add(pdfMask);
pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
pdfMask.Elements[PdfStream.Keys.Length] = new PdfInteger(maskDataCompressed.Length);
pdfMask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
pdfMask.Elements[Keys.Width] = new PdfInteger(width);
pdfMask.Elements[Keys.Height] = new PdfInteger(height);
pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true);
Elements[Keys.Mask] = pdfMask.Reference;
}
}
byte[] imageDataCompressed = fd.Encode(imageData, _document.Options.FlateEncodeMode);
byte[] imageDataFaxCompressed = isFaxEncoding ? fd.Encode(imageDataFax, _document.Options.FlateEncodeMode) : null;
bool usesCcittEncoding = false;
if (isFaxEncoding &&
(imageDataFax.Length < imageDataCompressed.Length ||
imageDataFaxCompressed.Length < imageDataCompressed.Length))
{
// /CCITTFaxDecode creates the smaller file (with or without /FlateDecode):
usesCcittEncoding = true;
if (imageDataFax.Length < imageDataCompressed.Length)
{
Stream = new PdfStream(imageDataFax, this);
Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataFax.Length);
Elements[PdfStream.Keys.Filter] = new PdfName("/CCITTFaxDecode");
//PdfArray array2 = new PdfArray(_document);
PdfDictionary dictionary = new PdfDictionary();
if (k != 0)
dictionary.Elements.Add("/K", new PdfInteger(k));
if (isBitonal < 0)
dictionary.Elements.Add("/BlackIs1", new PdfBoolean(true));
dictionary.Elements.Add("/EndOfBlock", new PdfBoolean(false));
dictionary.Elements.Add("/Columns", new PdfInteger(width));
dictionary.Elements.Add("/Rows", new PdfInteger(height));
//array2.Elements.Add(dictionary);
Elements[PdfStream.Keys.DecodeParms] = dictionary; // array2;
}
else
{
Stream = new PdfStream(imageDataFaxCompressed, this);
Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataFaxCompressed.Length);
PdfArray arrayFilters = new PdfArray(_document);
arrayFilters.Elements.Add(new PdfName("/FlateDecode"));
arrayFilters.Elements.Add(new PdfName("/CCITTFaxDecode"));
Elements[PdfStream.Keys.Filter] = arrayFilters;
PdfArray arrayDecodeParms = new PdfArray(_document);
PdfDictionary dictFlateDecodeParms = new PdfDictionary();
//dictFlateDecodeParms.Elements.Add("/Columns", new PdfInteger(1));
PdfDictionary dictCcittFaxDecodeParms = new PdfDictionary();
if (k != 0)
dictCcittFaxDecodeParms.Elements.Add("/K", new PdfInteger(k));
if (isBitonal < 0)
dictCcittFaxDecodeParms.Elements.Add("/BlackIs1", new PdfBoolean(true));
dictCcittFaxDecodeParms.Elements.Add("/EndOfBlock", new PdfBoolean(false));
dictCcittFaxDecodeParms.Elements.Add("/Columns", new PdfInteger(width));
dictCcittFaxDecodeParms.Elements.Add("/Rows", new PdfInteger(height));
arrayDecodeParms.Elements.Add(dictFlateDecodeParms); // How to add the "null object"?
arrayDecodeParms.Elements.Add(dictCcittFaxDecodeParms);
Elements[PdfStream.Keys.DecodeParms] = arrayDecodeParms;
}
}
else
{
// /FlateDecode creates the smaller file (or no monochrome bitmap):
Stream = new PdfStream(imageDataCompressed, this);
Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataCompressed.Length);
Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
}
Elements[Keys.Width] = new PdfInteger(width);
Elements[Keys.Height] = new PdfInteger(height);
Elements[Keys.BitsPerComponent] = new PdfInteger(bits);
// TODO: CMYK
// CCITT encoding: we need color palette for isBitonal == 0
// FlateDecode: we need color palette for isBitonal <= 0 unless we have grayscales
if ((usesCcittEncoding && isBitonal == 0) ||
(!usesCcittEncoding && isBitonal <= 0 && !isGray))
{
PdfDictionary colorPalette = null;
colorPalette = new PdfDictionary(_document);
byte[] packedPaletteData = paletteData.Length >= 48 ? fd.Encode(paletteData, _document.Options.FlateEncodeMode) : null; // don't compress small palettes
if (packedPaletteData != null && packedPaletteData.Length + 20 < paletteData.Length) // +20: compensate for the overhead (estimated value)
{
// Create compressed color palette:
colorPalette.CreateStream(packedPaletteData);
colorPalette.Elements[PdfStream.Keys.Length] = new PdfInteger(packedPaletteData.Length);
colorPalette.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
}
else
{
// Create uncompressed color palette:
colorPalette.CreateStream(paletteData);
colorPalette.Elements[PdfStream.Keys.Length] = new PdfInteger(paletteData.Length);
}
Owner._irefTable.Add(colorPalette);
PdfArray arrayColorSpace = new PdfArray(_document);
arrayColorSpace.Elements.Add(new PdfName("/Indexed"));
arrayColorSpace.Elements.Add(new PdfName("/DeviceRGB"));
arrayColorSpace.Elements.Add(new PdfInteger(paletteColors - 1));
arrayColorSpace.Elements.Add(colorPalette.Reference);
Elements[Keys.ColorSpace] = arrayColorSpace;
}
else
{
Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
}
if (_image.Interpolate)
Elements[Keys.Interpolate] = PdfBoolean.True;
}
}
///
/// Common keys for all streams.
///
public sealed new class Keys : PdfXObject.Keys
{
// ReSharper disable InconsistentNaming
///
/// (Optional) The type of PDF object that this dictionary describes;
/// if present, must be XObject for an image XObject.
///
[KeyInfo(KeyType.Name | KeyType.Optional)]
public const string Type = "/Type";
///
/// (Required) The type of XObject that this dictionary describes;
/// must be Image for an image XObject.
///
[KeyInfo(KeyType.Name | KeyType.Required)]
public const string Subtype = "/Subtype";
///
/// (Required) The width of the image, in samples.
///
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string Width = "/Width";
///
/// (Required) The height of the image, in samples.
///
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string Height = "/Height";
///
/// (Required for images, except those that use the JPXDecode filter; not allowed for image masks)
/// The color space in which image samples are specified; it can be any type of color space except
/// Pattern. If the image uses the JPXDecode filter, this entry is optional:
/// • If ColorSpace is present, any color space specifications in the JPEG2000 data are ignored.
/// • If ColorSpace is absent, the color space specifications in the JPEG2000 data are used.
/// The Decode array is also ignored unless ImageMask is true.
///
[KeyInfo(KeyType.NameOrArray | KeyType.Required)]
public const string ColorSpace = "/ColorSpace";
///
/// (Required except for image masks and images that use the JPXDecode filter)
/// The number of bits used to represent each color component. Only a single value may be specified;
/// the number of bits is the same for all color components. Valid values are 1, 2, 4, 8, and
/// (in PDF 1.5) 16. If ImageMask is true, this entry is optional, and if specified, its value
/// must be 1.
/// If the image stream uses a filter, the value of BitsPerComponent must be consistent with the
/// size of the data samples that the filter delivers. In particular, a CCITTFaxDecode or JBIG2Decode
/// filter always delivers 1-bit samples, a RunLengthDecode or DCTDecode filter delivers 8-bit samples,
/// and an LZWDecode or FlateDecode filter delivers samples of a specified size if a predictor function
/// is used.
/// If the image stream uses the JPXDecode filter, this entry is optional and ignored if present.
/// The bit depth is determined in the process of decoding the JPEG2000 image.
///
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string BitsPerComponent = "/BitsPerComponent";
///
/// (Optional; PDF 1.1) The name of a color rendering intent to be used in rendering the image.
/// Default value: the current rendering intent in the graphics state.
///
[KeyInfo(KeyType.Name | KeyType.Optional)]
public const string Intent = "/Intent";
///
/// (Optional) A flag indicating whether the image is to be treated as an image mask.
/// If this flag is true, the value of BitsPerComponent must be 1 and Mask and ColorSpace should
/// not be specified; unmasked areas are painted using the current nonstroking color.
/// Default value: false.
///
[KeyInfo(KeyType.Boolean | KeyType.Optional)]
public const string ImageMask = "/ImageMask";
///
/// (Optional except for image masks; not allowed for image masks; PDF 1.3)
/// An image XObject defining an image mask to be applied to this image, or an array specifying
/// a range of colors to be applied to it as a color key mask. If ImageMask is true, this entry
/// must not be present.
///
[KeyInfo(KeyType.StreamOrArray | KeyType.Optional)]
public const string Mask = "/Mask";
///
/// (Optional) An array of numbers describing how to map image samples into the range of values
/// appropriate for the image’s color space. If ImageMask is true, the array must be either
/// [0 1] or [1 0]; otherwise, its length must be twice the number of color components required
/// by ColorSpace. If the image uses the JPXDecode filter and ImageMask is false, Decode is ignored.
/// Default value: see “Decode Arrays”.
///
[KeyInfo(KeyType.Array | KeyType.Optional)]
public const string Decode = "/Decode";
///
/// (Optional) A flag indicating whether image interpolation is to be performed.
/// Default value: false.
///
[KeyInfo(KeyType.Boolean | KeyType.Optional)]
public const string Interpolate = "/Interpolate";
///
/// (Optional; PDF 1.3) An array of alternate image dictionaries for this image. The order of
/// elements within the array has no significance. This entry may not be present in an image
/// XObject that is itself an alternate image.
///
[KeyInfo(KeyType.Array | KeyType.Optional)]
public const string Alternates = "/Alternates";
///
/// (Optional; PDF 1.4) A subsidiary image XObject defining a soft-mask image to be used as a
/// source of mask shape or mask opacity values in the transparent imaging model. The alpha
/// source parameter in the graphics state determines whether the mask values are interpreted as
/// shape or opacity. If present, this entry overrides the current soft mask in the graphics state,
/// as well as the image’s Mask entry, if any. (However, the other transparency related graphics
/// state parameters — blend mode and alpha constant — remain in effect.) If SMask is absent, the
/// image has no associated soft mask (although the current soft mask in the graphics state may
/// still apply).
///
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string SMask = "/SMask";
///
/// (Optional for images that use the JPXDecode filter, meaningless otherwise; PDF 1.5)
/// A code specifying how soft-mask information encoded with image samples should be used:
/// 0 If present, encoded soft-mask image information should be ignored.
/// 1 The image’s data stream includes encoded soft-mask values. An application can create
/// a soft-mask image from the information to be used as a source of mask shape or mask
/// opacity in the transparency imaging model.
/// 2 The image’s data stream includes color channels that have been preblended with a
/// background; the image data also includes an opacity channel. An application can create
/// a soft-mask image with a Matte entry from the opacity channel information to be used as
/// a source of mask shape or mask opacity in the transparency model. If this entry has a
/// nonzero value, SMask should not be specified.
/// Default value: 0.
///
[KeyInfo(KeyType.Integer | KeyType.Optional)]
public const string SMaskInData = "/SMaskInData";
///
/// (Required in PDF 1.0; optional otherwise) The name by which this image XObject is
/// referenced in the XObject subdictionary of the current resource dictionary.
///
[KeyInfo(KeyType.Name | KeyType.Optional)]
public const string Name = "/Name";
///
/// (Required if the image is a structural content item; PDF 1.3) The integer key of the
/// image’s entry in the structural parent tree.
///
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string StructParent = "/StructParent";
///
/// (Optional; PDF 1.3; indirect reference preferred) The digital identifier of the image’s
/// parent Web Capture content set.
///
[KeyInfo(KeyType.String | KeyType.Optional)]
public const string ID = "/ID";
///
/// (Optional; PDF 1.2) An OPI version dictionary for the image. If ImageMask is true,
/// this entry is ignored.
///
[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
public const string OPI = "/OPI";
///
/// (Optional; PDF 1.4) A metadata stream containing metadata for the image.
///
[KeyInfo(KeyType.Stream | KeyType.Optional)]
public const string Metadata = "/Metadata";
///
/// (Optional; PDF 1.5) An optional content group or optional content membership dictionary,
/// specifying the optional content properties for this image XObject. Before the image is
/// processed, its visibility is determined based on this entry. If it is determined to be
/// invisible, the entire image is skipped, as if there were no Do operator to invoke it.
///
[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
public const string OC = "/OC";
// ReSharper restore InconsistentNaming
}
}
///
/// Helper class for creating bitmap masks (8 pels per byte).
///
class MonochromeMask
{
///
/// Returns the bitmap mask that will be written to PDF.
///
public byte[] MaskData
{
get { return _maskData; }
}
private readonly byte[] _maskData;
///
/// Creates a bitmap mask.
///
public MonochromeMask(int sizeX, int sizeY)
{
_sizeX = sizeX;
_sizeY = sizeY;
int byteSize = ((sizeX + 7) / 8) * sizeY;
_maskData = new byte[byteSize];
StartLine(0);
}
///
/// Starts a new line.
///
public void StartLine(int newCurrentLine)
{
_bitsWritten = 0;
_byteBuffer = 0;
_writeOffset = ((_sizeX + 7) / 8) * (_sizeY - 1 - newCurrentLine);
}
///
/// Adds a pel to the current line.
///
///
public void AddPel(bool isTransparent)
{
if (_bitsWritten < _sizeX)
{
// Mask: 0: opaque, 1: transparent (default mapping)
if (isTransparent)
_byteBuffer = (_byteBuffer << 1) + 1;
else
_byteBuffer = _byteBuffer << 1;
++_bitsWritten;
if ((_bitsWritten & 7) == 0)
{
_maskData[_writeOffset] = (byte)_byteBuffer;
++_writeOffset;
_byteBuffer = 0;
}
else if (_bitsWritten == _sizeX)
{
int n = 8 - (_bitsWritten & 7);
_byteBuffer = _byteBuffer << n;
_maskData[_writeOffset] = (byte)_byteBuffer;
}
}
}
///
/// Adds a pel from an alpha mask value.
///
public void AddPel(int shade)
{
// NYI: dithering!!!
AddPel(shade < 128);
}
private readonly int _sizeX;
private readonly int _sizeY;
private int _writeOffset;
private int _byteBuffer;
private int _bitsWritten;
}
}