/// <summary> /// Creates the keys for a JPEG image. /// </summary> 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"); }
/* 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; } } }
/// <summary> /// Reads images that are returned from GDI+ without color palette. /// </summary> /// <param name="components">4 (32bpp RGB), 3 (24bpp RGB, 32bpp ARGB)</param> /// <param name="bits">8</param> /// <param name="hasAlpha">true (ARGB), false (RGB)</param> 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; } } }