364 lines
12 KiB
C#
364 lines
12 KiB
C#
|
#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
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Imported JPEG image.
|
|||
|
/// </summary>
|
|||
|
internal class ImportedImageJpeg : ImportedImage
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Initializes a new instance of the <see cref="ImportedImageJpeg"/> class.
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Contains data needed for PDF. Will be prepared when needed.
|
|||
|
/// </summary>
|
|||
|
internal class ImageDataDct : ImageData
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Gets the data.
|
|||
|
/// </summary>
|
|||
|
public byte[] Data
|
|||
|
{
|
|||
|
get { return _data; }
|
|||
|
internal set { _data = value; }
|
|||
|
}
|
|||
|
private byte[] _data;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the length.
|
|||
|
/// </summary>
|
|||
|
public int Length
|
|||
|
{
|
|||
|
get { return _length; }
|
|||
|
internal set { _length = value; }
|
|||
|
}
|
|||
|
private int _length;
|
|||
|
}
|
|||
|
|
|||
|
/*internal*/
|
|||
|
/// <summary>
|
|||
|
/// Private data for JPEG images.
|
|||
|
/// </summary>
|
|||
|
internal class ImagePrivateDataDct : ImagePrivateData
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// Initializes a new instance of the <see cref="ImagePrivateDataDct"/> class.
|
|||
|
/// </summary>
|
|||
|
public ImagePrivateDataDct(byte[] data, int length)
|
|||
|
{
|
|||
|
_data = data;
|
|||
|
_length = length;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the data.
|
|||
|
/// </summary>
|
|||
|
public byte[] Data
|
|||
|
{
|
|||
|
get { return _data; }
|
|||
|
//internal set { _data = value; }
|
|||
|
}
|
|||
|
private readonly byte[] _data;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the length.
|
|||
|
/// </summary>
|
|||
|
public int Length
|
|||
|
{
|
|||
|
get { return _length; }
|
|||
|
//internal set { _length = value; }
|
|||
|
}
|
|||
|
private readonly int _length;
|
|||
|
}
|
|||
|
}
|