C# 使用iText 7,什么';导出平面编码图像的正确方法是什么?

C# 使用iText 7,什么';导出平面编码图像的正确方法是什么?,c#,pdf,itext7,C#,Pdf,Itext7,我正在尝试创建代码,以便使用iText版本7.19导出PDF中的图像。我对平面编码图像有一些问题。我正在使用的Microsoft free book中的所有Flate编码图像作为示例(请参阅),它们总是显示为粉红色,这取决于我如何尝试复制它们可能出现扭曲的字节 如果我试图一次复制所有图像字节(请参阅下面代码中的SaveFlateEncodedImage2方法),它们会像下面这样失真: 如果我试图逐行复制它们(请参阅下面代码中的SaveFlateEncodedImage方法),它们会像这样呈粉红

我正在尝试创建代码,以便使用iText版本7.19导出PDF中的图像。我对平面编码图像有一些问题。我正在使用的Microsoft free book中的所有Flate编码图像作为示例(请参阅),它们总是显示为粉红色,这取决于我如何尝试复制它们可能出现扭曲的字节

如果我试图一次复制所有图像字节(请参阅下面代码中的SaveFlateEncodedImage2方法),它们会像下面这样失真:

如果我试图逐行复制它们(请参阅下面代码中的SaveFlateEncodedImage方法),它们会像这样呈粉红色

下面是我用来导出它们的代码:

using iText.Kernel;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Filters;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;

namespace ITextPdfStuff
{
    public class MyPdfImageExtractor
    {
        private readonly string _pdfFileName;

        public MyPdfImageExtractor(string pdfFileName)
        {
            _pdfFileName = pdfFileName;
        }

        public void ExtractToDirectory(string directoryName)
        {
            using (var reader = new PdfReader(_pdfFileName))
            {
                // Avoid iText.Kernel.Crypto.BadPasswordException: https://stackoverflow.com/a/48065052/97803
                reader.SetUnethicalReading(true);

                using (var pdfDoc = new PdfDocument(reader))
                {
                    ExtractImagesOnAllPages(pdfDoc, directoryName);
                }
            }
        }


        private void ExtractImagesOnAllPages(PdfDocument pdfDoc, string directoryName)
        {
            Console.WriteLine($"Number of pdf {pdfDoc.GetNumberOfPdfObjects()} objects");

            // Extract objects https://itextpdf.com/en/resources/examples/itext-7/extracting-objects-pdf
            for (int objNumber = 1; objNumber <= pdfDoc.GetNumberOfPdfObjects(); objNumber++)
            {

                PdfObject currentObject = pdfDoc.GetPdfObject(objNumber);

                if (currentObject != null && currentObject.IsStream())
                {
                    try
                    {                 
                        ExtractImagesOneImage(currentObject as PdfStream, Path.Combine(directoryName, $"image{objNumber}.png"));
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Object number {objNumber} is NOT an image! -- error: {ex.Message}");
                    }
                }
            }
        }

        private void ExtractImagesOneImage(PdfStream someStream, string fileName)
        {
            var pdfDict = (PdfDictionary)someStream;
            string subType = pdfDict.Get(PdfName.Subtype)?.ToString() ?? string.Empty;

            bool isImage = subType == "/Image";

            if (isImage == false)
                return;

            bool decoded = false;


            string filter = pdfDict.Get(PdfName.Filter).ToString();

            if (filter == "/FlateDecode")
            {
                SaveFlateEncodedImage(fileName, pdfDict, someStream.GetBytes(false));
            }
            else
            {
                byte[] imgData;

                try
                {
                    imgData = someStream.GetBytes(decoded);
                }
                catch (PdfException ex)
                {
                    imgData = someStream.GetBytes(!decoded);
                }

                SaveNormalImage(fileName, imgData);
            }

        }

        private void SaveNormalImage(string fileName, byte[] imgData)
        {
            using (var memStream = new System.IO.MemoryStream(imgData))
            using (var image = System.Drawing.Image.FromStream(memStream))
            {
                image.Save(fileName, ImageFormat.Png);
                Console.WriteLine($"{Path.GetFileName(fileName)}");
            }
        }

        private void SaveFlateEncodedImage(string fileName, PdfDictionary pdfDict, byte[] imgData)
        {
            int width = int.Parse(pdfDict.Get(PdfName.Width).ToString());
            int height = int.Parse(pdfDict.Get(PdfName.Height).ToString());
            int bpp = int.Parse(pdfDict.Get(PdfName.BitsPerComponent).ToString());

            // Example that helped: https://stackoverflow.com/a/8517377/97803
            PixelFormat pixelFormat;
            switch (bpp)
            {
                case 1:
                    pixelFormat = PixelFormat.Format1bppIndexed;
                    break;
                case 8:
                    pixelFormat = PixelFormat.Format8bppIndexed;
                    break;
                case 24:
                    pixelFormat = PixelFormat.Format24bppRgb;
                    break;
                default:
                    throw new Exception("Unknown pixel format " + bpp);
            }

            // .NET docs https://api.itextpdf.com/iText7/dotnet/7.1.9/classi_text_1_1_kernel_1_1_pdf_1_1_filters_1_1_flate_decode_strict_filter.html
            // Java docs have more detail: https://api.itextpdf.com/iText7/java/7.1.7/com/itextpdf/kernel/pdf/filters/FlateDecodeFilter.html
            imgData = FlateDecodeStrictFilter.FlateDecode(imgData, true);
            //  byte[] streamBytes = FlateDecodeStrictFilter.DecodePredictor(imgData, pdfDict);

            // Copy the image one row at a time
            using (var bmp = new Bitmap(width, height, pixelFormat))
            {
                BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat);

                int length = (int)Math.Ceiling(width * bpp / 8.0);
                for (int i = 0; i < height; i++)
                {
                    int offset = i * length;
                    int scanOffset = i * bmpData.Stride;
                    Marshal.Copy(imgData, offset, new IntPtr(bmpData.Scan0.ToInt64() + scanOffset), length);
                }

                bmp.UnlockBits(bmpData);
                bmp.Save(fileName, ImageFormat.Png);
            }

            Console.WriteLine($"FlateDecode! {Path.GetFileName(fileName)}");
        }


        /// <summary>This method distorts the image badly</summary>
        private void SaveFlateEncodedImage2(string fileName, PdfDictionary pdfDict, byte[] imgData)
        {
            int width = int.Parse(pdfDict.Get(PdfName.Width).ToString());
            int height = int.Parse(pdfDict.Get(PdfName.Height).ToString());
            int bpp = int.Parse(pdfDict.Get(PdfName.BitsPerComponent).ToString());

            // Example that helped: https://stackoverflow.com/a/8517377/97803
            PixelFormat pixelFormat;
            switch (bpp)
            {
                case 1:
                    pixelFormat = PixelFormat.Format1bppIndexed;
                    break;
                case 8:
                    pixelFormat = PixelFormat.Format8bppIndexed;
                    break;
                case 24:
                    pixelFormat = PixelFormat.Format24bppRgb;
                    break;
                default:
                    throw new Exception("Unknown pixel format " + bpp);
            }

            // .NET docs https://api.itextpdf.com/iText7/dotnet/7.1.9/classi_text_1_1_kernel_1_1_pdf_1_1_filters_1_1_flate_decode_strict_filter.html
            // Java docs have more detail: https://api.itextpdf.com/iText7/java/7.1.7/com/itextpdf/kernel/pdf/filters/FlateDecodeFilter.html
            imgData = FlateDecodeStrictFilter.FlateDecode(imgData, true);
            // byte[] streamBytes = FlateDecodeStrictFilter.DecodePredictor(imgData, pdfDict);

            // Copy the entire image in one go
            using (var bmp = new Bitmap(width, height, pixelFormat))
            {
                BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat);
                Marshal.Copy(imgData, 0, bmpData.Scan0, imgData.Length);
                bmp.UnlockBits(bmpData);
                bmp.Save(fileName, ImageFormat.Png);
            }

            Console.WriteLine($"FlateDecode! {Path.GetFileName(fileName)}");
        }
    }
}
我正在通过此代码运行以下免费Microsoft书籍:

有问题的图片在第10页,是黑白的(不是粉色的)

我不是PDF专家,我花了几天的时间来研究这段代码,现在我挑选了一些例子来尝试将其拼凑起来。任何能帮助我克服粉色图像的帮助,我都将不胜感激

-------更新日期:2020年2月4日------

以下是MKL建议更改后的修订版本。他的改变提取了比我更多的图像,并产生了在我上面提到的书中出现的看起来合适的图像:

using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Data;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
using iText.Kernel.Pdf.Xobject;
using System;
using System.Collections.Generic;
using System.IO;

namespace ITextPdfStuff
{
    public class MyPdfImageExtractor
    {
        private readonly string _pdfFileName;

        public MyPdfImageExtractor(string pdfFileName)
        {
            _pdfFileName = pdfFileName;
        }

        public void ExtractToDirectory(string directoryName)
        {
            using (var reader = new PdfReader(_pdfFileName))
            {
                // Avoid iText.Kernel.Crypto.BadPasswordException: https://stackoverflow.com/a/48065052/97803
                reader.SetUnethicalReading(true);

                using (var pdfDoc = new PdfDocument(reader))
                {
                    ExtractImagesOnAllPages(pdfDoc, directoryName);
                }
            }
        }

        private void ExtractImagesOnAllPages(PdfDocument pdfDoc, string directoryName)
        {
            Console.WriteLine($"Number of pdf {pdfDoc.GetNumberOfPdfObjects()} objects");

            IEventListener strategy = new ImageRenderListener(Path.Combine(directoryName, @"image{0}.{1}"));
            PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy);
            for (var i = 1; i <= pdfDoc.GetNumberOfPages(); i++)
            {
                parser.ProcessPageContent(pdfDoc.GetPage(i));
            }
        }
    }


    public class ImageRenderListener : IEventListener
    {
        public ImageRenderListener(string format)
        {
            this.format = format;
        }

        public void EventOccurred(IEventData data, EventType type)
        {
            if (data is ImageRenderInfo imageData)
            {
                try
                {
                    PdfImageXObject imageObject = imageData.GetImage();
                    if (imageObject == null)
                    {
                        Console.WriteLine("Image could not be read.");
                    }
                    else
                    {
                        File.WriteAllBytes(string.Format(format, index++, imageObject.IdentifyImageFileExtension()), imageObject.GetImageBytes());
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Image could not be read: {0}.", ex.Message);
                }
            }
        }

        public ICollection<EventType> GetSupportedEvents()
        {
            return null;
        }

        string format;
        int index = 0;
    }
}
使用iText.Kernel.Pdf;
使用iText.Kernel.Pdf.Canvas.Parser;
使用iText.Kernel.Pdf.Canvas.Parser.Data;
使用iText.Kernel.Pdf.Canvas.Parser.Listener;
使用iText.Kernel.Pdf.Xobject;
使用制度;
使用System.Collections.Generic;
使用System.IO;
命名空间ITEXTPDFSTAFF
{
公共类MyPdfImageExtractor
{
私有只读字符串_pdfFileName;
公共MyPdfImageExtractor(字符串PdfileName)
{
_PdfileName=PdfileName;
}
public void extractodirectory(字符串directoryName)
{
使用(变量读取器=新的PdfReader(_pdfFileName))
{
//避免iText.Kernel.Crypto.BadPasswordException:https://stackoverflow.com/a/48065052/97803
读者。设置不道德阅读(正确);
使用(var pdfDoc=新的PdfDocument(读卡器))
{
ExtractImagesOnAllPages(pdfDoc,目录名);
}
}
}
私有void ExtractImagesOnAllPages(PdfDocument pdfDoc,字符串directoryName)
{
WriteLine($“pdf{pdfDoc.GetNumberOfPdfObjects()}对象的数量”);
IEventListener strategy=newImageRenderListener(Path.Combine(directoryName,@“image{0}.{1}”);
PdfCanvasProcessor parser=新的PdfCanvasProcessor(策略);

对于(var i=1;iPDF内部支持非常灵活的位图图像格式,特别是对于不同的颜色空间而言

iText在其解析API中支持导出其中的一个子集,基本上是可以轻松导出为常规JPEG或PNG的图像子集

因此,首先尝试使用iText解析API进行导出是有意义的。您可以按如下方式进行:

Directory.CreateDirectory(@"extract\");
using (PdfReader reader = new PdfReader(@"Moving to Microsoft Visual Studio 2010 ebook.pdf"))
using (PdfDocument pdfDocument = new PdfDocument(reader))
{
    IEventListener strategy = new ImageRenderListener(@"extract\Moving to Microsoft Visual Studio 2010 ebook-i7-{0}.{1}");
    PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy);
    for (var i = 1; i <= pdfDocument.GetNumberOfPages(); i++)
    {
        parser.ProcessPageContent(pdfDocument.GetPage(i));
    }
}
在您的示例文档中,它成功导出了近400幅图像,其中包括上面的示例图像:


但也有不到30个图像无法导出,在standard out上,您会发现“无法读取图像:不支持颜色空间/设备…”

有什么原因让你处理原始数据而不使用itext解析框架吗?@mkl我不确定我是否理解。有没有更简单的方法来实现这一点?itext文档不是很好,而且我似乎找不到任何.NET C#示例来实现这一点。我发现了一些将文件作为原始数据转储,但没有一个将其导出为图像。这些事情我确实发现它们非常古老,而且在iText 5和7之间,该库发生了很大变化。此外,如果有其他不太昂贵的.NET PDF库可以做到这一点,我也愿意接受。“有没有更简单的方法来做到这一点?”-是的,您可以使用解析框架(通常用于提取文本,但实际上还具有访问位图和矢量图形的功能)。当我下周回到办公室时,我可以写一个更详细的解释。@mkl当你有机会的时候,我仍然对任何建议感兴趣。我仍然标记了你的问题。不幸的是,我标记了许多问题,所以我无法查看所有问题。你的方式提取的图像也比我的多。我走了t 318图像和您的方式导致399。谢谢!@DavidYates这些额外的图像中的一些可能是重复的。我的答案中的代码导出图像的每次使用。因此,多次使用的图像会被导出多次。
Directory.CreateDirectory(@"extract\");
using (PdfReader reader = new PdfReader(@"Moving to Microsoft Visual Studio 2010 ebook.pdf"))
using (PdfDocument pdfDocument = new PdfDocument(reader))
{
    IEventListener strategy = new ImageRenderListener(@"extract\Moving to Microsoft Visual Studio 2010 ebook-i7-{0}.{1}");
    PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy);
    for (var i = 1; i <= pdfDocument.GetNumberOfPages(); i++)
    {
        parser.ProcessPageContent(pdfDocument.GetPage(i));
    }
}
public class ImageRenderListener : IEventListener
{
    public ImageRenderListener(string format)
    {
        this.format = format;
    }

    public void EventOccurred(IEventData data, EventType type)
    {
        if (data is ImageRenderInfo imageData)
        {
            try
            {
                PdfImageXObject imageObject = imageData.GetImage();
                if (imageObject == null)
                {
                    Console.WriteLine("Image could not be read.");
                }
                else
                {
                    File.WriteAllBytes(string.Format(format, index++, imageObject.IdentifyImageFileExtension()), imageObject.GetImageBytes());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Image could not be read: {0}.", ex.Message);
            }
        }
    }

    public ICollection<EventType> GetSupportedEvents()
    {
        return null;
    }

    string format;
    int index = 0;
}