我有一组正常 RGB 颜色的 PDF。他们将受益于转换为 8 位以减小文件大小。是否有任何 API 或工具可以让我在保留 PDF 中的非光栅元素的同时做到这一点?
1 回答
这是一个有趣的。带有 PDF Rasterizer 和 dotPdf 的 Atalasoft dotImage 可以做到这一点(免责声明:我为 Atalasoft 工作并编写了大多数 PDF 工具)。我首先会找到候选页面:
List<int> GetCandidatePages(Stream pdf, string password)
{
List<int> retVal = new List<int>();
using (PageCollection pages = new PageCollection(pdf, password)) {
for (int i=0; i < pages.Count; i++) {
if (pages[i].SingleImageOnly())
retVal.Add(i);
}
}
pdf.Seek(0, SeekOrigin.Begin); // restore file pointer
return retVal;
}
接下来,我将仅对这些页面进行光栅化,将它们转换为 8 位图像,但为了保持效率,我将使用一个很好地管理内存的 ImageSource:
public class SelectPageImageSource : RandomAccessImageSource {
private List<int> _pages;
private Stream _stm;
public SelectPageImageSource(Stream stm, List<int> pages)
{
_stm = stm;
_pages = pages;
}
protected override ImageSourceNode LowLevelAcquire(int index)
{
PdfDecoder decoder = new PdfDecoder();
_stm.Seek(0, SeekOrigin.Begin);
AtalaImage image = PdfDecoder.Read(_stm, _pages[index], null);
// change to 8 bit
if (image.PixelFormat != PixelFormat.Pixel8bppIndexed) {
AtalaImage changed = image.GetChangedPixelFormat(PixelFormat.Pixel8bppIndexed);
image.Dispose();
image = changed;
}
return new FileReloader(image, new PngEncoder());
}
protected override int LowLevelTotalImages() { return _pages.Count; }
}
接下来,您需要从中创建一个新的 PDF:
public void Make8BitImagePdf(Stream pdf, Stream outPdf, List<int> pages)
{
PdfEncoder encoder = new PdfEncoder();
SelectPageImageSource source = new SelectPageImageSource(pdf, pages);
encoder.Save(outPdf, source, null);
}
接下来,您需要将原始页面替换为新页面:
public void ReplaceOriginalPages(Stream pdf, Stream image8Bit, Stream outPdf, List<int> pages)
{
PdfDocument docOrig = new PdfDocument(pdf);
PdfDocument doc8Bit = new PdfDocument(image8Bit);
for (int i=0; i < pages.Count; i++) {
docOrig.Pages[pages[i]] = doc8Bit[i];
}
docOrig.Save(outPdf); // this is your final
}
这或多或少会做你想做的事。不太理想的一点是图像页面已被光栅化,这可能不是您想要的。好消息是,仅仅通过光栅化,生成输出很容易,但它可能不是原始图像的分辨率。这可以完成,但需要从SingleImageOnly
页面,然后更改其像素格式。问题在于 SingleImageOnly 并不意味着图像适合整个页面,也不意味着图像放置在任何特定位置。除了 PixelFormat 更改(实际上是在更改之前)之外,您还希望将用于将图像放置在页面上的矩阵应用于图像本身,并使用具有适当的边距和原始页面大小的 PdfEncoder将图像放在应有的位置。这一切都是干脆的,但它是大量的代码。
还有另一种方法也可以使用我们的 PDF 生成 API。它涉及打开文档并将文档的图像资源换成 8 位的。这也是可行的,但并非完全微不足道。你会做这样的事情:
public void ReplaceImageResources(Stream pdf, Stream outPdf, List<int> pages)
{
PdfGeneratedDocument doc = new PdfGeneratedDocument(pdf);
doc.Resources.Images.Compressors.Insert(0, new AtalaImageCompressor());
foreach (int page in pages) {
// GetSinglePageImage uses PageCollection, as above, to
// pull a single image from the page (no need to use the matrix)
// then converts it to 8 bpp indexed and returns it or null if it
// is already 8 bpp indexed (or 4bpp or 1bpp).
using (AtalaImage image = GetSinglePageImage(pdf, page)) {
if (image == null) continue;
foreach (string resName in doc.Pages[page].ImportedImages) {
doc.Resources.Images.Remove(resName);
doc.Resources.Images.Add(resName, image);
break;
}
}
}
doc.Save(outPdf);
}
正如我所说,这很棘手 - PDF 生成套件是为从整块布料制作新 PDF 或向现有 PDF 添加新页面(将来,我们希望添加完整编辑)而设计的。但是 PDF 将其所有图像作为文档中的资源进行管理,我们有能力完全替换这些资源。因此,为了让生活更轻松,我们将 ImageCompressor 添加到处理 AtalaImage 对象的 Image 资源集合中,并删除现有的图像资源并用新的图像资源替换它们。
现在我将做一些你可能不会看到任何供应商在谈论他们自己的产品时会做的事情——我将在多个层面上批评它。首先,它不是超级便宜。对不起。当您查看价格时,您可能会感到震惊,但价格包括来自真正首屈一指的员工的技术支持。
您可以使用 iTextPdf Sharp 或 Bit Miracle 的 Docotic PDF 库或 Tall Components PDF 库来做很多这样的事情。后两者也要花钱。事实证明,Bit Miracle 的工程师非常乐于助人,您很可能会在这里看到他们(嗨!)。也许他们也可以帮助你。iTextPdfSharp 存在问题,因为您确实需要了解 PDF 规范才能做正确的事情,否则您可能会输出垃圾 PDF - 我已经用我自己的库与 iTextPdfSharp 并排完成了这个实验,发现了一些需要深入了解 PDF 规范才能解决的常见任务的痛点。我试图在我的高级工具中做出决定,这样您就不需要了解 PDF 规范,也不需要担心创建错误的 PDF。
我不特别喜欢我们的代码库中有几个明显不同的工具可以做类似的事情。由于历史原因,PageCollection 是我们 PDF 光栅化器的一部分。PdfDocument 严格用于操作页面,并试图轻量级和吝啬内存。PdfGeneratedDocument 用于操作/创建页面内容。PdfDecoder 用于从现有 PDF 生成光栅图像。PdfEncoder 用于从图像生成纯图像 PDF。拥有所有这些明显重叠的利基工具可能会令人生畏,但它们之间存在逻辑关系以及它们之间的关系。