#region PDFsharp - A .NET library for processing PDF // // Authors: // Thomas Hövel // // Copyright (c) 2005-2017 empira Software GmbH, Cologne Area (Germany) // // http://www.pdfsharp.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 PdfSharp.Pdf; namespace PdfSharp.Drawing.Internal { // ReSharper disable once InconsistentNaming internal class ImageImporterJpeg : ImageImporterRoot, IImageImporter { // TODO Find information about JPEG2000. // Notes: JFIF is big-endian. public ImportedImage ImportImage(StreamReaderHelper stream, PdfDocument document) { try { stream.CurrentOffset = 0; // Test 2 magic bytes. if (TestFileHeader(stream)) { // Skip over 2 magic bytes. stream.CurrentOffset += 2; ImagePrivateDataDct ipd = new ImagePrivateDataDct(stream.Data, stream.Length); ImportedImage ii = new ImportedImageJpeg(this, ipd, document); if (TestJfifHeader(stream, ii)) { bool colorHeader = false, infoHeader = false; while (MoveToNextHeader(stream)) { if (TestColorFormatHeader(stream, ii)) { colorHeader = true; } else if (TestInfoHeader(stream, ii)) { infoHeader = true; } } if (colorHeader && infoHeader) return ii; } } } // ReSharper disable once EmptyGeneralCatchClause catch (Exception) { } return null; } private bool TestFileHeader(StreamReaderHelper stream) { // File must start with 0xffd8. return stream.GetWord(0, true) == 0xffd8; } private bool TestJfifHeader(StreamReaderHelper stream, ImportedImage ii) { // The App0 header should be the first header in every JFIF file. if (stream.GetWord(0, true) == 0xffe0) { // Now check for text "JFIF". if (stream.GetDWord(4, true) == 0x4a464946) { int blockLength = stream.GetWord(2, true); if (blockLength >= 16) { int version = stream.GetWord(9, true); int units = stream.GetByte(11); int densityX = stream.GetWord(12, true); int densityY = stream.GetWord(14, true); switch (units) { case 0: // Aspect ratio only. ii.Information.HorizontalAspectRatio = densityX; ii.Information.VerticalAspectRatio = densityY; break; case 1: // DPI. ii.Information.HorizontalDPI = densityX; ii.Information.VerticalDPI = densityY; break; case 2: // DPCM. ii.Information.HorizontalDPM = densityX * 100; ii.Information.VerticalDPM = densityY * 100; break; } // More information here? More tests? return true; } } } return false; } private bool TestColorFormatHeader(StreamReaderHelper stream, ImportedImage ii) { // The SOS header (start of scan). if (stream.GetWord(0, true) == 0xffda) { int components = stream.GetByte(4); if (components < 1 || components > 4 || components == 2) return false; // 1 for grayscale, 3 for RGB, 4 for CMYK. int blockLength = stream.GetWord(2, true); // Integrity check: correct size? if (blockLength != 6 + 2 * components) return false; // Eventually do more tests here. // Magic: we assume that all JPEG files with 4 components are RGBW (inverted CMYK) and not CMYK. // We add a test to tell CMYK from RGBW when we encounter a test file in CMYK format. ii.Information.ImageFormat = components == 3 ? ImageInformation.ImageFormats.JPEG : (components == 1 ? ImageInformation.ImageFormats.JPEGGRAY : ImageInformation.ImageFormats.JPEGRGBW); return true; } return false; } private bool TestInfoHeader(StreamReaderHelper stream, ImportedImage ii) { // The SOF header (start of frame). int header = stream.GetWord(0, true); if (header >= 0xffc0 && header <= 0xffc3 || header >= 0xffc9 && header <= 0xffcb) { // Lines in image. int sizeY = stream.GetWord(5, true); // Samples per line. int sizeX = stream.GetWord(7, true); // $THHO TODO: Check if we always get useful information here. ii.Information.Width = (uint)sizeX; ii.Information.Height = (uint)sizeY; return true; } return false; } private bool MoveToNextHeader(StreamReaderHelper stream) { int blockLength = stream.GetWord(2, true); int headerMagic = stream.GetByte(0); int headerType = stream.GetByte(1); if (headerMagic == 0xff) { // EOI: last header. if (headerType == 0xd9) return false; // Check for standalone markers. if (headerType == 0x01 || headerType >= 0xd0 && headerType <= 0xd7) { stream.CurrentOffset += 2; return true; } // Now assume header with block size. stream.CurrentOffset += 2 + blockLength; return true; } return false; } public ImageData PrepareImage(ImagePrivateData data) { throw new NotImplementedException(); } //int GetJpgSizeTestCode(byte[] pData, uint FileSizeLow, out int pWidth, out int pHeight) //{ // pWidth = -1; // pHeight = -1; // int i = 0; // if ((pData[i] == 0xFF) && (pData[i + 1] == 0xD8) && (pData[i + 2] == 0xFF) && (pData[i + 3] == 0xE0)) // { // i += 4; // // Check for valid JPEG header (null terminated JFIF) // if ((pData[i + 2] == 'J') && (pData[i + 3] == 'F') && (pData[i + 4] == 'I') && (pData[i + 5] == 'F') // && (pData[i + 6] == 0x00)) // { // //Retrieve the block length of the first block since the first block will not contain the size of file // int block_length = pData[i] * 256 + pData[i + 1]; // while (i < FileSizeLow) // { // //Increase the file index to get to the next block // i += block_length; // if (i >= FileSizeLow) // { // //Check to protect against segmentation faults // return -1; // } // if (pData[i] != 0xFF) // { // return -2; // } // if (pData[i + 1] == 0xC0) // { // //0xFFC0 is the "Start of frame" marker which contains the file size // //The structure of the 0xFFC0 block is quite simple [0xFFC0][ushort length][uchar precision][ushort x][ushort y] // pHeight = pData[i + 5] * 256 + pData[i + 6]; // pWidth = pData[i + 7] * 256 + pData[i + 8]; // return 0; // } // else // { // i += 2; //Skip the block marker // //Go to the next block // block_length = pData[i] * 256 + pData[i + 1]; // } // } // //If this point is reached then no size was found // return -3; // } // else // { // return -4; // } //Not a valid JFIF string // } // else // { // return -5; // } //Not a valid SOI header // //return -6; //} // GetJpgSize } /// /// Imported JPEG image. /// internal class ImportedImageJpeg : ImportedImage { /// /// Initializes a new instance of the class. /// public ImportedImageJpeg(IImageImporter importer, ImagePrivateDataDct data, PdfDocument document) : base(importer, data, document) { } internal override ImageData PrepareImageData() { ImagePrivateDataDct data = (ImagePrivateDataDct)Data; ImageDataDct imageData = new ImageDataDct(); imageData.Data = data.Data; imageData.Length = data.Length; return imageData; } } /// /// Contains data needed for PDF. Will be prepared when needed. /// internal class ImageDataDct : ImageData { /// /// Gets the data. /// public byte[] Data { get { return _data; } internal set { _data = value; } } private byte[] _data; /// /// Gets the length. /// public int Length { get { return _length; } internal set { _length = value; } } private int _length; } /*internal*/ /// /// Private data for JPEG images. /// internal class ImagePrivateDataDct : ImagePrivateData { /// /// Initializes a new instance of the class. /// public ImagePrivateDataDct(byte[] data, int length) { _data = data; _length = length; } /// /// Gets the data. /// public byte[] Data { get { return _data; } //internal set { _data = value; } } private readonly byte[] _data; /// /// Gets the length. /// public int Length { get { return _length; } //internal set { _length = value; } } private readonly int _length; } }