/* 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*/) { #if DEBUG_ image.image.Save("$$$.bmp", ImageFormat.Bmp); #endif int pdfVersion = Owner.Version; int firstMaskColor = -1, lastMaskColor = -1; bool segmentedColorMask = false; MemoryStream memory = new MemoryStream(); #if GDI image.gdiImage.Save(memory, ImageFormat.Bmp); #endif #if WPF #if !SILVERLIGHT // WPFTHHO: StL: keine Ahnung ob das so stimmt. BmpBitmapEncoder encoder = new BmpBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(image.wpfImage)); encoder.Save(memory); #else // AGHACK #endif #endif 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.Close(); int height = image.PixelHeight; int width = image.PixelWidth; if (ReadWord(imageBits, 0) != 0x4d42 || // "BM" ReadDWord(imageBits, 2) != streamLength || ReadDWord(imageBits, 14) != 40 || // sizeof BITMAPINFOHEADER #if WPF // TODOWPF: bug with height and width??? With which files??? ReadDWord(imageBits, 18) != width || ReadDWord(imageBits, 22) != height) #else ReadDWord(imageBits, 18) != width || ReadDWord(imageBits, 22) != height) #endif { throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format"); } #if WPF // TODOWPF: bug with height and width width = ReadDWord(imageBits, 18); height = ReadDWord(imageBits, 22); #endif 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); 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) // { ... } FlateDecode fd = new FlateDecode(); PdfDictionary colorPalette = null; if (isBitonal == 0 && !isGray) { colorPalette = new PdfDictionary(this.document); byte[] packedPaletteData = paletteData.Length >= 48 ? fd.Encode(paletteData) : 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[Keys.Length] = new PdfInteger(packedPaletteData.Length); colorPalette.Elements[Keys.Filter] = new PdfName("/FlateDecode"); } else { // Create uncompressed color palette: colorPalette.CreateStream(paletteData); colorPalette.Elements[Keys.Length] = new PdfInteger(paletteData.Length); } Owner.irefTable.Add(colorPalette); } 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"); } } if (firstMaskColor != -1 && lastMaskColor != -1) { // Color mask requires Reader 4.0 or higher: if (!segmentedColorMask && pdfVersion >= 13) { 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); PdfDictionary pdfMask = new PdfDictionary(document); pdfMask.Elements.SetName(Keys.Type, "/XObject"); pdfMask.Elements.SetName(Keys.Subtype, "/Image"); this.Owner.irefTable.Add(pdfMask); pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask); pdfMask.Elements[Keys.Length] = new PdfInteger(maskDataCompressed.Length); pdfMask.Elements[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); byte[] imageDataFaxCompressed = isFaxEncoding ? fd.Encode(imageDataFax) : 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[Keys.Length] = new PdfInteger(imageDataFax.Length); Elements[Keys.Filter] = new PdfName("/CCITTFaxDecode"); //PdfArray array2 = new PdfArray(this.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[Keys.DecodeParms] = dictionary; // array2; } else { Stream = new PdfStream(imageDataFaxCompressed, this); Elements[Keys.Length] = new PdfInteger(imageDataFaxCompressed.Length); PdfArray arrayFilters = new PdfArray(document); arrayFilters.Elements.Add(new PdfName("/FlateDecode")); arrayFilters.Elements.Add(new PdfName("/CCITTFaxDecode")); Elements[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[Keys.DecodeParms] = arrayDecodeParms; } } else { // /FlateDecode creates the smaller file (or no monochrome bitmap): Stream = new PdfStream(imageDataCompressed, this); Elements[Keys.Length] = new PdfInteger(imageDataCompressed.Length); Elements[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)) { PdfArray arrayColorSpace = new PdfArray(document); arrayColorSpace.Elements.Add(new PdfName("/Indexed")); arrayColorSpace.Elements.Add(new PdfName("/DeviceRGB")); arrayColorSpace.Elements.Add(new PdfInteger(paletteColors - 1)); // ReSharper disable PossibleNullReferenceException arrayColorSpace.Elements.Add(colorPalette.Reference); // ReSharper restore PossibleNullReferenceException Elements[Keys.ColorSpace] = arrayColorSpace; } else { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } if (image.Interpolate) Elements[Keys.Interpolate] = PdfBoolean.True; } }
/// <summary> /// Creates the keys for a JPEG image. /// </summary> void InitializeJpeg() { // PDF support JPEG, so there's not much to be done MemoryStream memory; byte[] imageBits = null; int streamLength = 0; #if GDI memory = new MemoryStream(); image.gdiImage.Save(memory, ImageFormat.Jpeg); if ((int)memory.Length == 0) { Debug.Assert(false, "Internal error? JPEG image, but file not found!"); } #endif #if WPF && !SILVERLIGHT // AGHACK //string filename = XImage.GetImageFilename(image.wpfImage); //if (XImage.ReadJpegFile(filename, -1, ref imageBits)) //{ // streamLength = imageBits.Length; //} //else // imageBits = null; memory = image.Memory; #endif if (imageBits == null) { streamLength = (int)memory.Length; imageBits = new byte[streamLength]; memory.Seek(0, SeekOrigin.Begin); memory.Read(imageBits, 0, streamLength); memory.Close(); } FlateDecode fd = new FlateDecode(); byte[] imageDataCompressed = fd.Encode(imageBits); if (imageDataCompressed.Length < imageBits.Length) { Stream = new PdfStream(imageDataCompressed, this); Elements[Keys.Length] = new PdfInteger(imageDataCompressed.Length); PdfArray arrayFilters = new PdfArray(document); arrayFilters.Elements.Add(new PdfName("/FlateDecode")); arrayFilters.Elements.Add(new PdfName("/DCTDecode")); Elements[Keys.Filter] = arrayFilters; } else { Stream = new PdfStream(imageBits, this); Elements[Keys.Length] = new PdfInteger(streamLength); Elements[Keys.Filter] = new PdfName("/DCTDecode"); } Elements[Keys.Width] = new PdfInteger(image.PixelWidth); Elements[Keys.Height] = new PdfInteger(image.PixelHeight); Elements[Keys.BitsPerComponent] = new PdfInteger(8); #if GDI if ((image.gdiImage.Flags & ((int)ImageFlags.ColorSpaceCmyk | (int)ImageFlags.ColorSpaceYcck)) != 0) { // TODO: Test with CMYK JPEG files // THHO: I only found ImageFlags.ColorSpaceYcck JPEG files ... Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK"); if ((image.gdiImage.Flags & (int)ImageFlags.ColorSpaceYcck) != 0) Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors? Why?? } else if ((image.gdiImage.Flags & (int)ImageFlags.ColorSpaceGray) != 0) { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } else { Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); } #endif #if WPF // TODOWPF // WPFTHHO #if !SILVERLIGHT string pixelFormat = image.wpfImage.Format.ToString(); #else string pixelFormat = "xxx"; #endif bool isCmyk = image.IsCmyk; bool isGrey = pixelFormat == "Gray8"; if (isCmyk) { // TODO: Test with CMYK JPEG files // THHO: I only found ImageFlags.ColorSpaceYcck JPEG files ... Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK"); Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors? Why?? } else if (isGrey) { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } else { Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); } #endif }
/// <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) { #if DEBUG_ image.image.Save("$$$.bmp", ImageFormat.Bmp); #endif int pdfVersion = Owner.Version; MemoryStream memory = new MemoryStream(); #if GDI image.gdiImage.Save(memory, ImageFormat.Bmp); #endif #if WPF #if !SILVERLIGHT // WPFTHHO: Bitte prüfen BmpBitmapEncoder encoder = new BmpBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(image.wpfImage)); encoder.Save(memory); #else // AGHACK #endif #endif 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.Close(); 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) 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("ReadTrueColorMemoryBitmap: unsupported format"); } if (ReadWord(imageBits, 26) != 1 || (!hasAlpha && ReadWord(imageBits, 28) != components * bits || hasAlpha && ReadWord(imageBits, 28) != (components + 1) * bits) || ReadDWord(imageBits, 30) != 0) { throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format #2"); } 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); 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[Keys.Length] = new PdfInteger(maskDataCompressed.Length); pdfMask.Elements[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); 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[Keys.Length] = new PdfInteger(alphaMaskCompressed.Length); smask.Elements[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); Stream = new PdfStream(imageDataCompressed, this); Elements[Keys.Length] = new PdfInteger(imageDataCompressed.Length); Elements[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; } }
private void CreateTrueColorMemoryBitmap(int components, int bits, bool hasAlpha) { int pdfVersion = Owner.Version; FlateDecode fd = new FlateDecode(); ImageDataBitmap idb = (ImageDataBitmap)_image._importedImage.ImageData; ImageInformation ii = _image._importedImage.Information; bool hasMask = idb.AlphaMaskLength > 0 || idb.BitmapMaskLength > 0; bool hasAlphaMask = idb.AlphaMaskLength > 0; if (hasMask) { // monochrome mask is either sufficient or // provided for compatibility with older reader versions byte[] maskDataCompressed = fd.Encode(idb.BitmapMask, _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((int)ii.Width); pdfMask.Elements[Keys.Height] = new PdfInteger((int)ii.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(idb.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((int)ii.Width); smask.Elements[Keys.Height] = new PdfInteger((int)ii.Height); smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8); smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); Elements[Keys.SMask] = smask.Reference; } byte[] imageDataCompressed = fd.Encode(idb.Data, _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((int)ii.Width); Elements[Keys.Height] = new PdfInteger((int)ii.Height); Elements[Keys.BitsPerComponent] = new PdfInteger(8); // TODO: CMYK Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); if (_image.Interpolate) Elements[Keys.Interpolate] = PdfBoolean.True; }
private void CreateIndexedMemoryBitmap(int bits) { ImageDataBitmap idb = (ImageDataBitmap)_image._importedImage.ImageData; ImageInformation ii = _image._importedImage.Information; int pdfVersion = Owner.Version; int firstMaskColor = -1, lastMaskColor = -1; bool segmentedColorMask = idb.SegmentedColorMask; { 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 && !idb.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(idb.BitmapMask, _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((int)ii.Width); pdfMask.Elements[Keys.Height] = new PdfInteger((int)ii.Height); pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1); pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true); Elements[Keys.Mask] = pdfMask.Reference; } } byte[] imageDataCompressed = fd.Encode(idb.Data, _document.Options.FlateEncodeMode); byte[] imageDataFaxCompressed = idb.DataFax != null ? fd.Encode(idb.DataFax, _document.Options.FlateEncodeMode) : null; bool usesCcittEncoding = false; if (idb.DataFax != null && (idb.LengthFax < imageDataCompressed.Length || imageDataFaxCompressed.Length < imageDataCompressed.Length)) { // /CCITTFaxDecode creates the smaller file (with or without /FlateDecode): usesCcittEncoding = true; if (idb.LengthFax < imageDataCompressed.Length) { Stream = new PdfStream(idb.DataFax, this); Elements[PdfStream.Keys.Length] = new PdfInteger(idb.LengthFax); Elements[PdfStream.Keys.Filter] = new PdfName("/CCITTFaxDecode"); //PdfArray array2 = new PdfArray(_document); PdfDictionary dictionary = new PdfDictionary(); if (idb.K != 0) dictionary.Elements.Add("/K", new PdfInteger(idb.K)); if (idb.IsBitonal < 0) dictionary.Elements.Add("/BlackIs1", new PdfBoolean(true)); dictionary.Elements.Add("/EndOfBlock", new PdfBoolean(false)); dictionary.Elements.Add("/Columns", new PdfInteger((int)ii.Width)); dictionary.Elements.Add("/Rows", new PdfInteger((int)ii.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 (idb.K != 0) dictCcittFaxDecodeParms.Elements.Add("/K", new PdfInteger(idb.K)); if (idb.IsBitonal < 0) dictCcittFaxDecodeParms.Elements.Add("/BlackIs1", new PdfBoolean(true)); dictCcittFaxDecodeParms.Elements.Add("/EndOfBlock", new PdfBoolean(false)); dictCcittFaxDecodeParms.Elements.Add("/Columns", new PdfInteger((int)ii.Width)); dictCcittFaxDecodeParms.Elements.Add("/Rows", new PdfInteger((int)ii.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((int)ii.Width); Elements[Keys.Height] = new PdfInteger((int)ii.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 && idb.IsBitonal == 0) || (!usesCcittEncoding && idb.IsBitonal <= 0 && !idb.IsGray)) { PdfDictionary colorPalette = null; colorPalette = new PdfDictionary(_document); byte[] packedPaletteData = idb.PaletteDataLength >= 48 ? fd.Encode(idb.PaletteData, _document.Options.FlateEncodeMode) : null; // don't compress small palettes if (packedPaletteData != null && packedPaletteData.Length + 20 < idb.PaletteDataLength) // +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(idb.PaletteData); colorPalette.Elements[PdfStream.Keys.Length] = new PdfInteger(idb.PaletteDataLength); } 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((int)ii.ColorsUsed - 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> /// Creates the keys for a JPEG image. /// </summary> void InitializeJpeg() { // PDF supports JPEG, so there's not much to be done. MemoryStream memory = null; // Close the MemoryStream if we create it. bool ownMemory = false; byte[] imageBits = null; int streamLength = 0; #if CORE || GDI || WPF if (_image._importedImage != null) { ImageDataDct idd = (ImageDataDct)_image._importedImage.ImageData; imageBits = idd.Data; streamLength = idd.Length; } #endif #if CORE || GDI if (_image._importedImage == null) { if (!_image._path.StartsWith("*")) { // Image does not come from a stream, so we have the path to the file - just use the path. // If the image was modified in memory, those changes will be lost and the original image, as it was read from the file, will be added to the PDF. using (FileStream sourceFile = File.OpenRead(_image._path)) { int count; byte[] buffer = new byte[8192]; memory = new MemoryStream((int)sourceFile.Length); ownMemory = true; do { count = sourceFile.Read(buffer, 0, buffer.Length); // memory.Write(buffer, 0, buffer.Length); memory.Write(buffer, 0, count); } while (count > 0); } } else { memory = new MemoryStream(); ownMemory = true; // If we have a stream, copy data from the stream. if (_image._stream != null && _image._stream.CanSeek) { Stream stream = _image._stream; stream.Seek(0, SeekOrigin.Begin); byte[] buffer = new byte[32 * 1024]; // 32K buffer. int bytesRead; while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) { memory.Write(buffer, 0, bytesRead); } } else { #if CORE_WITH_GDI // No stream, no filename, get image data. // Save the image to a memory stream. _image._gdiImage.Save(memory, ImageFormat.Jpeg); #endif } } if ((int)memory.Length == 0) { Debug.Assert(false, "Internal error? JPEG image, but file not found!"); } } #endif #if WPF // AGHACK //string filename = XImage.GetImageFilename(image._wpfImage); //if (XImage.ReadJpegFile(filename, -1, ref imageBits)) //{ // streamLength = imageBits.Length; //} //else // imageBits = null; #if !SILVERLIGHT memory = _image.Memory; #else memory = new MemoryStream(); ownMemory = true; #endif #endif #if NETFX_CORE memory = new MemoryStream(); ownMemory = true; #endif // THHO4THHO Use ImageImporterJPEG here to avoid redundant code. if (imageBits == null) { streamLength = (int)memory.Length; imageBits = new byte[streamLength]; memory.Seek(0, SeekOrigin.Begin); memory.Read(imageBits, 0, streamLength); // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (ownMemory) { #if UWP || true memory.Dispose(); #else memory.Close(); #endif } } 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(streamLength); 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); #if CORE || GDI || WPF if (_image._importedImage != null) { if (_image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGCMYK || _image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGRGBW) { // TODO: Test with CMYK JPEG files (so far I only found ImageFlags.ColorSpaceYcck JPEG files ...) Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK"); if (_image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGRGBW) Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors from RGBW to CMYK. } else if (_image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGGRAY) { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } else { Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); } } #endif #if CORE_WITH_GDI if (_image._importedImage == null) { if ((_image._gdiImage.Flags & ((int)ImageFlags.ColorSpaceCmyk | (int)ImageFlags.ColorSpaceYcck)) != 0) { // TODO: Test with CMYK JPEG files (so far I only found ImageFlags.ColorSpaceYcck JPEG files ...) Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK"); if ((_image._gdiImage.Flags & (int)ImageFlags.ColorSpaceYcck) != 0) Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors? Why?? } else if ((_image._gdiImage.Flags & (int)ImageFlags.ColorSpaceGray) != 0) { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } else { Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); } } #endif #if GDI if (_image._importedImage == null) { if ((_image._gdiImage.Flags & ((int)ImageFlags.ColorSpaceCmyk | (int)ImageFlags.ColorSpaceYcck)) != 0) { // TODO: Test with CMYK JPEG files (so far I only found ImageFlags.ColorSpaceYcck JPEG files ...) Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK"); if ((_image._gdiImage.Flags & (int)ImageFlags.ColorSpaceYcck) != 0) Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors? Why?? } else if ((_image._gdiImage.Flags & (int)ImageFlags.ColorSpaceGray) != 0) { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } else { Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); } } #endif #if WPF // TODOSILVERLIGHT #if !SILVERLIGHT string pixelFormat = _image._wpfImage.Format.ToString(); #else string pixelFormat = "xxx"; #endif bool isCmyk = _image.IsCmyk; bool isGrey = pixelFormat == "Gray8"; if (isCmyk) { // TODO: Test with CMYK JPEG files (so far I only found ImageFlags.ColorSpaceYcck JPEG files ...) Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK"); Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors? Why?? } else if (isGrey) { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } else { Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); } #endif }
private void ReadTrueColorMemoryBitmapAg(int components, int bits, bool hasAlpha) { int pdfVersion = Owner.Version; MemoryStream memory = new MemoryStream(); WriteableBitmap bitmap = null; if (_image._wpfImage is WriteableBitmap) bitmap = (WriteableBitmap)_image._wpfImage; else if (_image._wpfImage is BitmapImage) bitmap = new WriteableBitmap(_image._wpfImage); if (bitmap != null) { int height = _image.PixelHeight; int width = _image.PixelWidth; 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 * y * width; // 3*(height - 1 - y)*width; int nOffsetWriteAlpha = 0; if (hasAlpha) { mask.StartLine(y); nOffsetWriteAlpha = y * width; // (height - 1 - y) * width; } for (int x = 0; x < width; ++x) { uint pixel = (uint)bitmap.Pixels[nOffsetRead]; imageData[nOffsetWrite] = (byte)(pixel >> 16); imageData[nOffsetWrite + 1] = (byte)(pixel >> 8); imageData[nOffsetWrite + 2] = (byte)(pixel); if (hasAlpha) { byte pel = (byte)(pixel >> 24); mask.AddPel(pel); alphaMask[nOffsetWriteAlpha] = pel; if (!hasMask || !hasAlphaMask) { if (pel != 255) { hasMask = true; if (pel != 0) hasAlphaMask = true; } } ++nOffsetWriteAlpha; } //nOffsetRead += hasAlpha ? 4 : components; ++nOffsetRead; 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[Keys.Length] = new PdfInteger(maskDataCompressed.Length); pdfMask.Elements[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[Keys.Length] = new PdfInteger(alphaMaskCompressed.Length); smask.Elements[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[Keys.Length] = new PdfInteger(imageDataCompressed.Length); Elements[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) { #if DEBUG_ image.image.Save("$$$.bmp", ImageFormat.Bmp); #endif int pdfVersion = this.Owner.Version; int firstMaskColor = -1, lastMaskColor = -1; bool segmentedColorMask = false; MemoryStream memory = new MemoryStream(); #if GDI image.gdiImage.Save(memory, ImageFormat.Bmp); #endif #if WPF // WPFTHHO: StL: keine Ahnung ob das so stimmt. BmpBitmapEncoder encoder = new BmpBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(this.image.wpfImage)); encoder.Save(memory); #endif 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.Close(); int height = this.image.PixelHeight; int width = this.image.PixelWidth; if (ReadWord(imageBits, 0) != 0x4d42 || // "BM" ReadDWord(imageBits, 2) != streamLength || ReadDWord(imageBits, 14) != 40 || // sizeof BITMAPINFOHEADER #if WPF // TODOWPF: bug with height and width false) #else ReadDWord(imageBits, 18) != width || ReadDWord(imageBits, 22) != height) #endif { throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format"); } #if WPF // TODOWPF: bug with height and width width = ReadDWord(imageBits, 18); height = ReadDWord(imageBits, 22); #endif 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); 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); 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 (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: } } // NYI: (no sample found where this was required) // if (segmentedColorMask = true) // { ... } FlateDecode fd = new FlateDecode(); PdfDictionary colorPalette = new PdfDictionary(this.document); // TODO: decide at run-time if compression makes sense #if false // Create uncompressed color palette: colorPalette.CreateStream(paletteData); colorPalette.Elements[Keys.Length] = new PdfInteger(paletteData.Length); #else // Create compressed color palette: byte[] packedPaletteData = fd.Encode(paletteData); colorPalette.CreateStream(packedPaletteData); colorPalette.Elements[Keys.Length] = new PdfInteger(packedPaletteData.Length); colorPalette.Elements[Keys.Filter] = new PdfName("/FlateDecode"); #endif this.Owner.irefTable.Add(colorPalette); byte[] imageData = new byte[1 * width * height]; 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) { imageData[bytesOffsetWrite] = imageBits[bytesFileOffset + bytesOffsetRead]; if (firstMaskColor != -1) { int n = imageBits[bytesFileOffset + bytesOffsetRead]; if (bits == 8) { // TODO???: segmentedColorMask == true => falsche Maske NYI mask.AddPel((n >= firstMaskColor) && (n <= lastMaskColor)); } else if (bits == 4) { // TODO???: segmentedColorMask == true => falsche Maske 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"); } if (firstMaskColor != -1 && lastMaskColor != -1) { // Color mask requires Reader 4.0 or higher: if (!segmentedColorMask && pdfVersion >= 13) { PdfArray array = new PdfArray(this.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); PdfDictionary pdfMask = new PdfDictionary(this.document); pdfMask.Elements.SetName(Keys.Type, "/XObject"); pdfMask.Elements.SetName(Keys.Subtype, "/Image"); this.Owner.irefTable.Add(pdfMask); pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask); pdfMask.Elements[Keys.Length] = new PdfInteger(maskDataCompressed.Length); pdfMask.Elements[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); Stream = new PdfStream(imageDataCompressed, this); Elements[Keys.Length] = new PdfInteger(imageDataCompressed.Length); Elements[Keys.Filter] = new PdfName("/FlateDecode"); Elements[Keys.Width] = new PdfInteger(width); Elements[Keys.Height] = new PdfInteger(height); Elements[Keys.BitsPerComponent] = new PdfInteger(bits); PdfArray array2 = new PdfArray(this.document); array2.Elements.Add(new PdfName("/Indexed")); // TODO: CMYK array2.Elements.Add(new PdfName("/DeviceRGB")); array2.Elements.Add(new PdfInteger(paletteColors - 1)); array2.Elements.Add(colorPalette.Reference); Elements[Keys.ColorSpace] = array2; } }