ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 143|回复: 3

[分享] VBA 调用 Ghostscript API 将PDF转换为图片

[复制链接]

TA的精华主题

TA的得分主题

发表于 2024-10-12 23:27 | 显示全部楼层 |阅读模式
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 ycz22 于 2024-10-12 23:28 编辑

Option Explicit
'备注:低版本的不支持中文路径(要9.0或更高版本)
'------------------------------------------------
'API 调用开始
'------------------------------------------------
'Win32 API 声明
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal dest As Long, ByVal source As Long, ByVal bytes As Long)
'此函数将一定字节长度的数据从内存中的一个位置(源)复制到另一个位置(目的地).

'GhostScript API 声明
Private Declare Function gsapi_revision Lib "gsdll32.dll" (ByVal pGSRevisionInfo As Long, ByVal intLen As Long) As Long
'此函数返回 GhostScript 解释器库包括版本号相关信息的字符串, 您应该在任何其他解释器库函数之前将其调用, 以确保已加载正确版本的 GhostScript 解释器
Private Declare Function gsapi_new_instance Lib "gsdll32.dll" (ByRef lngGSInstance As Long, ByVal lngCallerHandle As Long) As Long
'创建 Ghostscript 新的实例.此实例传递给大多数其他 gsapi 函数. lngCallerHandle 将提供给回调函数. 在这个阶段, Ghostscript 只支持一个实例.
Private Declare Function gsapi_set_stdio Lib "gsdll32.dll" (ByVal lngGSInstance As Long, ByVal gsdll_stdin As Long, ByVal gsdll_stdout As Long, ByVal gsdll_stderr As Long) As Long
'设置 stdio 的回调函数. 回调中使用的 handle 将取自传递给 gsapi_new_instance() 的值. 否则, 此函数的行为将与 gsapi_set_stdio_with_handle()匹配.
Private Declare Sub gsapi_delete_instance Lib "gsdll32.dll" (ByVal lngGSInstance As Long)
'销毁 GhostScript 的实例. 在您调用此之前, GhostScript 必须已经完成. 如果 GhostScript 已初始化, 则必须在 gsapi_delete_instance()之前调用 gsapi_exit()
Private Declare Function gsapi_init_with_args Lib "gsdll32.dll" (ByVal lngGSInstance As Long, ByVal lngArgumentCount As Long, ByVal lngArguments As Long) As Long
'初始化解释器.这在 imainarg.c 中调用 gs_main_init_with_args().返回代码请参阅返回代码列表. 参数与 "C" 主函数相同: argv(0)将被忽略, 用户提供的参数是从 argv(1)至 argv(Ubound(argv)).
Private Declare Function gsapi_run_file Lib "gsdll32.dll" (ByVal lngGSInstance As Long, ByVal strFileName As String, ByVal intErrors As Long, ByVal intExitCode As Long) As Long
'gsapi_run_file 函数与 gs_main_run_file 类似,只是省略了 error_object. 如果此函数返回 <= -100, 则表示退出或发生致命错误. 接下来必须调用 gsapi_exit().
Private Declare Function gsapi_exit Lib "gsdll32.dll" (ByVal lngGSInstance As Long) As Long
'退出 GhostScript 解释器,如果已调用 gsapi_init_with_args(),则必须在关闭时调用该命令, 并且在 gsapi_delete_instance()之前调用

'------------------------------------------------
'API 调用开始结束
'------------------------------------------------

'------------------------------------------------
'回调函数开始
'------------------------------------------------
'只有使用 gsapi_set_stdio() 时才需要这些

Public Function gsdll_stdin(ByVal intGSInstanceHandle As Long, ByVal strz As Long, ByVal intBytes As Long) As Long
    'VBA 没有 console, 只需返回 EOF
    gsdll_stdin = 0
End Function

Public Function gsdll_stdout(ByVal intGSInstanceHandle As Long, ByVal strz As Long, ByVal intBytes As Long) As Long
    '如果你能想到更有效率的方法, 请告知.
    '我们需要从字节缓冲区转换为字符串
    '首先, 我们创建一个适当大小的字节数组
    Dim aByte() As Byte
    ReDim aByte(intBytes)
    '然后我们获取字节数组的地址
    Dim ptrByte As Long
    ptrByte = VarPtr(aByte(0))
    '然后我们字节将缓冲区复制到字节数组
    CopyMemory ptrByte, strz, intBytes
    '然后我们将字节数组逐个字符复制到字符串
    Dim str As String
    Dim i As Long
    For i = 0 To intBytes - 1
        str = str + Chr(aByte(i))
    Next
    '最后,我们输出信息
    gsdll_stdout = intBytes
End Function

Public Function gsdll_stderr(ByVal intGSInstanceHandle As Long, ByVal strz As Long, ByVal intBytes As Long) As Long
    gsdll_stderr = gsdll_stdout(intGSInstanceHandle, strz, intBytes)
End Function
'------------------------------------------------
'回调函数结束
'------------------------------------------------


'------------------------------------------------
'用户自定义函数开始
'------------------------------------------------
Public Function AnsiZtoString(ByVal strz As Long) As String
    '我们需要从字节缓冲区转换为字符串
    Dim byteCh(1) As Byte
    Dim bOK As Boolean
    bOK = True
    Dim ptrByte As Long
    ptrByte = VarPtr(byteCh(0))
    Dim j As Long
    j = 0
    Dim str As String
    While bOK
        '这就是指针算术的方法!
        CopyMemory ptrByte, strz + j, 1
        If byteCh(0) = 0 Then
            bOK = False
        Else
            str = str + Chr(byteCh(0))
        End If
        j = j + 1
    Wend
    AnsiZtoString = str
End Function

Public Function CallGS(ByRef astrGSArgs() As String) As Boolean
    Dim intReturn As Long
    Dim intGSInstanceHandle As Long
    Dim aAnsiArgs() As String
    Dim aPtrArgs() As Long
    Dim intCounter As Long
    Dim intElementCount As Long
    Dim iTemp As Long
    Dim callerHandle As Long
    Dim ptrArgs As Long

    ' 加载 Ghostscript 并获取实例的 handle
    intReturn = gsapi_new_instance(intGSInstanceHandle, callerHandle)
    If (intReturn < 0) Then
        CallGS = False
        Return
    End If

    '捕获 stdio
    intReturn = gsapi_set_stdio(intGSInstanceHandle, AddressOf gsdll_stdin, AddressOf gsdll_stdout, AddressOf gsdll_stderr)

    If (intReturn >= 0) Then
        '将 Unicode 字符串转换为以 null 结尾的 ANSI 字节数组
        '然后获取指向字节数组的指针.
        intElementCount = UBound(astrGSArgs)
        ReDim aAnsiArgs(intElementCount)
        ReDim aPtrArgs(intElementCount)
      
        For intCounter = 0 To intElementCount
            aAnsiArgs(intCounter) = StrConv(astrGSArgs(intCounter), vbFromUnicode)
            aPtrArgs(intCounter) = StrPtr(aAnsiArgs(intCounter))
        Next
        ptrArgs = VarPtr(aPtrArgs(0))
      
        intReturn = gsapi_init_with_args(intGSInstanceHandle, intElementCount + 1, ptrArgs)

        ' 停止 Ghostscript 解释器
        gsapi_exit (intGSInstanceHandle)
    End If

    ' 释放 Ghostscript 实例 handle
    gsapi_delete_instance (intGSInstanceHandle)

    If (intReturn >= 0) Then
        CallGS = True
    Else
        CallGS = False
    End If

End Function

Function getPdfPageNum(ByVal sPath As String) As Long '获取PDF文件的总页数
         Dim iFN As Integer, bPos As Long
         iFN = FreeFile
         Dim bFileSize As Long
         bFileSize = FileLen(sPath)
         Open sPath For Binary Access Read As iFN
         Dim arrResult() As Byte
         '读取字节流
         arrResult = InputB(bFileSize, iFN)
         Dim arrFind() As Byte
         '要查找的字节串,"/Count"为pdf总页数的特征字符标识
         arrFind = StrConv("/Count", vbFromUnicode)
         '设置查找的起始位置
         bPos = 0
         bPos = InStrB(bPos + 1, arrResult, arrFind, vbBinaryCompare)
         '设置个变量预装"/Count"之后的字符
         Dim sResult As String
         sResult = Space(1000)
         Get iFN, bPos + 6, sResult
         getPdfPageNum = Val(sResult)
         Close iFN
End Function

Private Function Pdf2Img(InputFile As String, OutputFile As String, Optional DPI As Long = 300) As Boolean
    Dim DeviceFormat As String, fp As Long, lp As Long, OFile As String, astrArgs(11) As String
    If LCase(Right(OutputFile, 3)) = "jpg" Then
       DeviceFormat = "jpeg"
    ElseIf LCase(Right(OutputFile, 3)) = "png" Then
       DeviceFormat = "png16m"
    ElseIf LCase(Right(OutputFile, 3)) = "bmp" Then
       DeviceFormat = "bmp16m"
    End If
    If getPdfPageNum(InputFile) > 1 Then
       fp = 1
       lp = getPdfPageNum(InputFile)
       OFile = Split(OutputFile, ".")(0) & "_%02d." & Split(OutputFile, ".")(1)  '页码01代表1位数,02代表2位数,依次类推.
    Else
       fp = 1
       lp = 1
       OFile = OutputFile
    End If
    astrArgs(0) = "pdf2img"                     '首个参数将被忽略
    astrArgs(1) = "-dNOPAUSE"                   '每一页转换之间没有停顿
    astrArgs(2) = "-dBATCH"                     '防止 gs 进入交互模式
    astrArgs(3) = "-dPARANOIDSAFER"             '在安全模式下运行此命令
    astrArgs(4) = "-sDEVICE=" & DeviceFormat    '转换输出的文件类型格式
    astrArgs(5) = "-r" & DPI                    '图像分辨率
    astrArgs(6) = "-dQUIET"                     '静默的意思,指代执行过程中尽可能少的输出日志等信息.(也可以简写为"-q")
    astrArgs(7) = "-dNOPROMPT"                  '禁用用户交互提示
    astrArgs(8) = "-dFirstPage=" & fp           '从第几页开始
    astrArgs(9) = "-dLastPage=" & lp            '到第几页结束
    astrArgs(10) = "-sOutputFile=" & OFile      '输出文件路径,使用%d或%ld输出页数
    astrArgs(11) = InputFile                    '输入文件路径

    Pdf2Img = CallGS(astrArgs)                  '提交参数数组执行转换
End Function

'------------------------------------------------
'用户自定义函数结束
'------------------------------------------------

PdftoImg.zip

3.83 KB, 下载次数: 13

TA的精华主题

TA的得分主题

发表于 2024-10-13 09:57 | 显示全部楼层
非常好,能改成64位也可以使用的吗

TA的精华主题

TA的得分主题

发表于 2024-10-13 10:04 来自手机 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
getPdfPageNum不通用 有的PDF根本没有/count 还是用我的pq获取页数 通用不管有没有/Count

TA的精华主题

TA的得分主题

 楼主| 发表于 2024-10-13 11:22 来自手机 | 显示全部楼层
perfect131 发表于 2024-10-13 10:04
getPdfPageNum不通用 有的PDF根本没有/count 还是用我的pq获取页数 通用不管有没有/Count

网上找的,谢谢反馈
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-1 10:35 , Processed in 0.032562 second(s), 9 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表