2014-10-27 118 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),那麼此答案將解決您的問題。