• 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

     

  • 相关阅读:
    [Angular 9] Built-in template syntax $any
    [Angular 9] Improved Dependency Injection with the new providedIn scopes 'any' and 'platform'
    [Angular 9 Unit testing] Stronger typing for dependency injection in tests
    [Angular] Preserve the current route’s query parameters when navigating with the Angular Router
    [Angular] Do relative routing inside component
    [Typescript] Make your optional fields required in TypeScript
    [Typescript] Exclude Properties from a Type in TypeScript
    [Javascript] Hide Properties from Showing Up in "for ... in" Loops in JavaScript
    [Debug] Set and remove DOM breakpoints
    【职业素养】4种让你显得没教养的做法
  • 原文地址:https://www.cnblogs.com/yimingwang/p/15867990.html
Copyright © 2020-2023  润新知