I have been using this code with great success to pull out the first image found in each page of a PDF. However, it is now not working with some new PDFs for an uknown reason. I have used other tools (Datalogics, etc) that do pull out the images fine with these new PDFs. However, I do not want to buy Datalogics or any tool if I can use iTextSharp. Can anybody tell me why this code is not finding the images in the PDF?
Knowns: my PDFs only have 1 image per page and nothing else.
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();
}
}
-
The above will only work with JPEGs. Excluding inline images and embedded files, you need to go through the objects of subtype IMAGE, then look at the filter and take the appropriate action. Here's an example, assuming we have a PdfObject of subtype IMAGE:
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; } } } }
This will mess the color up because of the Microsoft BGR, of course, but I wanted to keep it short. Do something similar for "/CCITTFaxDecode", etc.
Dave : I appreciate the code. But I am not finding any IMAGE subtypes in my PDF. Everything is Indirect of type XObject. Any ideas on how I can find the images?R Ubben : Well, they are indirect xobjects; every stream must be indirect, according to the standard. What I did was go through the objects and look for streams. I should have included that in the code. I will edit it to add that part.R Ubben : So, you look through the objects for streams, then fetch its dictionary. The type will be stream, and for images, the subtype will be "/Image". -
I found that my problem was that I was not recursively searching inside of forms and groups for images. Basically, the original code would only find images that were embedded at the root of the pdf document. Here is the revised method plus a new method (FindImageInPDFDictionary) that recursively searches for images in the page. NOTE: the flaws of only supporting JPEG and non-compressed images still applies. See R Ubben's code for options to fix those flaws. HTH someone.
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; }
-
The following code incorporates all of Dave and R Ubben's ideas above, plus it returns a full list of all the images and also deals with multiple bit depths. I had to convert it to VB for the project I'm working on though, sorry about that...
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
-
I dint get to know to which name space this itext sharp belonging to..:( Which dll refrence i need to add ? Awaiting reply..
0 comments:
Post a Comment