2014-10-27 117 views
2

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

我有以下几种方法:

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; 
    } 

我试过品种的色彩空间和BITSPERCOMPONENTs的,但总是得到“没有足够的数据图像”,“内存不足”或“错误存在于这个页面“试图打开生成的PDF文件...所以我一定是做错了。我很确定FLATEDECODE是正确的使用方式。

任何援助将不胜感激。

+1

你在用什么FLATEDECODE?这是ZIP压缩,你不是在寻找DCTDECODE(它指的是JPEG压缩)吗? – 2014-10-27 13:33:49

+0

在问题中 - 正如我所提到的,我试图嵌入PNG格式 – Jeff 2014-10-27 14:13:31

+0

PNG无法像嵌入PDF一样嵌入。请使用适当的iTextSharp图像类。 – mkl 2014-10-31 09:50:09

回答

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); 

我去/XObject词典在第1页 的页面字典我采取的第一个X对象我遇到列出的/Resources,假定它是一个IMAGEM和我得到的图像作为PRStream对象。

您的代码比我的代码好,但是这部分代码与您的问题无关,它适用于我的示例的上下文,因此让我们忽略这一点对其他PDF无效的事实。你真正关心的是第2步和第3步。

步骤2:将所述着色JPG成黑白PNG

让我们写,需要一个PdfImageObject的方法和将其转换成被改变成灰色的颜色,并存储为一个PNG一个Image对象:

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提请类型的新形象。

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

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

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

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

第3步:使用新的图像流

取代了原来的图像流,我们现在有一个Image对象,但我们真正需要的是一个新的来取代原来的图像流,我们还需要适应图像字典为/DCTDecode将变为/FlateDecode,/DeviceRGB将变为/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),那么此答案将解决您的问题。