PDF转换为黑白PNG

Jef*_*eff 2 .net pdf system.drawing itextsharp

我正在尝试使用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;
    }
Run Code Online (Sandbox Code Playgroud)

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

任何援助将不胜感激.

Bru*_*gie 5

问题:

您有一张带有彩色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);
Run Code Online (Sandbox Code Playgroud)

我去了/XObject字典./Resources在第1页的页字典我采取的第一个X对象我遇到的上市,假定它是一个IMAGEM和我得到的图像作为PRStream对象.

你的代码比我的更好,但是这部分代码与你的问题无关,它在我的例子的上下文中工作,所以让我们忽略这个事实,这对其他PDF不起作用.你真正关心的是第2步和第3步.

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

让我们编写一个方法,该方法将a 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());
}
Run Code Online (Sandbox Code Playgroud)

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

完成此操作后,您需要PNG格式的图像字节.这也是使用标准ImageIO功能完成的:我们只是写BufferedImage一个字节数组来告诉ImageIO我们想要的"png".

我们可以使用结果字节来创建一个Image对象.

Image img = makeBlackAndWhitePng(new PdfImageObject(stream));
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

PdfImage image = new PdfImage(makeBlackAndWhitePng(new PdfImageObject(stream)), "", null);
Run Code Online (Sandbox Code Playgroud)

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

第4步:更换流后保留文档

我猜这个部分并不难:

replaceStream(stream, image);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
stamper.close();
reader.close();
Run Code Online (Sandbox Code Playgroud)

问题:

我不是C#开发人员.我从内到外都知道PDF,我知道Java.

  • 如果您的问题是在步骤2中引起的,那么您将不得不发布另一个问题,询问如何将彩色JPEG图像转换为黑白PNG图像.
  • 如果你的问题是在第3步引起的(例如因为你使用的是/DeviceRGB代替/DeviceGray),那么这个答案将解决你的问题.