【问题标题】:PDF Convert to Black And White PNGsPDF转换为黑白PNG
【发布时间】:2014-12-22 05:44:19
【问题描述】:

我正在尝试使用 iTextSharp 压缩 PDF。有很多页面将彩色图像存储为 JPEG(DCTDECODE)......所以我将它们转换为黑白 PNG 并在文档中替换它们(PNG 比黑白格式的 JPG 小得多)

我有以下方法:

    private static bool TryCompressPdfImages(PdfReader reader)
    {
        try
        {
            int n = reader.XrefSize;
            for (int i = 0; i < n; i++)
            {
                PdfObject obj = reader.GetPdfObject(i);
                if (obj == null || !obj.IsStream())
                {
                    continue;
                }

                var dict = (PdfDictionary)PdfReader.GetPdfObject(obj);
                var subType = (PdfName)PdfReader.GetPdfObject(dict.Get(PdfName.SUBTYPE));
                if (!PdfName.IMAGE.Equals(subType))
                {
                    continue;
                }

                var stream = (PRStream)obj;
                try
                {
                    var image = new PdfImageObject(stream);

                    Image img = image.GetDrawingImage();
                    if (img == null) continue;

                    using (img)
                    {
                        int width = img.Width;
                        int height = img.Height;

                        using (var msImg = new MemoryStream())
                        using (var bw = img.ToBlackAndWhite())
                        {
                            bw.Save(msImg, ImageFormat.Png);
                            msImg.Position = 0;
                            stream.SetData(msImg.ToArray(), false, PdfStream.NO_COMPRESSION);
                            stream.Put(PdfName.TYPE, PdfName.XOBJECT);
                            stream.Put(PdfName.SUBTYPE, PdfName.IMAGE);
                            stream.Put(PdfName.FILTER, PdfName.FLATEDECODE);
                            stream.Put(PdfName.WIDTH, new PdfNumber(width));
                            stream.Put(PdfName.HEIGHT, new PdfNumber(height));
                            stream.Put(PdfName.BITSPERCOMPONENT, new PdfNumber(8));
                            stream.Put(PdfName.COLORSPACE, PdfName.DEVICERGB);
                            stream.Put(PdfName.LENGTH, new PdfNumber(msImg.Length));
                        }
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError(ex.ToString());
                }
                finally
                {
                    // may or may not help      
                    reader.RemoveUnusedObjects();
                }
            }
            return true;
        }
        catch (Exception ex)
        {
            Trace.TraceError(ex.ToString());
            return false;
        }
    }

    public static Image ToBlackAndWhite(this Image image)
    {
        image = new Bitmap(image);
        using (Graphics gr = Graphics.FromImage(image))
        {
            var grayMatrix = new[]
            {
                new[] {0.299f, 0.299f, 0.299f, 0, 0},
                new[] {0.587f, 0.587f, 0.587f, 0, 0},
                new[] {0.114f, 0.114f, 0.114f, 0, 0},
                new [] {0f, 0, 0, 1, 0},
                new [] {0f, 0, 0, 0, 1}
            };

            var ia = new ImageAttributes();
            ia.SetColorMatrix(new ColorMatrix(grayMatrix));
            ia.SetThreshold((float)0.8); // Change this threshold as needed
            var rc = new Rectangle(0, 0, image.Width, image.Height);
            gr.DrawImage(image, rc, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, ia);
        }
        return image;
    }

我尝试了各种 COLORSPACE 和 BITSPERCOMPONENT,但在尝试打开生成的 PDF 时总是得到“图像数据不足”、“内存不足”或“此页面存在错误”...所以我一定做错了。我很确定 FLATEDECODE 是正确的选择。

任何帮助将不胜感激。

【问题讨论】:

  • 你在用什么FLATEDECODE?那是ZIP压缩,你不是在找DCTDECODE(指的是JPEG压缩)吗?
  • 在问题中 - 正如我所提到的,我正在尝试嵌入 PNG 格式
  • PNG 不能像 PDF 一样嵌入。请使用适当的 iTextSharp 图像类。
  • 是的,他们可以。如果我使用新文档和提到的图像类,它们可以正常工作。我只想插入现有的
  • 敌意、令人讨厌的回应不受欢迎。

标签: .net pdf itextsharp system.drawing


【解决方案1】:

问题:

您有一个带有彩色 JPG 的 PDF。例如:image.pdf

如果您查看此 PDF,您会看到图像流的过滤器是 /DCTDecode,颜色空间是 /DeviceRGB

现在您要替换 PDF 中的图像,结果如下所示:image_replaced.pdf

在此 PDF 中,过滤器为 /FlateDecode,色彩空间更改为 /DeviceGray

在转换过程中,您希望使用 PNG 格式。

示例:

我给你做了一个例子来进行这种转换:ReplaceImage

我将逐步解释这个例子:

第 1 步:查找图片

在我的示例中,我知道只有一个图像,因此我正在以一种快速而肮脏的方式使用图像字典和图像字节检索PRStream

PdfReader reader = new PdfReader(src);
PdfDictionary page = reader.getPageN(1);
PdfDictionary resources = page.getAsDict(PdfName.RESOURCES);
PdfDictionary xobjects = resources.getAsDict(PdfName.XOBJECT);
PdfName imgRef = xobjects.getKeys().iterator().next();
PRStream stream = (PRStream) xobjects.getAsStream(imgRef);

我转到/XObject 字典,/Resources 在第 1 页的页面字典中列出。 我采用我遇到的第一个 XObject,假设它是一个图像并且我将该图像作为 PRStream 对象。

您的代码比我的好,但是这部分代码与您的问题无关,它在我的示例上下文中有效,所以让我们忽略这不适用于其他 PDF 的事实。您真正关心的是第 2 步和第 3 步。

第 2 步:将彩色 JPG 转换为黑白 PNG

让我们编写一个方法,接收 PdfImageObject 并将其转换为 Image 对象,该对象变为灰色并存储为 PNG:

public static Image makeBlackAndWhitePng(PdfImageObject image) throws IOException, DocumentException {
    BufferedImage bi = image.getBufferedImage();
    BufferedImage newBi = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_USHORT_GRAY);
    newBi.getGraphics().drawImage(bi, 0, 0, null);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageIO.write(newBi, "png", baos);
    return Image.getInstance(baos.toByteArray());
}

我们使用标准BufferedImage 操作将原始图像转换为黑白图像:我们将原始图像bi 绘制成TYPE_USHORT_GRAY 类型的新图像newBi

完成后,您需要 PNG 格式的图像字节。这也可以使用标准ImageIO 功能完成:我们只需将BufferedImage 写入一个字节数组,告诉ImageIO 我们想要"png"

我们可以使用生成的字节来创建一个Image 对象。

Image img = makeBlackAndWhitePng(new PdfImageObject(stream));

现在我们有了一个 iText Image 对象,但请注意,存储在此 Image 对象中的图像字节不再是 PNG 格式。正如 cmets 中已经提到的,PDF 不支持 PNG。 iText 会将图像字节更改为 PDF 支持的格式(有关更多详细信息,请参阅The ABC of PDF 的第 4.2.6.2 节)。

第 3 步:用新的图像流替换原始图像流

我们现在有一个Image 对象,但我们真正需要的是用新的图像流替换原始图像流,我们还需要调整图像字典,因为/DCTDecode 将变为/FlateDecode,@987654352 @会变成/DeviceGray/Length的值也会不同。

您正在手动创建图像流及其字典。那是勇敢的。我把这份工作留给 iText 的 PdfImage 对象:

PdfImage image = new PdfImage(makeBlackAndWhitePng(new PdfImageObject(stream)), "", null);

PdfImage 扩展了PdfStream,我现在可以用这个新流替换原始流:

public static void replaceStream(PRStream orig, PdfStream stream) throws IOException {
    orig.clear();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    stream.writeContent(baos);
    orig.setData(baos.toByteArray(), false);
    for (PdfName name : stream.getKeys()) {
        orig.put(name, stream.get(name));
    }
}

您在这里做事的顺序很重要。您不希望 setData() 方法篡改长度和过滤器。

第 4 步:替换流后持久化文档

我想这部分不难弄清楚:

replaceStream(stream, image);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
stamper.close();
reader.close();

问题:

我不是 C# 开发人员。我对 PDF 了如指掌,也对 Java 了如指掌。

  • 如果您的问题是由第 2 步引起的,那么您必须发布另一个问题,询问如何将彩色 JPEG 图像转换为黑白 PNG 图像。
  • 如果您的问题是在第 3 步引起的(例如,因为您使用的是 /DeviceRGB 而不是 /DeviceGray),那么此答案将解决您的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-29
    • 1970-01-01
    • 2013-01-18
    • 2010-10-13
    • 1970-01-01
    • 2013-08-29
    • 2015-12-30
    • 1970-01-01
    相关资源
    最近更新 更多