【问题标题】:Why is my image distorted when decoding as FlateDecode using iTextSharp?为什么我的图像在使用 iTextSharp 解码为 FlateDecode 时会失真?
【发布时间】:2011-12-13 17:33:41
【问题描述】:

当通过 iTextSharp 将 PDF 中的图像解码为FlateDecode 时,图像失真,我似乎无法弄清楚原因。

识别的 bpp 是Format1bppIndexed。如果我将PixelFormat 修改为Format4bppIndexed,则图像在某种程度上是可识别的(缩小,着色已关闭但可读),并且以水平方式复制了 4 次。如果我将像素格式调整为Format8bppIndexed,它在某种程度上也是可识别的,并且以水平方式复制了 8 次。

下图采用Format1bppIndexed 像素格式方法。不幸的是,由于安全限制,我无法向其他人展示。

代码如下所示,它基本上是我在 SO 和网络上遇到的单一解决方案。

int xrefIdx = ((PRIndirectReference)obj).Number;
PdfObject pdfObj = doc.GetPdfObject(xrefIdx);
PdfStream str = (PdfStream)(pdfObj);
byte[] bytes = PdfReader.GetStreamBytesRaw((PRStream)str);

string filter = ((PdfArray)tg.Get(PdfName.FILTER))[0].ToString();
string width = tg.Get(PdfName.WIDTH).ToString();
string height = tg.Get(PdfName.HEIGHT).ToString();
string bpp = tg.Get(PdfName.BITSPERCOMPONENT).ToString();

if (filter == "/FlateDecode")
{
   bytes = PdfReader.FlateDecode(bytes, true);

   System.Drawing.Imaging.PixelFormat pixelFormat;
   switch (int.Parse(bpp))
   {
      case 1:
         pixelFormat = System.Drawing.Imaging.PixelFormat.Format1bppIndexed;
         break;
      case 8:
         pixelFormat = System.Drawing.Imaging.PixelFormat.Format8bppIndexed;
         break;
      case 24:
         pixelFormat = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
         break;
      default:
         throw new Exception("Unknown pixel format " + bpp);
   }

   var bmp = new System.Drawing.Bitmap(Int32.Parse(width), Int32.Parse(height), pixelFormat);
   System.Drawing.Imaging.BitmapData bmd = bmp.LockBits(new System.Drawing.Rectangle(0, 0, Int32.Parse(width),
             Int32.Parse(height)), System.Drawing.Imaging.ImageLockMode.WriteOnly, pixelFormat);
   Marshal.Copy(bytes, 0, bmd.Scan0, bytes.Length);
   bmp.UnlockBits(bmd);
   bmp.Save(@"C:\temp\my_flate_picture-" + DateTime.Now.Ticks.ToString() + ".png", ImageFormat.Png);
}

在处理FlateDecode 时,我需要做什么才能使我的图像提取工作正常?

注意:我不想使用其他库来提取图像。我正在寻找一种利用ONLY iTextSharp 和.NET FW 的解决方案。如果通过 Java (iText) 存在解决方案,并且可以轻松移植到 .NET FW 位,那也足够了。

更新ImageMask 属性设置为 true,这意味着没有色彩空间,因此隐含黑白。当 bpp 为 1 时,PixelFormat 应该是 Format1bppIndexed,如前所述,它会生成上面看到的嵌入图像。

更新:为了获得图像大小,我使用 Acrobat X Pro 将其提取出来,此特定示例的图像大小被列为 2403x3005。通过 iTextSharp 提取时,大小被列为 2544x3300。我在调试器中修改了图像大小以镜像 2403x3005 但是在调用 Marshal.Copy(bytes, 0, bmd.Scan0, bytes.Length); 时出现异常。

试图读取或写入受保护的内存。这通常是一个 指示其他内存已损坏。

我的假设是这是由于修改了大小,因此不再对应正在使用的字节数据。

更新:根据 Jimmy 的建议,我验证调用 PdfReader.GetStreamBytes 返回的 byte[] 长度等于 widthheight/8,因为 GetStreamBytes 应该调用 FlateDecode。手动调用FlateDecode和调用PdfReader.GetStreamBytes都产生了byte[]长度为1049401,而widthheight/8为2544*3300/8或1049400,所以相差1。不确定是否这将是根本原因与否,一个关闭;但是,如果确实如此,我不确定如何解决。

更新:在尝试 kuujinbo 提到的方法时,当我尝试在 RenderImage 侦听器中调用 renderInfo.GetImage(); 时,我遇到了 IndexOutOfRangeException。前面提到的 width*height/8 在调用FlateDecode 时与 byte[] 长度相比相差 1,这让我认为这些都是同一个;但是我仍然找不到解决方案。

   at System.util.zlib.Adler32.adler32(Int64 adler, Byte[] buf, Int32 index, Int32 len)
   at System.util.zlib.ZStream.read_buf(Byte[] buf, Int32 start, Int32 size)
   at System.util.zlib.Deflate.fill_window()
   at System.util.zlib.Deflate.deflate_slow(Int32 flush)
   at System.util.zlib.Deflate.deflate(ZStream strm, Int32 flush)
   at System.util.zlib.ZStream.deflate(Int32 flush)
   at System.util.zlib.ZDeflaterOutputStream.Write(Byte[] b, Int32 off, Int32 len)
   at iTextSharp.text.pdf.codec.PngWriter.WriteData(Byte[] data, Int32 stride)
   at iTextSharp.text.pdf.parser.PdfImageObject.DecodeImageBytes()
   at iTextSharp.text.pdf.parser.PdfImageObject..ctor(PdfDictionary dictionary, Byte[] samples)
   at iTextSharp.text.pdf.parser.PdfImageObject..ctor(PRStream stream)
   at iTextSharp.text.pdf.parser.ImageRenderInfo.PrepareImageObject()
   at iTextSharp.text.pdf.parser.ImageRenderInfo.GetImage()
   at cyos.infrastructure.Core.MyImageRenderListener.RenderImage(ImageRenderInfo renderInfo)

更新:尝试改变我的原始解决方案中列出的不同方法以及 kuujinbo 提出的解决方案,在 PDF 中使用不同的页面会产生图像;但是,当过滤器类型为 /FlateDecode 并且没有为该给定实例生成图像时,问题总是会浮出水面。

【问题讨论】:

  • 图片是怎么变形的?可以发截图吗?听起来您在某处步幅错误,或者乘数不正确。
  • 这与这个问题有关吗? stackoverflow.com/questions/757265/…如果没有,我会在有机会时尝试更深入地挖掘
  • 看起来您的步幅确实有误。嗯,当我以前定期进行图像阅读时,这种事情会一直发生。您需要检查widthheightbpp 的值是否符合您的预期。尝试在调试器中更改它们,直到获得正确的结果,然后返回到您从文件中读取的内容。
  • @ChrisHaas 我不确定是不是这样。看起来它可能是但是到目前为止我所知道的一切都表明指定的PixelFormat 是准确的,但结果却不是。
  • 我假设 GetStreamBytes 将代表您调用 FlateDecode。你检查过 bytes.length 是否等于(height*width/8)?我会相信图像字典中的宽度和高度(而不是 Adob​​e Acrobat 给出的值)。我最近做了类似的事情,但是因为我一直在使用 Java,所以我没有 C# 中的内存访问权限(我已经单独绘制了像素)。希望你能把它整理好。

标签: c# pdf itextsharp itext


【解决方案1】:

试着逐行复制你的数据,也许会解决问题。

int w = imgObj.GetAsNumber(PdfName.WIDTH).IntValue;
int h = imgObj.GetAsNumber(PdfName.HEIGHT).IntValue;
int bpp = imgObj.GetAsNumber(PdfName.BITSPERCOMPONENT).IntValue;
var pixelFormat = PixelFormat.Format1bppIndexed;

byte[] rawBytes = PdfReader.GetStreamBytesRaw((PRStream)imgObj);
byte[] decodedBytes = PdfReader.FlateDecode(rawBytes);
byte[] streamBytes = PdfReader.DecodePredictor(decodedBytes, imgObj.GetAsDict(PdfName.DECODEPARMS));
// byte[] streamBytes = PdfReader.GetStreamBytes((PRStream)imgObj); // same result as above 3 lines of code.

using (Bitmap bmp = new Bitmap(w, h, pixelFormat))
{
    var bmpData = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.WriteOnly, pixelFormat);
    int length = (int)Math.Ceiling(w * bpp / 8.0);
    for (int i = 0; i < h; i++)
    {
        int offset = i * length;
        int scanOffset = i * bmpData.Stride;
        Marshal.Copy(streamBytes, offset, new IntPtr(bmpData.Scan0.ToInt32() + scanOffset), length);
    }
    bmp.UnlockBits(bmpData);

    bmp.Save(fileName);
}

【讨论】:

  • 听起来是个很好的尝试,似乎步幅被四舍五入到 4 字节边界msdn.microsoft.com/en-us/library/…
  • @bigsan 成功了!颜色是颠倒的,黑色是白色,白色是黑色,但除此之外它工作得很好。
  • @AaronMcIver 反转问题是由于DecodePredictor 方法中的错误。为了纠正这个问题,pdfbox 项目中有另一个实现,请参阅line 202~324
  • 由于图像是模板蒙版,您还需要考虑 /Decode 键以了解如何解释颜色。很高兴你把它整理好了
  • 感谢 bigsan,这对我帮助很大。我遇到了溢出错误,但是通过将 bmpData.Scan0.ToInt32() 更改为 bmpData.Scan0.ToInt64() 解决了它。
【解决方案2】:

如果您能够使用最新版本 (5.1.3),提取 FlateDecode 和其他图像类型的 API 已使用 iTextSharp.text.pdf.parser 命名空间进行了简化。基本上,您使用PdfReaderContentParser 来帮助您解析PDF 文档,然后您专门(在这种情况下)实现IRenderListener 接口来处理图像。这是一个有效的 HTTP 处理程序示例:

<%@ WebHandler Language="C#" Class="bmpExtract" %>
using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;

public class bmpExtract : IHttpHandler {
  public void ProcessRequest (HttpContext context) {
    HttpServerUtility Server = context.Server;
    HttpResponse Response = context.Response;
    PdfReader reader = new PdfReader(Server.MapPath("./bmp.pdf"));
    PdfReaderContentParser parser = new PdfReaderContentParser(reader);
    MyImageRenderListener listener = new MyImageRenderListener();
    for (int i = 1; i <= reader.NumberOfPages; i++) {
      parser.ProcessContent(i, listener);
    } 
    for (int i = 0; i < listener.Images.Count; ++i) {
      string path = Server.MapPath("./" + listener.ImageNames[i]);
      using (FileStream fs = new FileStream(
        path, FileMode.Create, FileAccess.Write
      ))
      {
        fs.Write(listener.Images[i], 0, listener.Images[i].Length);
      }
    }         
  }
  public bool IsReusable { get { return false; } }

  public class MyImageRenderListener : IRenderListener {
    public void RenderText(TextRenderInfo renderInfo) { }
    public void BeginTextBlock() { }
    public void EndTextBlock() { }

    public List<byte[]> Images = new List<byte[]>();
    public List<string> ImageNames = new List<string>();
    public void RenderImage(ImageRenderInfo renderInfo) {
      PdfImageObject image = null;
      try {
        image = renderInfo.GetImage();
        if (image == null) return;

        ImageNames.Add(string.Format(
          "Image{0}.{1}", renderInfo.GetRef().Number, image.GetFileType()
        ));
        using (MemoryStream ms = new MemoryStream(image.GetImageAsBytes())) {
          Images.Add(ms.ToArray());
        }
      } 
      catch (IOException ie) {
/*
 * pass-through; image type not supported by iText[Sharp]; e.g. jbig2
*/
      }
    }
  }
}

iText[Sharp] 开发团队仍在努力实施,所以我不能确定它是否适用于 您的 案例。但它确实适用于this simple example PDF。 (在上面和我尝试使用位图图像的其他几个 PDF 中使用)

编辑:我也一直在试验新的 API,但在上面的原始代码示例中犯了一个错误。应该已将 PdfImageObject 初始化为空 outside try..catch 块。上面做了更正。

此外,当我在不受支持的图像类型(例如 jbig2)上使用上述代码时,我得到一个不同的异常 - “不支持颜色深度 XX”,其中“XX”是一个数字。在我尝试过的所有示例中,iTextSharp 确实支持FlateDecode。 (但在这个的情况下,我知道这对你没有帮助)

PDF 是由第三方软件制作的吗? (非 Adob​​e)从我在书中读到的内容来看,一些第三方供应商制作的 PDF 并不完全符合规范,iText[Sharp] 无法处理其中一些 PDF,而 Adob​​e 产品可以. IIRC 我在 iText 邮件列表中看到了特定于 Crystal Reports 生成的某些 PDF 的案例,这些案例导致了问题,here's one thread

有什么方法可以使用您正在使用的软件和一些非敏感的FlateDecode 图像生成测试 PDF?那么也许这里有人可以提供更好的帮助。

【讨论】:

  • 我最初尝试过这个,但记得遇到了一个问题。我只是再次旋转它,当我调用 renderInfo.GetImage() 时,IndexOutOfRangeException 表面。我已经相应地更新了这个问题。在获取图像的 width*height/8 时,初始 byte[] 长度减少了 1 的事实让我有点怀疑这也可能是异常的原因。
  • 更新了代码示例并添加了一些额外的想法。见上文。
  • 我首先尝试使用 bigsan 的解决方案,它奏效了。正如评论中所指出的,颜色是倒置的,但除此之外它是完美的。
  • 感谢您的更新。如果您不介意,我仍然想知道 PDF 是由第三方供应商还是 Adob​​e 制作的。同样奇怪的是,只有 一些/FlateDecode 图像会抛出异常。如果您可以使用非敏感数据创建测试文件,并在邮件列表或 [项目页面] (sourceforge.net/projects/itextsharp) 上发布类似的问题作为可能的错误,(关于新的解析 API)我认为它对开发团队和 iTextSharp 用户社区都有帮助。你的问题很有趣:)
  • 就 PDF 而言,情况好坏参半。部分页面/叠加层是使用 xpression 创建的,而其他部分是使用名为 Doc1 的工具创建的。在任何一种情况下,生成的格式都是 AFP。现在有另一个工具代替了 xpression,我不知道它的名称用于构建叠加层。这些数据全部合并到一个 PDF 中,我相信它实际上并不是通过 Adob​​e 软件生成的;但是我不是 100% 确定这一点。至少可以说这是一个混搭。当我调整字节数据时,异常出现了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-11
  • 2020-05-08
  • 2013-08-10
  • 2014-05-24
  • 2012-03-30
  • 1970-01-01
相关资源
最近更新 更多