mirror of
https://github.com/wmjordan/PDFPatcher.git
synced 2026-10-19 06:21:06 +08:00
154 lines
5.1 KiB
C#
154 lines
5.1 KiB
C#
using System;
|
||
using System.Linq;
|
||
using FreeImageAPI;
|
||
using iTextSharp.text.pdf;
|
||
using PDFPatcher.Model;
|
||
using PDFPatcher.Processor.Imaging;
|
||
using ColorDepth = FreeImageAPI.FREE_IMAGE_COLOR_DEPTH;
|
||
|
||
namespace PDFPatcher.Processor
|
||
{
|
||
sealed class ImageRecompressor : IPageProcessor
|
||
{
|
||
static readonly PdfName[] __IgnoreFilters = [PdfName.JBIG2DECODE];
|
||
static readonly ImageExtracterOptions _imgExpOption = new ImageExtracterOptions() {
|
||
OutputPath = System.IO.Path.GetTempPath(),
|
||
MergeImages = false
|
||
};
|
||
|
||
int _processedImageCount;
|
||
int _optimizedImageCount;
|
||
|
||
public byte BinaryThreshold { get; set; }
|
||
public byte Algorithm { get; set; }
|
||
|
||
#region IPageProcessor 成员
|
||
public string Name => "优化压缩黑白图片";
|
||
|
||
public void BeginProcess(DocProcessorContext context) {
|
||
_processedImageCount = 0;
|
||
_optimizedImageCount = 0;
|
||
}
|
||
public bool EndProcess(PdfReader pdf) {
|
||
Tracker.TraceMessage(Tracker.Category.Notice, Name + "功能:");
|
||
Tracker.TraceMessage($" 处理了 {_processedImageCount} 幅图片。");
|
||
Tracker.TraceMessage($" 优化了 {_optimizedImageCount} 幅图片的压缩率。");
|
||
return false;
|
||
}
|
||
|
||
public int EstimateWorkload(PdfReader pdf) {
|
||
return pdf.NumberOfPages * 10;
|
||
}
|
||
|
||
public bool Process(PageProcessorContext context) {
|
||
Tracker.IncrementProgress(10);
|
||
return IterateObjects(context.Page);
|
||
}
|
||
|
||
bool IterateObjects(PdfDictionary container) {
|
||
var items = container.Locate<PdfDictionary>(PdfName.RESOURCES, PdfName.XOBJECT);
|
||
if (items == null) {
|
||
return false;
|
||
}
|
||
foreach (var item in items) {
|
||
if (item.Value is PdfIndirectReference pdfRef
|
||
&& PdfReader.GetPdfObject(pdfRef) is PRStream im) {
|
||
var subType = im.GetAsName(PdfName.SUBTYPE);
|
||
if (PdfName.IMAGE.Equals(subType)) {
|
||
CompressImage(pdfRef, im);
|
||
}
|
||
else if (PdfName.FORM.Equals(subType)) {
|
||
IterateObjects(im);
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
void CompressImage(PdfIndirectReference pdfRef, PRStream img) {
|
||
_processedImageCount++;
|
||
var l = img.GetAsNumber(PdfName.LENGTH);
|
||
if (l == null || l.IntValue < 400 /*忽略小图片*/) {
|
||
return;
|
||
}
|
||
var f = img.Get(PdfName.FILTER);
|
||
PdfName fn = null;
|
||
if (f.Type == PdfObject.ARRAY) {
|
||
var fl = f as PdfArray;
|
||
fn = fl.GetAsName(fl.Size - 1);
|
||
}
|
||
else if (f.Type == PdfObject.NAME) {
|
||
fn = f as PdfName;
|
||
}
|
||
if (fn == null || !__IgnoreFilters.Contains(fn)) {
|
||
if (OptimizeBinaryImage(pdfRef, img, l.IntValue, BinaryThreshold, Algorithm)
|
||
/*|| ReplaceJ2kImage(pdfRef, im, fn)*/) {
|
||
_optimizedImageCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
static bool OptimizeBinaryImage(PdfIndirectReference imgRef, PRStream imgStream, int length, byte binaryThreshold, byte algorithm) {
|
||
var oneBitPerComponent = imgStream.GetAsNumber(PdfName.BITSPERCOMPONENT)?.IntValue == 1;
|
||
var isMask = imgStream.GetAsBoolean(PdfName.IMAGEMASK)?.BooleanValue == true;
|
||
if (!oneBitPerComponent && !isMask && binaryThreshold == 0) {
|
||
return false;
|
||
}
|
||
|
||
var info = new ImageInfo(imgRef);
|
||
var bytes = info.DecodeImage(_imgExpOption);
|
||
using (var fi = ImageExtractor.CreateFreeImageBitmap(info, ref bytes, false, info.ICCProfile != null)) {
|
||
info.CreatePaletteAndIccProfile(fi);
|
||
if (binaryThreshold != 0
|
||
&& (!fi.HasPalette
|
||
|| fi.UniqueColors > 256
|
||
|| !fi.UseDib(dib => FreeImage.ConvertColorDepth(dib, algorithm == 255 ? ColorDepth.FICD_01_BPP_THRESHOLD | ColorDepth.FICD_FORCE_GREYSCALE | ColorDepth.FICD_REORDER_PALETTE : ColorDepth.FICD_01_BPP_DITHER, binaryThreshold, false)))) {
|
||
return false;
|
||
}
|
||
var sb = JBig2Encoder.Encode(fi);
|
||
if (sb.Length > length) {
|
||
return false;
|
||
}
|
||
imgStream.SetData(sb, false);
|
||
imgStream.Put(PdfName.FILTER, PdfName.JBIG2DECODE);
|
||
imgStream.Put(PdfName.COLORSPACE, PdfName.DEVICEGRAY);
|
||
imgStream.Put(PdfName.BITSPERCOMPONENT, new PdfNumber(1));
|
||
imgStream.Put(PdfName.LENGTH, new PdfNumber(sb.Length));
|
||
imgStream.Remove(PdfName.K);
|
||
imgStream.Remove(PdfName.ENDOFLINE);
|
||
imgStream.Remove(PdfName.ENCODEDBYTEALIGN);
|
||
imgStream.Remove(PdfName.ROWS);
|
||
imgStream.Remove(PdfName.ENDOFBLOCK);
|
||
imgStream.Remove(PdfName.BLACKIS1);
|
||
imgStream.Remove(PdfName.PREDICTOR);
|
||
imgStream.Remove(PdfName.COLORS);
|
||
imgStream.Remove(PdfName.COLUMNS);
|
||
imgStream.Remove(PdfName.EARLYCHANGE);
|
||
imgStream.Remove(PdfName.DECODEPARMS);
|
||
imgStream.Remove(PdfName.DECODE);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool ReplaceJ2kImage(PdfIndirectReference imgRef, PRStream imgStream, PdfName filter) {
|
||
if (!PdfName.JPXDECODE.Equals(filter)) {
|
||
return false;
|
||
}
|
||
|
||
var info = new ImageInfo(imgRef);
|
||
byte[] jpg;
|
||
using (var js = new System.IO.MemoryStream())
|
||
using (var fi = FreeImageHelper.CreateFromBytes(info.DecodeImage(_imgExpOption))) {
|
||
fi.Save(js, FreeImageAPI.FREE_IMAGE_FORMAT.FIF_JPEG, FreeImageAPI.FREE_IMAGE_SAVE_FLAGS.JPEG_BASELINE | FreeImageAPI.FREE_IMAGE_SAVE_FLAGS.JPEG_QUALITYNORMAL | FreeImageAPI.FREE_IMAGE_SAVE_FLAGS.JPEG_PROGRESSIVE);
|
||
jpg = js.ToArray();
|
||
}
|
||
imgStream.SetData(jpg, false);
|
||
imgStream.Put(PdfName.FILTER, PdfName.DCTDECODE);
|
||
imgStream.Put(PdfName.LENGTH, new PdfNumber(jpg.Length));
|
||
return true;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|