使用 iText 7,导出 Flate 编码图像的正确方法是什么?

2024-04-13

我正在尝试创建代码以使用 iText 版本 7.19 导出 PDF 中的图像。我在使用 Flate 编码图像时遇到一些问题。我使用的 Microsoft 免费书籍中的所有 Flate 编码图像作为示例(请参阅迁移到 Microsoft Visual Studio 2010 https://blogs.msdn.microsoft.com/microsoft_press/2010/09/13/free-ebook-moving-to-microsoft-visual-studio-2010/)总是呈粉红色,并且根据我尝试复制字节的方式,它们可能会出现扭曲。

如果我尝试一次复制所有图像字节(请参阅下面代码中的 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)}");
        }
    }
}

该代码可以在 .NET Core 控制台应用程序中实例化和调用,如下所示:

  string existingFileName = @"c:\temp\ReallyLongBook1.pdf";
  var imageExtractor = new MyPdfImageExtractor(existingFileName);
  imageExtractor.ExtractToDirectory(@"c:\temp\images");

我正在通过此代码运行以下免费的 Microsoft 书籍:迁移到 Microsoft Visual Studio 2010 https://blogs.msdn.microsoft.com/microsoft_press/2010/09/13/free-ebook-moving-to-microsoft-visual-studio-2010/

有问题的图像位于第 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;
    }
}

PDF 内部支持非常灵活的位图图像格式,特别是就不同的色彩空间而言。

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));
    }
}

与助手类ImageRenderListener:

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;
}

对于您的示例文档,它成功导出了近 400 个图像,其中包括上面的示例图像:

但也有不到 30 个图像无法导出,在标准输出上您会发现“图像无法读取:不支持色彩空间 /DeviceN..”

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 iText 7,导出 Flate 编码图像的正确方法是什么? 的相关文章

  • 我应该把 try/catch 和“using”语句放在哪里? [复制]

    这个问题在这里已经有答案了 可能的重复 try catch using 正确的语法 https stackoverflow com questions 4590490 try catch using right syntax 我想try c
  • WebClient.DownloadDataAsync 冻结了我的 UI

    我在 Form 构造函数中的 InitializeComponent 之后有以下代码 using WebClient client new WebClient client DownloadDataCompleted new Downloa
  • 如何使用 ASP.NET MVC 编辑多选列表?

    我想编辑一个如下所示的对象 我希望用 UsersGrossList 中的一个或多个用户填充 UsersSelectedList 使用 mvc 中的标准编辑视图 我只得到映射的字符串和布尔值 下面未显示 我在 google 上找到的许多示例都
  • 进程退出后 POSIX 名称信号量不会释放

    我正在尝试使用 POSIX 命名信号量进行跨进程同步 我注意到进程死亡或退出后 信号量仍然被系统打开 在进程 打开它 死亡或退出后是否有办法使其关闭 释放 早期的讨论在这里 当将信号量递减至零的进程崩溃时 如何恢复信号量 https sta
  • MFC CList 支持复制分配吗?

    我在 MSVC 中查找了 CList 定义afxtempl h http www cppdoc com example mfc classdoc MFC AFXTEMPL H html并记录在MSDN http msdn microsoft
  • 异常堆栈跟踪不显示抛出异常的位置

    通常 当我抛出异常 捕获它并打印出堆栈跟踪时 我会看到抛出异常的调用 导致该异常的调用 导致该异常的调用that 依此类推回到整个程序的根 现在它只向我显示异常所在的调用caught 而不是它所在的地方thrown 我不明白是什么改变导致了
  • 将 OpenCV Mat 转换为数组(可能是 NSArray)

    我的 C C 技能很生疏 OpenCV 的文档也相当晦涩难懂 有没有办法获得cv Mat data属性转换为数组 NSArray 我想将其序列化为 JSON 我知道我可以使用 FileStorage 实用程序转换为 YAML XML 但这不
  • 如何以编程方式播放 16 位 pcm 数组 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我有一个包含 16 位 pcm 值的短 数组 我希望能够在不添加任何标题 也不将任何文件保存到内存的情况下播放它 我知道我可能需要一个提供
  • 将下拉列表与字典绑定

    我将字典绑定到下拉列表 举例来说 我的字典中有以下项目 Test1 123 Test2 321 我希望下拉文本采用以下格式 Test1 Count 123 Test2 Count 321 我沿着以下路径走 但没有运气 MyDropDown
  • C# 编译器数字文字

    有谁知道 C 编译器数字文字修饰符的完整列表 默认情况下 声明 0 使其成为 Int32 声明 0 0 使其成为 Double 我可以在末尾使用文字修饰符 f 来确保某些内容被视为 Single 例如像这样 var x 0 x is Int
  • 使用 OleDbCommandBuilder 时访问 SQL 语法错误

    我要在 C 中使用 OleDbDataAdapter 在 Access 数据库中插入数据 但收到错误消息INSERT INTO 命令中的语法错误 BackgroundWorker worker new BackgroundWorker Ol
  • 如何使用 CSI.exe 脚本参数

    当你运行csi exe 安装了 Visual Studio 2015 update 2 您将得到以下语法 Microsoft R Visual C Interactive Compiler version 1 2 0 51106 Copyr
  • EnumDisplayDevices 与 WMI Win32_DesktopMonitor,如何检测活动监视器?

    对于我当前的 C 项目 我需要为在大量计算机上连接并处于活动状态的每个监视器检测一个唯一的字符串 研究指出了两种选择 使用 WMI 并查询 Win32 DesktopMonitor 以获取所有活动监视器 使用 PNPDeviceID 来唯一
  • 为什么 f(i = -1, i = -1) 是未定义的行为?

    我正在读关于违反评估顺序 http en cppreference com w cpp language eval order 他们举了一个令我困惑的例子 1 如果标量对象上的副作用相对于同一标量对象上的另一个副作用是无序的 则行为未定义
  • 从 NumPy 数组到 Mat 的 C++ 转换 (OpenCV)

    我正在围绕 ArUco 增强现实库 基于 OpenCV 编写一个薄包装器 我试图构建的界面非常简单 Python 将图像传递给 C 代码 C 代码检测标记并将其位置和其他信息作为字典元组返回给 Python 但是 我不知道如何在 Pytho
  • 有没有办法直接在函数参数中格式化字符串而不是使用临时字符串?

    我有一个接受字符串 字符数组 作为参数的函数 void enterString char my string 当使用这个函数时 我经常发现自己想要输入格式化的字符串 我使用 sprintf 来做到这一点 然而 我每次都必须创建一个临时字符串
  • 如何将 int 作为“void *”传递给线程启动函数?

    我最初有一个用于斐波那契变量数组的全局变量 但发现这是不允许的 我需要进行基本的多线程处理并处理竞争条件 但我无法在 pthread 创建中将 int 作为 void 参数提供 我尝试过使用常量指针 但没有成功 由于某些奇怪的原因 void
  • printf或iostream如何指定点后的最大位数

    字符串采用什么格式printf or iomanip我应该使用 iostream 中的运算符以以下格式打印浮点数 125 0 gt 125 125 1 gt 125 1 125 12312 gt 125 12 1 12345 gt 1 12
  • 将 char 绑定到枚举类型

    我有一段与此非常相似的代码 class someclass public enum Section START MID END vector section Full void ex for int i 0 i section
  • 为什么表达式 a = a + b - ( b = a ) 在 C++ 中给出序列点警告?

    以下是测试代码 int main int a 3 int b 4 a a b b a cout lt lt a lt lt a lt lt lt lt b lt lt b lt lt n return 0 编译此命令会出现以下警告 gt g

随机推荐