使用免费库和C#编程压缩现有PDF

26

我在谷歌上搜了很多有关如何压缩现有的 pdf(大小)的方法。

我的问题是:

  1. 我不能使用任何应用程序,因为它需要由 C# 程序完成。

  2. 我不能使用任何付费库,因为我的客户不想超出预算。因此付费库肯定是不行的。

我已经做了两天的功课,并找到了使用iTextSharp、BitMiracle的解决方案,但都无济于事,前者只能减少1%的文件大小,后者是收费的。

我还发现了PDFcompressNET和pdftk,但我找不到它们的.dll文件。

实际上,这个PDF文件是一份保险单,带有2-3张黑白图片,大约70页,占据5MB的空间。

我需要输出结果仍然是pdf格式(不能是其他格式)。


3
压缩是否真的有帮助?尝试创建一些PDF文件测试用例,并使用各种现成的程序/方法进行压缩。这些文件的压缩比率是多少?也许你正在尝试做一些不值得/不可能做到的事情? - André C. Andersen
3
如果你所提到的文件是代表性的话,“合并50个PDF文件”这一步骤很遗憾地错误使用了iTextSharp 4.1.2库(在此任务中使用PdfWriter而不是PdfCopy)……嗯,乍一看,你的主要问题可能是包含的70个字体子集文件;尽管进行了压缩,但其中许多文件仍需要超过80 KB!不幸的是,重新组合同一字体的多个不同子集通常很难(大部分文档页面的内容可能必须重写),而且iText(Sharp)并没有明确支持此类操作;这将是一项相当困难的任务! - mkl
@Prahalad,正如你所说,这是一个选项,如果我在你的位置上,我会首先尝试合并文档并将合并后的文档导出为PDF。概念验证(一个函数,将一个这样的保险案例的文档合并,然后导出为PDF)不应该超过一个小时,很可能(显然我们无法保证任何事情,因为不知道相关的文档文件和软件版本)这将使事情变得更好。 - mkl
1
@Vijay,没有进一步的解释,我怀疑你的赏金是否花得值得。一个新问题,包含你的要求和尝试(我希望你已经尝试过),会更好些。 - mkl
很简单。客户:我想要X。你:X的价格是$$。客户:我不想支付$ $。你:那么,你就得不到X。 - Chris Pratt
显示剩余16条评论
4个回答

16
以下是一种处理方法(无论你使用的工具包是什么都应该适用):
如果你有一个24位rgb或32位cmyk图像,请执行以下操作:
  • 确定图像是否是真实的。如果是CMYK,请转换为RGB。如果是RGB并且确实是灰色,请转换为灰度。如果是灰度或调色板,并且只有2种真实颜色,请转换为1位。如果是灰度并且灰度变化相对较少,请考虑使用适当的二值化技术将其转换为1位。
  • 根据页面上的放置方式测量图像尺寸-如果它的分辨率为300 dpi或更高,请考虑重新采样图像以缩小大小,具体取决于图像的位深度-例如,您可以从300 dpi的灰度或RGB转换为200 dpi,而不会失去太多细节。
  • 如果您有一个真正的彩色RGB图像,请考虑将其调色板化。
  • 检查图像内容,看看是否可以帮助使其更易于压缩。例如,如果您运行彩色/灰度图像并发现许多颜色聚集在一起,请考虑平滑它们。如果它是灰度或黑白色,并包含许多斑点,请考虑去斑。
  • 明智选择最终的压缩方法。JPEG2000比JPEG更好。JBIG2比G4好得多。Flate可能是灰度的最佳非破坏性压缩。大多数JPEG2000和JBIG2的实现都不是免费的。
  • 如果您是一位摇滚明星,您可以尝试分割图像并将其分成真正的黑白色区域和真正的彩色区域。
如果您能够独立完成所有这些工作,那么您就拥有了一款商业产品。我想说的是,您可以使用Atalasoft dotImage来完成大部分工作(免责声明:此软件非免费;我在那里工作;我几乎编写了所有PDF工具;我曾经在Acrobat上工作)。使用dotImage的一个特定方法是将所有仅为图像的页面提取出来,重新压缩它们并将其保存到新的PDF中,然后通过从原始文档中获取所有页面并将其替换为重新压缩的页面来构建新的PDF,最后再次保存。这并不难。
List<int> pagesToReplace = new List<int>();
PdfImageCollection pagesToEncode = new PdfImageCollection();

using (Document doc = new Document(sourceStream, password)) {

    for (int i=0; i < doc.Pages.Count; i++) {
        Page page = doc.Pages[i];
        if (page.SingleImageOnly) {
            pagesToReplace.Add(i);
            // a PDF image encapsulates an image an compression parameters
            PdfImage image = ProcessImage(sourceStream, doc, page, i);
            pagesToEncode.Add(i);
        }
    }

    PdfEncoder encoder = new PdfEncoder();
    encoder.Save(tempOutStream, pagesToEncode, null); // re-encoded pages
    tempOutStream.Seek(0, SeekOrigin.Begin);

    sourceStream.Seek(0, SeekOrigin.Begin);
    PdfDocument finalDoc = new PdfDocument(sourceStream, password);
    PdfDocument replacementPages = new PdfDocument(tempOutStream);

    for (int i=0; i < pagesToReplace.Count; i++) {
         finalDoc.Pages[pagesToReplace[i]] = replacementPages.Pages[i];
    }

    finalDoc.Save(finalOutputStream);

这里缺少的是ProcessImage()。ProcessImage将会栅格化页面(而且您不需要理解图像可能已被缩放以适合PDF),或者提取图像(并跟踪图像上的转换矩阵),然后按照上述步骤进行处理。这是非常复杂的,但是可以做到。


7
我认为你需要让客户知道你提到的任何库都不是完全免费的:
- iTextSharp采用AGPL许可证,因此您必须发布解决方案的源代码或购买商业许可证。 - PDFcompressNET是商业库。 - pdftk采用GPL许可证,因此您必须发布解决方案的源代码或购买商业许可证。 - Docotic.Pdf是商业库。
考虑到上述所有情况,我认为可以放弃免费软件的要求。
Docotic.Pdf可以在不引入任何破坏性更改的情况下减小压缩和未压缩PDF的大小
收益取决于PDF的大小和结构:对于小文件或大部分为扫描图像的文件,减少可能不会那么大,因此您应该使用自己的文件尝试该库并自行查看。
如果您最关心文件大小,并且您的文件中有许多图像,而且您可以接受失去一些图像质量,那么您可以使用Docotic.Pdf轻松地重新压缩现有图像。
以下是使所有图像变为二值图并使用传真压缩压缩的代码:
static void RecompressExistingImages(string fileName, string outputName)
{
    using (PdfDocument doc = new PdfDocument(fileName))
    {
        foreach (PdfImage image in doc.Images)
            image.RecompressWithGroup4Fax();

        doc.Save(outputName);
    }
}

此外还有RecompressWithFlateRecompressWithGroup3FaxRecompressWithJpeg方法。

如果需要,该库将把彩色图像转换为双色图像。您可以指定deflate压缩级别、JPEG质量等。

Docotic.Pdf还可以调整PDF中的大型图像大小(并同时重新压缩它们)。如果文档中的图像实际上比所需的要大,或者图像的质量不是很重要,则这可能非常有用。

以下是一个代码,它缩放所有具有宽度或高度大于或等于256的图像。然后使用JPEG压缩对缩放后的图像进行编码。

public static void RecompressToJpeg(string path, string outputPath)
{
    using (PdfDocument doc = new PdfDocument(path))
    {
        foreach (PdfImage image in doc.Images)
        {
            // image that is used as mask or image with attached mask are
            // not good candidates for recompression
            if (!image.IsMask && image.Mask == null && (image.Width >= 256 || image.Height >= 256))
                image.Scale(0.5, PdfImageCompression.Jpeg, 65);
        }

        doc.Save(outputPath);
    }
}

图片可以使用其中一个“ResizeTo”方法调整大小到指定的宽度和高度。请注意,“ResizeTo”方法不会尝试保持图像的宽高比。您应该自己计算适当的宽度和高度。
免责声明:我为Bit Miracle工作。

3
当你使用JPEG进行缩放/重新压缩并改变JPEG图片质量时,一定要非常小心。我知道有一个程序员被分配将法律文件归档,并且结果是一些法庭案件必须被撤销,因为文件的唯一副本现在已经无法读取。请注意避免这样的情况。 - plinth

5
使用PdfSharp技术。
public static void CompressPdf(string targetPath)
{
    using (var stream = new MemoryStream(File.ReadAllBytes(targetPath)) {Position = 0})
    using (var source = PdfReader.Open(stream, PdfDocumentOpenMode.Import))
    using (var document = new PdfDocument())
    {
        var options = document.Options;
        options.FlateEncodeMode = PdfFlateEncodeMode.BestCompression;
        options.UseFlateDecoderForJpegImages = PdfUseFlateDecoderForJpegImages.Automatic;
        options.CompressContentStreams = true;
        options.NoCompression = false;
        foreach (var page in source.Pages)
        {
            document.AddPage(page);
        }

        document.Save(targetPath);
    }
}

谢谢@Simon。这是我非常失败的第一个任务。现在我已经开始着手处理BI应用和数据库。 - Prahalad Gaggar
我尝试在.NET Core 5和lib版本1.50.5147上使用这个库。这段代码片段引发了一些错误。 - toha

3

GhostScript是一款AGPL许可的软件,可以压缩PDF文件。此外,在github上还有一个AGPL许可的C#封装程序在这里

您可以使用该封装程序中的GhostscriptProcessor类向GhostScript传递自定义命令,例如在这个AskUbuntu答案中描述的PDF压缩命令。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接