Files
PDFPatcher/App/Processor/Imaging/ImageInfo.cs
T
2023-01-18 19:29:36 +08:00

487 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using FreeImageAPI;
using iTextSharp.text.pdf;
using PDFPatcher.Model;
namespace PDFPatcher.Processor.Imaging
{
[DebuggerDisplay("REF = {PdfRef}({ReferenceCount}); Size = {Width} * {Height}")]
internal sealed class ImageInfo
{
public string FileName { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int BitsPerComponent { get; private set; }
public PixelFormat PixelFormat { get; private set; }
public int ReferenceCount { get; set; }
public bool VerticalFlip { get; set; }
public string ExtName { get; private set; }
public string LastDecodeError { get; private set; }
public PdfName ColorSpace { get; private set; }
public PdfName PaletteColorSpace { get; private set; }
public byte[] PaletteBytes { get; private set; }
public byte[] ICCProfile { get; private set; }
public FreeImageBitmap Mask { get; private set; }
public int PaletteEntryCount { get; private set; }
public RGBQUAD[] PaletteArray { get; private set; }
public PdfImageData InlineImage { get; private set; }
internal ImageInfo() { }
internal ImageInfo(PdfImageData image) {
InlineImage = image;
}
internal ImageInfo(PdfIndirectReference pdfIndirect) {
InlineImage = new PdfImageData(pdfIndirect);
}
internal ImageInfo(PRStream stream) {
InlineImage = new PdfImageData(stream);
}
internal byte[] DecodeImage(ImageExtracterOptions options) {
return DecodeImage(this, options);
}
private static byte[] DecodeImage(ImageInfo info, ImageExtracterOptions options) {
byte[] decodedBytes;
var data = info.InlineImage;
info.ExtName = Constants.FileExtensions.Dat;
info.Width = data.TryGetInt32(PdfName.WIDTH, 0);
info.Height = data.TryGetInt32(PdfName.HEIGHT, 0);
if (info.Width < options.MinWidth || info.Height < options.MinHeight) {
if (info.InlineImage.PdfRef != null) {
Tracker.TraceMessage(String.Concat("忽略了一幅编号为 ", info.InlineImage.ToString(), ",尺寸为 ", info.Width, "*", info.Height, "的图像。"));
}
else {
Tracker.TraceMessage(String.Concat("忽略了一幅尺寸为 ", info.Width, "*", info.Height, "的内嵌图像。"));
}
return null;
}
info.BitsPerComponent = data.TryGetInt32(PdfName.BITSPERCOMPONENT, 1);
info.PixelFormat = PixelFormat.Format8bppIndexed;
var decParams = PdfHelper.GetObjectDirectOrFromContainerArray(data, PdfName.DECODEPARMS, PdfObject.DICTIONARY);
var filters = PdfHelper.GetObjectDirectOrFromContainerArray(data, PdfName.FILTER, PdfObject.NAME);
decodedBytes = DecodeStreamContent(data, filters);
var filter = filters.Count > 0 ? (filters[filters.Count - 1] as PdfName ?? PdfName.DEFAULT).ToString() : "BMP";
var decParam = decParams.Count > 0 ? decParams[decParams.Count - 1] as PdfDictionary : null;
ExportColorspace(data.GetDirectObject(PdfName.COLORSPACE), info);
switch (filter) {
case "/DCTDecode":
case "/DCT":
info.ExtName = Constants.FileExtensions.Jpg;
goto case "JPG";
case "/JPXDecode":
case "/JPX":
info.ExtName = Constants.FileExtensions.Jp2;
//goto case "JPG";
goto EXIT;
case "/CCITTFaxDecode":
case "/CCF":
case "/JBIG2Decode":
info.ExtName = Constants.FileExtensions.Tif;
var k = 0;
var blackIs1 = false;
var byteAlign = false;
var endOfLine = false;
var endOfBlock = true;
if (decParam != null) {
k = decParam.TryGetInt32(PdfName.K, 0);
blackIs1 = decParam.TryGetBoolean(PdfName.BLACKIS1, false);
byteAlign = decParam.TryGetBoolean(PdfName.ENCODEDBYTEALIGN, false);
endOfBlock = decParam.TryGetBoolean(PdfName.ENDOFBLOCK, true);
endOfLine = decParam.TryGetBoolean(PdfName.ENDOFLINE, false);
}
blackIs1 = IsDecodeParamInverted(data, blackIs1);
if (options.InvertBlackAndWhiteImages) {
blackIs1 = !blackIs1;
}
byte[] outBuf;
if (filter == "/JBIG2Decode") {
var globals = new byte[0];
if (decParam != null) {
var gRef = decParam.GetAsIndirectObject(PdfName.JBIG2GLOBALS);
if (gRef != null) {
var gs = PdfReader.GetPdfObjectRelease(gRef) as PRStream;
if (gs != null) {
globals = PdfReader.GetStreamBytes(gs);
}
}
}
outBuf = Processor.Imaging.JBig2Decoder.Decode(decodedBytes, globals);
if (outBuf == null) {
info.LastDecodeError = "导出 JBig2 编码图片失败。";
return null;
}
if (blackIs1 == false) {
InvertBits(outBuf);
}
}
else {
outBuf = TiffHelper.Decode(info, decodedBytes, k, endOfLine, byteAlign, endOfBlock, blackIs1);
}
info.PixelFormat = PixelFormat.Format1bppIndexed;
info.BitsPerComponent = 1;
decodedBytes = outBuf;
break;
case "/FlateDecode":
case "/Fl":
case "/LZWDecode":
info.ExtName = Constants.FileExtensions.Png;
info.PixelFormat = GetPixelFormat(decodedBytes.Length, info);
if (info.PixelFormat == PixelFormat.Undefined) {
info.LastDecodeError = "无法判定图像的颜色格式。";
info.ExtName = Constants.FileExtensions.Dat;
return null;
}
else if (info.PixelFormat == PixelFormat.Format1bppIndexed) {
blackIs1 = IsDecodeParamInverted(data, false);
if (options.InvertBlackAndWhiteImages) {
blackIs1 = !blackIs1;
}
if (blackIs1) {
InvertBits(decodedBytes);
}
}
break;
case "BMP":
info.ExtName = Constants.FileExtensions.Png;
break;
case "JPG":
if (options.MergeImages == false) {
goto EXIT;
}
using (var ms = new MemoryStream(decodedBytes))
using (var bm = PdfName.DEVICECMYK.Equals(info.ColorSpace)
? new FreeImageBitmap(ms, FREE_IMAGE_LOAD_FLAGS.JPEG_CMYK)
: new FreeImageBitmap(ms)) {
info.PixelFormat = bm.PixelFormat;
switch (bm.ColorType) {
case FREE_IMAGE_COLOR_TYPE.FIC_CMYK:
info.ColorSpace = PdfName.DEVICECMYK;
break;
case FREE_IMAGE_COLOR_TYPE.FIC_MINISBLACK:
case FREE_IMAGE_COLOR_TYPE.FIC_MINISWHITE:
info.ColorSpace = PdfName.DEVICEGRAY;
break;
case FREE_IMAGE_COLOR_TYPE.FIC_PALETTE:
info.ColorSpace = PdfName.INDEXED;
break;
case FREE_IMAGE_COLOR_TYPE.FIC_RGB:
case FREE_IMAGE_COLOR_TYPE.FIC_RGBALPHA:
info.ColorSpace = PdfName.DEVICERGB;
break;
default:
break;
}
info.BitsPerComponent =
info.PixelFormat == PixelFormat.Format1bppIndexed ? 1
: info.PixelFormat == PixelFormat.Format4bppIndexed ? 4
: 8;
}
goto EXIT;
default:
info.PixelFormat = PixelFormat.Undefined;
info.LastDecodeError = "未支持的图像数据格式:" + filter;
return null;
}
if (PdfName.DEVICECMYK.Equals(info.ColorSpace)) {
info.ExtName = Constants.FileExtensions.Tif;
}
EXIT:
PRStream sm;
if (options.ExtractSoftMask && (
(sm = data.GetAsStream(PdfName.SMASK) as PRStream) != null
|| (sm = data.GetAsStream(PdfName.MASK) as PRStream) != null)
) {
var mi = new ImageInfo(sm);
var mask = DecodeImage(mi, new ImageExtracterOptions() { InvertBlackAndWhiteImages = !options.InvertSoftMask });
if (mask != null) {
info.Mask = ImageExtractor.CreateFreeImageBitmap(mi, ref mask, options.VerticalFlipImages, false);
}
}
return decodedBytes;
}
static void InvertBits(byte[] outBuf) {
int len = outBuf.Length;
for (int t = 0; t < len; ++t) {
outBuf[t] ^= 0xff;
}
}
internal void CreatePaletteAndIccProfile(FreeImageBitmap bmp) {
if (PixelFormat == PixelFormat.Format1bppIndexed) {
ColorSpace = PdfName.DEVICEGRAY;
}
CreatePalette(bmp);
if (PaletteEntryCount > 0) {
if (PaletteEntryCount < 3) {
PixelFormat = PixelFormat.Format1bppIndexed;
bmp.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_01_BPP);
}
else if (PaletteEntryCount < 17) {
PixelFormat = PixelFormat.Format4bppIndexed;
bmp.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_04_BPP);
}
}
if (ICCProfile != null) {
bmp.CreateICCProfile(ICCProfile);
}
}
void CreatePalette(FreeImageBitmap bmp) {
//if (PaletteColorSpace == null) {
// //todo++ 缺少色域信息的图片不一定是灰度图像
// if (bmp.HasPalette) {
// PaletteColorSpace = PdfName.DEVICEGRAY;
// }
// else {
// return;
// }
//}
if (bmp.HasPalette == false) {
Trace.WriteLine("Bitmap does not have palette.");
return;
}
var pal = bmp.Palette;
if (PdfName.DEVICEGRAY.Equals(PaletteColorSpace)) {
if (PaletteBytes == null) {
pal.CreateGrayscalePalette();
PaletteEntryCount = pal.Count;
}
else {
var pattern = PaletteBytes;
var l = pattern.Length;
var l2 = pal.Count;
int i;
byte p;
for (i = 0; i < l && i < l2; i++) {
p = pattern[i];
pal.SetValue(new RGBQUAD(Color.FromArgb(p, p, p)), i);
}
PaletteEntryCount = i;
}
}
else {
var pattern = PaletteBytes;
if (pattern == null) {
bmp.Palette.CreateGrayscalePalette();
return;
}
var i = 0;
var l = pattern.Length;
var l2 = pal.Count;
for (int pi = 0; pi < l && i < l2; pi++) {
pal.SetValue(new RGBQUAD(Color.FromArgb(pattern[pi++], pi < l ? pattern[pi++] : 0, pi < l ? pattern[pi] : 0)), i);
i++;
}
PaletteEntryCount = i;
}
PaletteArray = pal.AsArray;
}
static bool IsDecodeParamInverted(PdfDictionary data, bool blackIs1) {
var a = data.GetAsArray(PdfName.DECODE);
if (a?.Size == 2 && a[0].Type == PdfObject.NUMBER) {
blackIs1 = ((PdfNumber)a[0]).IntValue == (blackIs1 ? 0 : 1);
}
return blackIs1;
}
static byte[] DecodeStreamContent(PdfImageData data, IList<PdfObject> filters) {
var buffer = data.RawBytes;
if (filters.Count == 0) {
return buffer;
}
var dp = new List<PdfObject>();
var dpo = PdfReader.GetPdfObjectRelease(data.Get(PdfName.DECODEPARMS));
if (dpo == null || (!dpo.IsDictionary() && !dpo.IsArray()))
dpo = PdfReader.GetPdfObjectRelease(data.Get(PdfName.DP));
if (dpo != null) {
if (dpo.IsDictionary())
dp.Add(dpo);
else if (dpo.IsArray())
dp = ((PdfArray)dpo).ArrayList;
}
for (int i = 0; i < filters.Count; i++) {
var name = (filters[i] as PdfName).ToString();
switch (name) {
case "/FlateDecode":
case "/Fl":
buffer = PdfReader.FlateDecode(buffer);
goto case "DecodePredictor";
case "/ASCIIHexDecode":
case "/AHx":
buffer = PdfReader.ASCIIHexDecode(buffer);
break;
case "/ASCII85Decode":
case "/A85":
buffer = PdfReader.ASCII85Decode(buffer);
break;
case "/LZWDecode":
buffer = PdfReader.LZWDecode(buffer);
goto case "DecodePredictor";
case "/Crypt":
break;
case "/DCTDecode":
case "/JPXDecode":
case "/CCITTFaxDecode":
case "/JBIG2Decode":
if (i != filters.Count - 1) {
Tracker.TraceMessage(Tracker.Category.Error, "文件格式错误:" + name + " 解码器不是最后一个解码器。");
}
break;
case "DecodePredictor":
if (i < dp.Count) {
buffer = PdfReader.DecodePredictor(buffer, (PdfObject)dp[i]);
}
break;
default:
Trace.WriteLine(Tracker.Category.Error, "不支持的流编码格式:" + name);
break;
}
}
return buffer;
}
static PixelFormat GetPixelFormat(int byteLength, ImageInfo info) {
var pf = PixelFormat.Undefined;
var components = byteLength / info.Width / info.Height;
switch (info.BitsPerComponent) {
case 1: pf = PixelFormat.Format1bppIndexed; break;
case 2:
pf = PixelFormat.Format1bppIndexed;
Trace.WriteLine("Warning: unsupported bpc = 2");
break;
case 4: pf = PixelFormat.Format4bppIndexed; break;
case 8:
switch (components) {
case 0: // 兼容异常图片(github:#119)
Trace.WriteLine("Warning: Not enough bytes.");
goto case 1;
case 1:
pf = PixelFormat.Format8bppIndexed;
break;
case 2:
pf = PixelFormat.Format16bppRgb555;
break;
case 3:
pf = PixelFormat.Format24bppRgb;
break;
case 4:
pf = PixelFormat.Format32bppRgb;
break;
default:
Trace.WriteLine("Warning: Unknown colors.");
break;
}
break;
case 16:
pf = PixelFormat.Format48bppRgb;
break;
default:
Debug.WriteLine("Warning: bitsPerComponent missing or incorrect (" + info.BitsPerComponent + ").");
if (components > 0) {
goto case 8;
}
else {
var areaPixels = (info.Width + 7) / 8 * info.Height;
switch (areaPixels / byteLength) {
case 1: pf = PixelFormat.Format1bppIndexed; info.BitsPerComponent = 1; break;
case 2: pf = PixelFormat.Format1bppIndexed; info.BitsPerComponent = 2; break;
case 4: pf = PixelFormat.Format4bppIndexed; info.BitsPerComponent = 4; break;
default: pf = PixelFormat.Format8bppIndexed; info.BitsPerComponent = 8; break;
}
}
break;
}
return pf;
}
static void ExportColorspace(PdfObject cs, ImageInfo info) {
if (cs == null) {
return;
}
info.ColorSpace = cs as PdfName;
if (info.ColorSpace != null) {
return;
}
if (cs.Type != PdfObject.ARRAY) {
return;
}
var colorspace = cs as PdfArray;
// todo: 是否需要将所有 ColorSpace 换成 PaletteColorSpace
if (PdfName.ICCBASED.Equals(colorspace.GetAsName(0))) {
var iccs = colorspace.GetDirectObject(1) as PRStream;
info.ColorSpace = iccs.GetAsName(PdfName.ALTERNATE);
return;
}
if (PdfName.INDEXED.Equals(colorspace.GetAsName(0))) {
var o = colorspace.GetDirectObject(1);
info.PaletteColorSpace = o as PdfName;
if (info.PaletteColorSpace == null && o is PdfArray arr && arr.Size == 2) {
if (PdfName.ICCBASED.Equals(arr.GetAsName(0)) && arr.Size == 2) {
var iccs = arr.GetDirectObject(1) as PRStream;
info.ColorSpace = iccs.GetAsName(PdfName.ALTERNATE) ?? PdfName.DEVICERGB;
info.ICCProfile = PdfReader.GetStreamBytes(iccs);
}
else {
info.ColorSpace = arr.GetAsName(0);
//Tracker.TraceMessage (String.Concat ("不支持此图片的色域:", info.ColorSpace));
}
}
var csp = colorspace.GetDirectObject(3);
if (csp.IsString()) {
info.PaletteBytes = ((PdfString)csp).GetOriginalBytes();
}
else if (csp is PRStream s) {
info.PaletteBytes = PdfReader.GetStreamBytes(s);
}
//}
}
}
internal void ConvertDecodedBytes(ref byte[] bytes) {
if (PixelFormat == PixelFormat.Format24bppRgb) {
// from RGB array to BGR GDI+ data
byte b;
for (int i = 0; i < bytes.Length; i += 3) {
b = bytes[i];
bytes[i] = bytes[i + 2];
bytes[i + 2] = b;
}
}
else if (PixelFormat == PixelFormat.Format1bppIndexed && BitsPerComponent == 2) {
// 支持四级灰度的图像
var l = bytes.Length;
var newBytes = new byte[l << 1];
var i = 0;
foreach (var b in bytes) {
newBytes[i++] = (byte)(((b & 0xC0) >> 0x02) + ((b & 0x30) >> 0x04));
newBytes[i++] = (byte)(((b & 0x0C) << 0x02) + (b & 0x03));
}
if (PaletteBytes != null) {
var pattern = PaletteBytes;
Array.Resize(ref pattern, 16 * 3);
PaletteBytes = pattern;
}
PixelFormat = PixelFormat.Format4bppIndexed;
BitsPerComponent = 4;
ColorSpace = PdfName.DEVICEGRAY;
bytes = newBytes;
}
}
}
}