Почему мое изображение искажается при декодировании как FlateDecode с помощью iTextSharp? - PullRequest
4 голосов
/ 13 декабря 2011

При декодировании изображения в PDF как FlateDecode через iTextSharp изображение искажается, и я не могу понять, почему.

Распознанный bpp - Format1bppIndexed. Если я изменю значение PixelFormat на Format4bppIndexed, изображение будет в некоторой степени узнаваемо (уменьшено, цвета отключены, но читаемы) и будет дублировано 4 раза по горизонтали. Если я настраиваю формат пикселя на Format8bppIndexed, он также в некоторой степени распознается и дублируется 8 раз по горизонтали.

Изображение ниже соответствует формату Format1bppIndexed пикселей. К сожалению, я не могу показать остальные из-за ограничений безопасности.

distorted image

Код, приведенный ниже, по сути, является единственным решением, с которым я столкнулся, замусоренным вокруг 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?

ПРИМЕЧАНИЕ : я не хочу использовать другую библиотеку для извлечения изображений. Я ищу решение, использующее ТОЛЬКО 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); я получаю исключение.

Попытка чтения или записи в защищенную память. Это часто признак того, что другая память повреждена.

Я предполагаю, что это связано с изменением размера и, следовательно, больше не соответствует используемым байтовым данным.

ОБНОВЛЕНИЕ : согласно рекомендации Джимми, я подтвердил, что вызов PdfReader.GetStreamBytes возвращает длину в байтах [], равную ширине height / 8, так как GetStreamBytes должен вызывать FlateDecode. В результате ручного вызова FlateDecode и вызова PdfReader.GetStreamBytes длина байта [] составила 1049401, а ширина высота / 8 равна 2544 * 3300/8 или 1049400, поэтому разница равна 1. Не уверен, что это будет коренной причиной или нет, выключено одним; однако я не уверен, как решить, если это действительно так.

ОБНОВЛЕНИЕ : При попытке использовать подход, упомянутый kuujinbo, меня встречает IndexOutOfRangeException, когда я пытаюсь вызвать renderInfo.GetImage(); в приемнике RenderImage. Тот факт, что ширина * высота / 8, как указано выше, отключена на 1 по сравнению с длиной байта [] при вызове FlateDecode, заставляет меня думать, что все они едины; однако решение все еще ускользает от меня.

   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, и изображение для этого конкретного экземпляра не создается.

Ответы [ 2 ]

10 голосов
/ 15 декабря 2011

Попробуйте скопировать данные построчно, возможно, это решит проблему.

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);
}
1 голос
/ 15 декабря 2011

Если вы можете использовать последнюю версию (5.1.3), API для извлечения FlateDecode и других типов изображений был упрощен с использованием пространства имен 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] все еще работает над реализацией, поэтому я не могу точно сказать, сработает ли она в вашем случае. Но он работает на в этом простом примере PDF . (используется выше и с парой других PDF-файлов, которые я пробовал с растровыми изображениями)

РЕДАКТИРОВАТЬ : Я тоже экспериментировал с новым API и допустил ошибку в приведенном выше примере исходного кода. Должен был инициализировать PdfImageObject в ноль вне блока try..catch. Исправление сделано выше.

Кроме того, когда я использую приведенный выше код для неподдерживаемого типа изображения (например, jbig2), я получаю другое исключение - «Глубина цвета XX не поддерживается», где «XX» - это число. И iTextSharp поддерживает поддержку FlateDecode во всех примерах, которые я пробовал. (но это не поможет вам в этом случае, я знаю)

PDF создается сторонним программным обеспечением? (не Adobe) Из того, что я прочитал в этой книге, некоторые сторонние поставщики создают PDF-файлы, которые не полностью соответствуют спецификации, и iText [Sharp] не может справиться с некоторыми из этих PDF-файлов, в то время как продукты Adobe могут , IIRC Я видел случаи, связанные с некоторыми PDF-файлами, сгенерированными Crystal Reports, в списке рассылки iText, которые вызывали проблемы, вот один поток .

Можно ли как-нибудь сгенерировать тестовый PDF с помощью программного обеспечения, которое вы используете с некоторыми нечувствительными FlateDecode изображениями? Тогда, может быть, кто-то здесь мог бы помочь немного лучше.

...