golang实现PDF转图片

总体实现:利用 cgo 基于pdfium(C库)实现pdf单页转图片,多页转图片后可拼接为长图

前期准备 linux上安装C库

1 创建文件目录结构   /opt/pdfium   下载pdfium-linux-x64,解压放入此目录下 (https://github.com/wanggang826/go-pdfium-demo 中 lib可下载)

 

 

 

为了保证编译的时候,pkg-config能够找到这个C语言库,需要为这个库生成一个描述文件,pdfium.pc,放入目录:/usr/lib64/pkgconfig。

3 为了运行时候能找到动态库,需要把so文件拷贝到/usr/lib目录下,赋予755权限,并执行ldconfig指令

 

CODE

package render

// #cgo pkg-config: pdfium
// #include "fpdfview.h"
// #include "fpdf_annot.h"
// #include "fpdf_edit.h"
// #include "fpdf_structtree.h"
import "C"

import (
    "errors"
    "fmt"
    "github.com/nfnt/resize"
    "image"
    "image/color"
    "image/draw"
    "image/jpeg"
    "io/ioutil"
    "log"
    "math"
    "os"
    "strconv"
    "sync"
    "unsafe"
)

// Document is good
type Document struct {
    doc  C.FPDF_DOCUMENT
    data *[]byte // Keep a refrence to the data otherwise wierd stuff happens
}

const MaxWidth float64 = 600

var mutex = &sync.Mutex{}

// NewDocument shoud have docs
func NewDocument(data *[]byte) (*Document, error) {
    mutex.Lock()
    defer mutex.Unlock()
    // doc := C.FPDF_LoadDocument(C.CString("in.pdf"), nil)
    doc := C.FPDF_LoadMemDocument(
        unsafe.Pointer(&((*data)[0])),
        C.int(len(*data)),
        nil)

    if doc == nil {
        var errMsg string

        //defer C.FPDF_CloseDocument(doc)
        errorcase := C.FPDF_GetLastError()
        switch errorcase {
        case C.FPDF_ERR_SUCCESS:
            errMsg = "Success"
        case C.FPDF_ERR_UNKNOWN:
            errMsg = "Unknown error"
        case C.FPDF_ERR_FILE:
            errMsg = "Unable to read file"
        case C.FPDF_ERR_FORMAT:
            errMsg = "Incorrect format"
        case C.FPDF_ERR_PASSWORD:
            errMsg = "Invalid password"
        case C.FPDF_ERR_SECURITY:
            errMsg = "Invalid encryption"
        case C.FPDF_ERR_PAGE:
            errMsg = "Incorrect page"
        default:
            errMsg = "Unexpected error"
        }
        return nil, errors.New(errMsg)
    }
    return &Document{doc: doc, data: data}, nil
}

// GetPageCount shoud have docs
func (d *Document) GetPageCount() int {
    mutex.Lock()
    defer mutex.Unlock()
    return int(C.FPDF_GetPageCount(d.doc))
}

// CloseDocument shoud have docs
func (d *Document) Close() {
    mutex.Lock()
    C.FPDF_CloseDocument(d.doc)
    mutex.Unlock()
}

// RenderPage should have docs
func (d *Document) RenderPage(i int, dpi int) *image.RGBA {
    mutex.Lock()

    page := C.FPDF_LoadPage(d.doc, C.int(i))
    scale := float64(dpi) / 72.0
    imgWidth := C.FPDF_GetPageWidth(page) * C.double(scale)
    imgHeight := C.FPDF_GetPageHeight(page) * C.double(scale)

    // pixelBound := int(dpi * (3508 / 300))
    // imgWidthRatio := float64(pixelBound) / float64(imgWidth)
    // imgHeightRatio := float64(pixelBound) / float64(imgHeight)
    // scaleFactor := math.Min(imgWidthRatio, imgHeightRatio)
    scaleFactor := 1.0

    width := C.int(imgWidth * C.double(scaleFactor))
    height := C.int(imgHeight * C.double(scaleFactor))

    alpha := C.FPDFPage_HasTransparency(page)

    //创建空白位图对象
    bitmap := C.FPDFBitmap_Create(width, height, alpha)

    fillColor := 4294967295
    if int(alpha) == 1 {
        fillColor = 0
    }
    C.FPDFBitmap_FillRect(bitmap, 0, 0, width, height, C.ulong(fillColor))
    C.FPDF_RenderPageBitmap(bitmap, page, 0, 0, width, height, 0, C.FPDF_ANNOT) //C.FPDF_ANNOT 彩色|C.FPDF_GRAYSCALE 黑白

    p := C.FPDFBitmap_GetBuffer(bitmap)

    img := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
    img.Stride = int(C.FPDFBitmap_GetStride(bitmap))
    mutex.Unlock()

    // This takes a bit of time and I *think* we can do this without the lock
    bgra := make([]byte, 4)
    for y := 0; y < int(height); y++ {
        for x := 0; x < int(width); x++ {
            for i := range bgra {
                bgra[i] = *((*byte)(p))
                p = unsafe.Pointer(uintptr(p) + 1)
            }
            color := color.RGBA{B: bgra[0], G: bgra[1], R: bgra[2], A: bgra[3]}
            img.SetRGBA(x, y, color)
        }
    }
    mutex.Lock()
    C.FPDFBitmap_Destroy(bitmap)
    C.FPDF_ClosePage(page)
    mutex.Unlock()

    // should maybe return err
    //println(C.FPDF_GetLastError())

    return img
}

func InitLibrary() {
    mutex.Lock()
    C.FPDF_InitLibrary()
    mutex.Unlock()
}

func DestroyLibrary() {
    mutex.Lock()
    C.FPDF_DestroyLibrary()
    mutex.Unlock()
}

// FixSize 图片拼接之前计算  宽度尺寸
func FixSize(img1W, img2W int) (new1W, new2W int) {
    var ( //为了方便计算,将两个图片的宽转为 float64
        img1Width, img2Width = float64(img1W), float64(img2W)
        ratio1, ratio2       float64
    )

    minWidth := math.Min(img1Width, img2Width) // 取出两张图片中宽度最小的为基准

    if minWidth > 600 { // 如果最小宽度大于600,那么两张图片都需要进行缩放
        ratio1 = MaxWidth / img1Width // 图片1的缩放比例
        ratio2 = MaxWidth / img2Width // 图片2的缩放比例

        // 原宽度 * 比例 = 新宽度
        return int(img1Width * ratio1), int(img2Width * ratio2)
    }

    // 如果最小宽度小于600,那么需要将较大的图片缩放,使得两张图片的宽度一致
    if minWidth == img1Width {
        ratio2 = minWidth / img2Width // 图片2的缩放比例
        return img1W, int(img2Width * ratio2)
    }

    ratio1 = minWidth / img1Width // 图片1的缩放比例
    return int(img1Width * ratio1), img2W
}

// MergeImageNew  拼接图片
func MergeImageNew(basePath string, maskPath string, outImageName string) {
    file1, _ := os.Open(basePath) //打开图片1
    file2, _ := os.Open(maskPath) //打开图片2
    defer file1.Close()
    defer file2.Close()

    // image.Decode 图片
    var (
        img1, img2 image.Image
        err        error
    )
    if img1, _, err = image.Decode(file1); err != nil {
        log.Fatal(err)
        return
    }
    if img2, _, err = image.Decode(file2); err != nil {
        log.Fatal(err)
        return
    }
    b1 := img1.Bounds()
    b2 := img2.Bounds()
    new1W, new2W := FixSize(b1.Max.X, b2.Max.X)

    // 调用resize库进行图片缩放(高度填0,resize.Resize函数中会自动计算缩放图片的宽高比)
    m1 := resize.Resize(uint(new1W), 0, img1, resize.Lanczos3)
    m2 := resize.Resize(uint(new2W), 0, img2, resize.Lanczos3)

    // 将两个图片合成一张
    newWidth := m1.Bounds().Max.X                                                                          //新宽度 = 随意一张图片的宽度
    newHeight := m1.Bounds().Max.Y + m2.Bounds().Max.Y                                                     // 新图片的高度为两张图片高度的和
    newImg := image.NewNRGBA(image.Rect(0, 0, newWidth, newHeight))                                        //创建一个新RGBA图像
    draw.Draw(newImg, newImg.Bounds(), m1, m1.Bounds().Min, draw.Over)                                     //画上第一张缩放后的图片
    draw.Draw(newImg, newImg.Bounds(), m2, m2.Bounds().Min.Sub(image.Pt(0, m1.Bounds().Max.Y)), draw.Over) //画上第二张缩放后的图片(这里需要注意Y值的起始位置)

    // 保存文件
    os.Remove(outImageName + ".jpg")
    imgFile, _ := os.Create(outImageName + ".jpg")
    defer imgFile.Close()
    jpeg.Encode(imgFile, newImg, &jpeg.Options{100})
}

// PdfToImg PDF 转 图片 并拼接后保存
func PdfToImg(filePath string,outName string){
    data, _ := ioutil.ReadFile(filePath)
    outImgName := "./outImg/"+outName
    //C.FPDF_InitLibrary()
    InitLibrary()
    d, err := NewDocument(&data)
    if err != nil {
        println(err)
    } else {
        count := d.GetPageCount()
        img0 := d.RenderPage(0, 600)
        fb, _ := os.OpenFile(outImgName+".jpg", os.O_WRONLY|os.O_CREATE, 0600)
        jpeg.Encode(fb, img0, nil)
        fb.Close()
        imgSlice := make([]string, 0, 30)
        for i := 0; i < count; i++ {

            img := d.RenderPage(i, 600)
            f, _ := os.OpenFile(outImgName+strconv.Itoa(i)+".jpg", os.O_WRONLY|os.O_CREATE, 0600)
            if errSImg := jpeg.Encode(f, img, nil); errSImg != nil {
                fmt.Println(errSImg)
            }
            f.Close()
            imgSlice = append(imgSlice, outImgName+strconv.Itoa(i)+".jpg")
        }
        if len(imgSlice) > 1 {
            for j := 1; j < count; j++ {
                MergeImageNew(outImgName+".jpg", outImgName+strconv.Itoa(j)+".jpg", outImgName)
                os.Remove(outImgName+strconv.Itoa(j)+".jpg") //删除单图
            }
        }
        os.Remove(outImgName+strconv.Itoa(0)+".jpg")
        d.Close()
    }
    DestroyLibrary()
}

 完成代码https://github.com/wanggang826/go-pdfium-demo

 

 

posted @ 2022-02-07 15:20  $wanggang  阅读(1835)  评论(0编辑  收藏  举报