visual studio license itextsharp

studio - Extraer imágenes usando iTextSharp



itextsharp pdf (6)

Aquí hay una solución más simple:

iTextSharp.text.pdf.parser.PdfImageObject pdfImage = new iTextSharp.text.pdf.parser.PdfImageObject(imgPRStream); System.Drawing.Image img = pdfImage.GetDrawingImage();

He estado utilizando este código con gran éxito para extraer la primera imagen que se encuentra en cada página de un PDF. Sin embargo, ahora no está funcionando con algunos archivos PDF nuevos por una razón desconocida. He usado otras herramientas (Datalogics, etc.) que extraen bien las imágenes con estos nuevos PDF. Sin embargo, no quiero comprar Datalogics ni ninguna otra herramienta si puedo usar iTextSharp. ¿Alguien puede decirme por qué este código no encuentra las imágenes en el PDF?

Conocidos: mis archivos PDF solo tienen 1 imagen por página y nada más.

using iTextSharp.text; using iTextSharp.text.pdf; ... public static void ExtractImagesFromPDF(string sourcePdf, string outputPath) { // NOTE: This will only get the first image it finds per page. PdfReader pdf = new PdfReader(sourcePdf); RandomAccessFileOrArray raf = new iTextSharp.text.pdf.RandomAccessFileOrArray(sourcePdf); try { for (int pageNumber = 1; pageNumber <= pdf.NumberOfPages; pageNumber++) { PdfDictionary pg = pdf.GetPageN(pageNumber); PdfDictionary res = (PdfDictionary)PdfReader.GetPdfObject(pg.Get(PdfName.RESOURCES)); PdfDictionary xobj = (PdfDictionary)PdfReader.GetPdfObject(res.Get(PdfName.XOBJECT)); if (xobj != null) { foreach (PdfName name in xobj.Keys) { PdfObject obj = xobj.Get(name); if (obj.IsIndirect()) { PdfDictionary tg = (PdfDictionary)PdfReader.GetPdfObject(obj); PdfName type = (PdfName)PdfReader.GetPdfObject(tg.Get(PdfName.SUBTYPE)); if (PdfName.IMAGE.Equals(type)) { int XrefIndex = Convert.ToInt32(((PRIndirectReference)obj).Number.ToString(System.Globalization.CultureInfo.InvariantCulture)); PdfObject pdfObj = pdf.GetPdfObject(XrefIndex); PdfStream pdfStrem = (PdfStream)pdfObj; byte[] bytes = PdfReader.GetStreamBytesRaw((PRStream)pdfStrem); if ((bytes != null)) { using (System.IO.MemoryStream memStream = new System.IO.MemoryStream(bytes)) { memStream.Position = 0; System.Drawing.Image img = System.Drawing.Image.FromStream(memStream); // must save the file while stream is open. if (!Directory.Exists(outputPath)) Directory.CreateDirectory(outputPath); string path = Path.Combine(outputPath, String.Format(@"{0}.jpg", pageNumber)); System.Drawing.Imaging.EncoderParameters parms = new System.Drawing.Imaging.EncoderParameters(1); parms.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Compression, 0); System.Drawing.Imaging.ImageCodecInfo jpegEncoder = Utilities.GetImageEncoder("JPEG"); img.Save(path, jpegEncoder, parms); break; } } } } } } } } catch { throw; } finally { pdf.Close(); raf.Close(); } }


De c # version:

private IList<System.Drawing.Image> GetImagesFromPdfDict(PdfDictionary dict, PdfReader doc){ List<System.Drawing.Image> images = new List<System.Drawing.Image>(); PdfDictionary res = (PdfDictionary)(PdfReader.GetPdfObject(dict.Get(PdfName.RESOURCES))); PdfDictionary xobj = (PdfDictionary)(PdfReader.GetPdfObject(res.Get(PdfName.XOBJECT))); if (xobj != null) { foreach (PdfName name in xobj.Keys) { PdfObject obj = xobj.Get(name); if (obj.IsIndirect()) { PdfDictionary tg = (PdfDictionary)(PdfReader.GetPdfObject(obj)); pdf.PdfName subtype = (pdf.PdfName)(pdf.PdfReader.GetPdfObject(tg.Get(pdf.PdfName.SUBTYPE))); if (pdf.PdfName.IMAGE.Equals(subtype)) { int xrefIdx = ((pdf.PRIndirectReference)obj).Number; pdf.PdfObject pdfObj = doc.GetPdfObject(xrefIdx); pdf.PdfStream str = (pdf.PdfStream)(pdfObj); byte[] bytes = pdf.PdfReader.GetStreamBytesRaw((pdf.PRStream)str); string filter = tg.Get(pdf.PdfName.FILTER).ToString(); string width = tg.Get(pdf.PdfName.WIDTH).ToString(); string height = tg.Get(pdf.PdfName.HEIGHT).ToString(); string bpp = tg.Get(pdf.PdfName.BITSPERCOMPONENT).ToString(); if (filter == "/FlateDecode") { bytes = pdf.PdfReader.FlateDecode(bytes, true); System.Drawing.Imaging.PixelFormat pixelFormat; switch (int.Parse(bpp)) { case 1: pixelFormat = System.Drawing.Imaging.PixelFormat.Format1bppIndexed; 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); using (var ms = new MemoryStream()) { bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png); bytes = ms.GetBuffer(); } } images.Add(System.Drawing.Image.FromStream(new MemoryStream(bytes))); } else if (pdf.PdfName.FORM.Equals(subtype) || pdf.PdfName.GROUP.Equals(subtype)) { images.AddRange(GetImagesFromPdfDict(tg, doc)); } } } } return images; }


Descubrí que mi problema era que no estaba buscando imágenes de forma recursiva dentro de formularios y grupos. Básicamente, el código original solo encontraría imágenes incrustadas en la raíz del documento pdf. Aquí está el método revisado más un nuevo método (FindImageInPDFDictionary) que busca recursivamente las imágenes en la página. NOTA: los defectos de solo admitir imágenes JPEG y no comprimidas siguen siendo válidos. Consulte el código de R Ubben para conocer las opciones para corregir esos defectos. HTH alguien.

public static void ExtractImagesFromPDF(string sourcePdf, string outputPath) { // NOTE: This will only get the first image it finds per page. PdfReader pdf = new PdfReader(sourcePdf); RandomAccessFileOrArray raf = new iTextSharp.text.pdf.RandomAccessFileOrArray(sourcePdf); try { for (int pageNumber = 1; pageNumber <= pdf.NumberOfPages; pageNumber++) { PdfDictionary pg = pdf.GetPageN(pageNumber); // recursively search pages, forms and groups for images. PdfObject obj = FindImageInPDFDictionary(pg); if (obj != null) { int XrefIndex = Convert.ToInt32(((PRIndirectReference)obj).Number.ToString(System.Globalization.CultureInfo.InvariantCulture)); PdfObject pdfObj = pdf.GetPdfObject(XrefIndex); PdfStream pdfStrem = (PdfStream)pdfObj; byte[] bytes = PdfReader.GetStreamBytesRaw((PRStream)pdfStrem); if ((bytes != null)) { using (System.IO.MemoryStream memStream = new System.IO.MemoryStream(bytes)) { memStream.Position = 0; System.Drawing.Image img = System.Drawing.Image.FromStream(memStream); // must save the file while stream is open. if (!Directory.Exists(outputPath)) Directory.CreateDirectory(outputPath); string path = Path.Combine(outputPath, String.Format(@"{0}.jpg", pageNumber)); System.Drawing.Imaging.EncoderParameters parms = new System.Drawing.Imaging.EncoderParameters(1); parms.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Compression, 0); System.Drawing.Imaging.ImageCodecInfo jpegEncoder = Utilities.GetImageEncoder("JPEG"); img.Save(path, jpegEncoder, parms); } } } } } catch { throw; } finally { pdf.Close(); raf.Close(); } } private static PdfObject FindImageInPDFDictionary(PdfDictionary pg) { PdfDictionary res = (PdfDictionary)PdfReader.GetPdfObject(pg.Get(PdfName.RESOURCES)); PdfDictionary xobj = (PdfDictionary)PdfReader.GetPdfObject(res.Get(PdfName.XOBJECT)); if (xobj != null) { foreach (PdfName name in xobj.Keys) { PdfObject obj = xobj.Get(name); if (obj.IsIndirect()) { PdfDictionary tg = (PdfDictionary)PdfReader.GetPdfObject(obj); PdfName type = (PdfName)PdfReader.GetPdfObject(tg.Get(PdfName.SUBTYPE)); //image at the root of the pdf if (PdfName.IMAGE.Equals(type)) { return obj; }// image inside a form else if (PdfName.FORM.Equals(type)) { return FindImageInPDFDictionary(tg); } //image inside a group else if (PdfName.GROUP.Equals(type)) { return FindImageInPDFDictionary(tg); } } } } return null; }


El siguiente código incorpora todas las ideas de Dave y R Ubben más arriba, además de que devuelve una lista completa de todas las imágenes y también trata con varias profundidades de bits. Tuve que convertirlo a VB para el proyecto en el que estoy trabajando, perdón por eso ...

Private Sub getAllImages(ByVal dict As pdf.PdfDictionary, ByVal images As List(Of Byte()), ByVal doc As pdf.PdfReader) Dim res As pdf.PdfDictionary = CType(pdf.PdfReader.GetPdfObject(dict.Get(pdf.PdfName.RESOURCES)), pdf.PdfDictionary) Dim xobj As pdf.PdfDictionary = CType(pdf.PdfReader.GetPdfObject(res.Get(pdf.PdfName.XOBJECT)), pdf.PdfDictionary) If xobj IsNot Nothing Then For Each name As pdf.PdfName In xobj.Keys Dim obj As pdf.PdfObject = xobj.Get(name) If (obj.IsIndirect) Then Dim tg As pdf.PdfDictionary = CType(pdf.PdfReader.GetPdfObject(obj), pdf.PdfDictionary) Dim subtype As pdf.PdfName = CType(pdf.PdfReader.GetPdfObject(tg.Get(pdf.PdfName.SUBTYPE)), pdf.PdfName) If pdf.PdfName.IMAGE.Equals(subtype) Then Dim xrefIdx As Integer = CType(obj, pdf.PRIndirectReference).Number Dim pdfObj As pdf.PdfObject = doc.GetPdfObject(xrefIdx) Dim str As pdf.PdfStream = CType(pdfObj, pdf.PdfStream) Dim bytes As Byte() = pdf.PdfReader.GetStreamBytesRaw(CType(str, pdf.PRStream)) Dim filter As String = tg.Get(pdf.PdfName.FILTER).ToString Dim width As String = tg.Get(pdf.PdfName.WIDTH).ToString Dim height As String = tg.Get(pdf.PdfName.HEIGHT).ToString Dim bpp As String = tg.Get(pdf.PdfName.BITSPERCOMPONENT).ToString If filter = "/FlateDecode" Then bytes = pdf.PdfReader.FlateDecode(bytes, True) Dim pixelFormat As System.Drawing.Imaging.PixelFormat Select Case Integer.Parse(bpp) Case 1 pixelFormat = Drawing.Imaging.PixelFormat.Format1bppIndexed Case 24 pixelFormat = Drawing.Imaging.PixelFormat.Format24bppRgb Case Else Throw New Exception("Unknown pixel format " + bpp) End Select Dim bmp As New System.Drawing.Bitmap(Int32.Parse(width), Int32.Parse(height), pixelFormat) Dim bmd As System.Drawing.Imaging.BitmapData = 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) Using ms As New MemoryStream bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png) bytes = ms.GetBuffer End Using End If images.Add(bytes) ElseIf pdf.PdfName.FORM.Equals(subtype) Or pdf.PdfName.GROUP.Equals(subtype) Then getAllImages(tg, images, doc) End If End If Next End If End Sub


Esto es solo otra repetición de las ideas de otros, pero la que funcionó para mí. Aquí utilizo el fragmento de captura de imagen de @ Malco con el bucle de R Ubben:

private IList<System.Drawing.Image> GetImagesFromPdfDict(PdfDictionary dict, PdfReader doc) { List<System.Drawing.Image> images = new List<System.Drawing.Image>(); PdfDictionary res = (PdfDictionary)(PdfReader.GetPdfObject(dict.Get(PdfName.RESOURCES))); PdfDictionary xobj = (PdfDictionary)(PdfReader.GetPdfObject(res.Get(PdfName.XOBJECT))); if (xobj != null) { foreach (PdfName name in xobj.Keys) { PdfObject obj = xobj.Get(name); if (obj.IsIndirect()) { PdfDictionary tg = (PdfDictionary)(PdfReader.GetPdfObject(obj)); PdfName subtype = (PdfName)(PdfReader.GetPdfObject(tg.Get(PdfName.SUBTYPE))); if (PdfName.IMAGE.Equals(subtype)) { int xrefIdx = ((PRIndirectReference)obj).Number; PdfObject pdfObj = doc.GetPdfObject(xrefIdx); PdfStream str = (PdfStream)(pdfObj); iTextSharp.text.pdf.parser.PdfImageObject pdfImage = new iTextSharp.text.pdf.parser.PdfImageObject((PRStream)str); System.Drawing.Image img = pdfImage.GetDrawingImage(); images.Add(img); } else if (PdfName.FORM.Equals(subtype) || PdfName.GROUP.Equals(subtype)) { images.AddRange(GetImagesFromPdfDict(tg, doc)); } } } } return images; }


Lo anterior solo funcionará con archivos JPEG. Excluyendo las imágenes en línea y los archivos incrustados, debe revisar los objetos del subtipo IMAGEN, luego mirar el filtro y tomar la acción adecuada. Aquí hay un ejemplo, asumiendo que tenemos un PdfObject de subtipo IMAGEN:

PdfReader pdf = new PdfReader("c://temp//exp0.pdf"); int xo=pdf.XrefSize; for (int i=0;i<xo;i++) { PdfObject obj=pdf.GetPdfObject(i); if (obj!=null && obj.IsStream()) { PdfDictionary pd=(PdfDictionary)obj; if (pd.Contains(PdfName.SUBTYPE) && pd.Get(PdfName.SUBTYPE).ToString()=="/Image") { string filter=pd.Get(PdfName.FILTER).ToString(); string width=pd.Get(PdfName.WIDTH).ToString(); string height=pd.Get(PdfName.HEIGHT).ToString(); string bpp=pd.Get(PdfName.BITSPERCOMPONENT).ToString(); string extent="."; byte [] img=null; switch (filter) { case "/FlateDecode": byte[] arr=PdfReader.FlateDecode(PdfReader.GetStreamBytesRaw((PRStream)obj),true); Bitmap bmp=new Bitmap(Int32.Parse(width),Int32.Parse(height),PixelFormat.Format24bppRgb); BitmapData bmd=bmp.LockBits(new Rectangle(0,0,Int32.Parse(width),Int32.Parse(height)),ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); Marshal.Copy(arr,0,bmd.Scan0,arr.Length); bmp.UnlockBits(bmd); bmp.Save("c://temp//bmp1.png",ImageFormat.Png); break; default: break; } } } }

Esto arruinará el color debido al Microsoft BGR, por supuesto, pero quería mantenerlo corto. Haga algo similar para "/ CCITTFaxDecode", etc.