klippa-app / go-pdfium

Easy to use PDF library using Go and PDFium
MIT License
199 stars 17 forks source link

how can I make images compressed #194

Open ZMbiubiubiu opened 6 days ago

ZMbiubiubiu commented 6 days ago

I want to compress images in the pdf, but what I do is not work. here is my algorithm:

- FPDF_GetPageCount
 - Loop through every page
   - FPDF_LoadPage
   - FPDFPage_CountObjects
   - Loop through every object
     - FPDFPage_GetObject
     - FPDFPageObj_GetType == FPDF_PAGEOBJ_IMAGE
       - FPDFImageObj_GetBitmap
       - FPDFBitmap_GetStride
       - FPDFBitmap_GetWidth
       - FPDFBitmap_GetHeight
       - FPDFBitmap_GetFormat
       - Pull the image through libjpeg-turbo with the given compression
       - FPDFImageObj_LoadJpegFileInline the new file
   - FPDFPage_GenerateContent
   - FPDF_SaveAsCopy with FPDF_NO_INCREMENTAL

here is my code. I can extract images and try to compressed, but the compressed images are not loaded to the pdf. So No matter how many times I run it, the size of pdf stays the same. I'm trapped.

func CompressImagesInPlace(instance pdfium.Pdfium, inputPath string) error {

    // 因为是原地更新,测试阶段,先备份原文件
    outputPath := strings.Replace(inputPath, ".pdf", fmt.Sprintf(".%d.backup.pdf", time.Now().UnixNano()), 1)
    if err := util.CopyFile(inputPath, outputPath); err != nil {
        return fmt.Errorf("无法备份原文件: %v", err)
    }

    pdfDoc, err := instance.FPDF_LoadDocument(&requests.FPDF_LoadDocument{
        Path:     &outputPath,
        Password: nil,
    })
    if err != nil {
        return fmt.Errorf("无法加载 PDF 文档: %v", err)
    }
    defer instance.FPDF_CloseDocument(&requests.FPDF_CloseDocument{
        Document: pdfDoc.Document,
    })

    // 源文档页面数量
    pageCountRes, err := instance.FPDF_GetPageCount(&requests.FPDF_GetPageCount{
        Document: pdfDoc.Document,
    })
    if err != nil {
        return err
    }

    fmt.Printf("pdf page count: %d\n", pageCountRes.PageCount)

    // 遍历所有页面
    for i := 0; i < pageCountRes.PageCount; i++ {
        fmt.Printf("加载页面:%d\n", i)
        pdfPage, err := instance.FPDF_LoadPage(&requests.FPDF_LoadPage{
            Document: pdfDoc.Document,
            Index:    i,
        })
        if err != nil {
            return fmt.Errorf("无法加载页面: %v", err)
        }

        // 遍历一个页面中的对象
        objectCountRes, err := instance.FPDFPage_CountObjects(&requests.FPDFPage_CountObjects{
            Page: requests.Page{ByReference: &pdfPage.Page},
        })
        if err != nil {
            return fmt.Errorf("无法获取页面对象数量: %v", err)
        }

        for j := 0; j < objectCountRes.Count; j++ {
            objRes, err := instance.FPDFPage_GetObject(&requests.FPDFPage_GetObject{
                Page:  requests.Page{ByReference: &pdfPage.Page},
                Index: j,
            })
            if err != nil {
                return fmt.Errorf("无法获取页面对象: %v", err)
            }

            objTypeRes, err := instance.FPDFPageObj_GetType(&requests.FPDFPageObj_GetType{
                PageObject: objRes.PageObject,
            })
            if err != nil {
                return fmt.Errorf("无法获取页面对象类型: %v", err)
            }

            // log.Printf("对象类型:%d\n", objTypeRes.Type)

            if objTypeRes.Type == enums.FPDF_PAGEOBJ_IMAGE {
                //    - FPDFImageObj_GetBitmap
                //    - FPDFBitmap_GetStride
                //    - FPDFBitmap_GetWidth
                //    - FPDFBitmap_GetHeight
                //    - FPDFBitmap_GetFormat
                imageMetadataRes, err := instance.FPDFImageObj_GetImageMetadata(&requests.FPDFImageObj_GetImageMetadata{
                    ImageObject: objRes.PageObject,
                    Page:        requests.Page{ByReference: &pdfPage.Page},
                })
                if err != nil {
                    return fmt.Errorf("无法获取图片元数据: %v", err)
                }
                fmt.Printf("图片元数据:%+v\n", imageMetadataRes.ImageMetadata)

                imageDataDecodedRes, err := instance.FPDFImageObj_GetImageDataDecoded(&requests.FPDFImageObj_GetImageDataDecoded{
                    ImageObject: objRes.PageObject,
                })
                if err != nil {
                    return fmt.Errorf("无法获取图片解码数据: %v", err)
                }

                filePrefix := fmt.Sprintf("./images-files/decoded_%d_%d", i, j)
                filename, err := util.SaveImageFromData(imageDataDecodedRes.Data, filePrefix)
                if err != nil {
                    if strings.Contains(err.Error(), "无法解码图片") {
                        fmt.Printf("无法解码图片:%d_%d: %v\n", i, j, err)
                        continue
                    }
                    return fmt.Errorf("无法保存图片:%d_%d: %v", i, j, err)
                }

                // defer os.Remove(filename)

                filterRes, err := instance.FPDFImageObj_GetImageFilter(&requests.FPDFImageObj_GetImageFilter{
                    ImageObject: objRes.PageObject,
                })
                if err != nil {
                    return fmt.Errorf("无法获取图片filter:%d_%d: %v", i, j, err)
                }
                fmt.Printf("filterRes:%d_%d: %+v\n", i, j, filterRes.ImageFilter)

                _, err = instance.FPDFImageObj_LoadJpegFile(&requests.FPDFImageObj_LoadJpegFile{
                    ImageObject: objRes.PageObject,
                    Page: &requests.Page{
                        // ByReference: &pdfPage.Page,
                        ByIndex: &requests.PageByIndex{
                            Document: pdfDoc.Document,
                            Index:    i,
                        },
                    },
                    Count: 0,
                    // FileData: data,
                    FilePath: filename,
                })
                if err != nil {
                    fmt.Printf("FPDFImageObj_LoadJpegFileInline: %v\n", err)
                    return fmt.Errorf("无法加载图片: %v", err)
                }

            }

            _, err = instance.FPDFPage_GenerateContent(&requests.FPDFPage_GenerateContent{
                Page: requests.Page{
                    ByIndex: &requests.PageByIndex{
                        Document: pdfDoc.Document,
                        Index:    i,
                    },
                },
            })
            if err != nil {
                return fmt.Errorf("无法生成页面内容: %v", err)
            }
        }

        _, err = instance.FPDF_ClosePage(&requests.FPDF_ClosePage{
            Page: pdfPage.Page,
        })
        if err != nil {
            return err
        }
    }

    _, err = instance.FPDF_SaveAsCopy(&requests.FPDF_SaveAsCopy{
        Document: pdfDoc.Document,
        FilePath: &outputPath,
    })
    if err != nil {
        return fmt.Errorf("无法保存 PDF: %v", err)
    }

    util.CompareFileSize(inputPath, outputPath)

    return nil
}
jerbob92 commented 4 days ago

Hello,

It's important that you always use the same reference when doing these operations, so in your case, you should give the page reference in FPDFPage_GenerateContent, not the page index.

I don't see where you're compressing the image, so can't validate that part.

I'm also not sure if you can write to the output path like that if you still have the file opened.

ZMbiubiubiu commented 4 days ago

Thank you for your replay. First . You are right, when I use the same method to reference a page, the question that I can't take compressed images back to pdf is done. So, I have to use one of the following two ways, and I can't mix them

// method 1
Page: requests.Page{
ByIndex: &requests.PageByIndex{
    Document: pdfDoc.Document,
    Index:    i,
},
},
// method2
Page: requests.Page{
ByReference: &pdfPage.Page,
}

Am I right?

Second I'm having trouble decoding the image object source data. Here is my compression logic

func SaveImageFromData(data []byte, filePath string) (string, error) {
    writeRawFile(filePath, data)

    // 使用 image.Decode 直接解码图片
    img, format, err := image.Decode(bytes.NewBuffer(data))
    if err != nil {
        return "", fmt.Errorf("无法解码图片: %v", err)

    }

    // 根据格式保存文件
    switch format {
    case "jpeg", "jpg":
        filePath = fmt.Sprintf("%s.jpeg", filePath)
        return filePath, writeJPEGFile(filePath, img)
    case "png":
        filePath = fmt.Sprintf("%s.png", filePath)
        return filePath, writePNGFile(filePath, img)
    case "gif":
        filePath = fmt.Sprintf("%s.gif", filePath)
        return filePath, writeGIFFile(filePath, img)
    default:
        filePath = fmt.Sprintf("%s.raw", filePath)
        return filePath, writeRawFile(filePath, data)
    }

}

func writeJPEGFile(filename string, img image.Image) error {
    outFile, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer outFile.Close()

    opts := &jpeg.Options{Quality: 60} // Adjust the quality as needed
    return jpeg.Encode(outFile, img, opts)
}

The question is when I decode a /DCTDecode filter(hint a jpeg) image object, I can do it. The go library image.Decode can give the right format -- jpeg

filter:[DCTDecode] bitmap buffer len:199584 format:2 FPDF_BITMAP_FORMAT_BGR {Width:167 Height:396 HorizontalDPI:96 VerticalDPI:96 BitsPerPixel:24 Colorspace:2 MarkedContentID:-1}

But when I decode a /FlateDecode filter (hint a png) image object, Ican't do it. The go library image.Decode will return a error:unknown format.

filter:[FlateDecode] bitmap buffer len:226800 format:2 FPDF_BITMAP_FORMAT_BGR {Width:540 Height:140 HorizontalDPI:431.78244 VerticalDPI:431.78238 BitsPerPixel:24 Colorspace:2 MarkedContentID:-1}
image: unknown format

I tested three ways to extract the image object data:FPDFImageObj_GetImageDataDecoded/FPDFImageObj_GetImageDataRaw/FPDFImageObj_GetBitmap+FPDFBitmap_GetBuffer.

I'm taking these three results as arguments to SaveImageFromData separately. I can't decode with /FlateDecode filter image. (Why I know the image is png format, Because I use the xpdf tools(pdfimages) to extract images.

So there must be some information that I didn't use to extract the PNG image correctly.

jerbob92 commented 4 days ago

So, I have to use one of the following two ways, and I can't mix them

Correct, if you want to make changes to the page, use FPDF_LoadPage, always use the reference to that page and then close it again to make sure your changes are applied.

The question is when I decode a /DCTDecode filter(hint a jpeg) image object, I can do it. The go library image.Decode can give the right format -- jpeg But when I decode a /FlateDecode filter (hint a png) image object, Ican't do it. The go library image.Decode will return a error:unknown format.

I don't think these are exactly correct, I don't think the filter FlateDecode indicates that it's PNG. It's probably that xpdf exports it as PNG, since both Flate and PNG are lossy.

I think your best bet would be FPDFImageObj_GetBitmap since that will always get you a bitmap that you can just read into Go.

Use a combination of the following methods: FPDFImageObj_GetBitmap / FPDFBitmap_GetStride / FPDFBitmap_GetWidth / FPDFBitmap_GetHeight / FPDFBitmap_GetFormat / FPDFBitmap_GetBuffer.

Use a Go image.Image of the correct format (from FPDFBitmap_GetFormat) and size (FPDFBitmap_GetStride / FPDFBitmap_GetWidth / FPDFBitmap_GetHeight) and put the pdfium image data in the Go image.Image data (from FPDFBitmap_GetBuffer), then you can render that Go image.Image as a JPEG and you can then use FPDFImageObj_LoadJpegFileInline to put it in the PDF again.

Probably you only want to compress FlateDecode, and not the DCTDecode images.

ZMbiubiubiu commented 4 days ago

Thank you for your reply. I've made a lot of progress. But I'm running into a couple of new issues. Here is my image decode code

func ConvertToJPEG(width, height, stride int, data []byte, outputPath string, quality int, format int) error {
    // 创建一个 RGBA 图像
    img, err := RenderImage(data, width, height, stride, format)
    if err != nil {
        return err
    }

    // 创建输出文件
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()

    // 设置 JPEG 压缩质量
    jpegOptions := &jpeg.Options{Quality: quality}

    // 将图像编码为 JPEG 格式并写入输出文件
    if err := jpeg.Encode(outFile, img, jpegOptions); err != nil {
        return err
    }

    log.Printf("JPEG 图像已保存到: %s", outputPath)
    return nil
}

func RenderImage(data []byte, width int, height int, stride int, format int) (image.Image, error) {

    switch enums.FPDF_BITMAP_FORMAT(format) {
    case enums.FPDF_BITMAP_FORMAT_BGR:
        fmt.Println("BGR")
        img := image.NewRGBA(image.Rect(0, 0, width, height))
        for y := 0; y < height; y++ {
            for x := 0; x < width; x++ {

                var r, g, b, a uint8
                // 计算数据索引
                index := y*stride + x*3 // 每个像素有 3 个字节(BGR)

                if index+2 < len(data) { // 确保不越界
                    b = data[index]
                    g = data[index+1]
                    r = data[index+2]
                    a = 255 // 默认 alpha 为 255
                }
                img.Set(x, y, color.RGBA{r, g, b, a})
            }
        }
        return img, nil
                // reduce unused case code

    }
    return nil, fmt.Errorf("不支持的图片格式: %d", format)
}

Q1: with some pdf files, (I set jpeg quality = 90)the compressed result is bigger than the original file

Q2: with some pdf files, some images are destroyed 1732020358296

maybe the original image is a png file, so there has transparence setting, but when I extract images from pdf, for this image, I got message:

imageMetadataRes:{Width:540 Height:140 HorizontalDPI:431.78244 VerticalDPI:431.78238 BitsPerPixel:24 Colorspace:2 MarkedContentID:-1} 
filter:[FlateDecode] 
bitmap info:width:540 height:140 stride:1620 format:2 data len:226800

because the format is 2 == FPDF_BITMAP_FORMAT_BGR, So there only has r,g,b information.

Q3: with some pdf files, the compressed has lost a lot of message WX20241119-205525

I'm sorry I have so many questions to bother you, Wish you a happy life 🕶 @jerbob92

jerbob92 commented 4 days ago

Q1: with some pdf files, (I set jpeg quality = 90)the compressed result is bigger than the original file

Yes, I have noticed this as well, that's why I said you might not want to re-compress images that have a DCTDecode filter.

Q2: with some pdf files, some images are destroyed

Main issue might be that in Go, no transparency = black, in other implementations, no transparency = white. What can you do, in case of BGR, is fill the image with white before putting in the pixels, so this goes wrong in the encoding, not the decoding. I have had this battle before, please check this: https://github.com/klippa-app/go-pdfium/blob/main/internal/implementation_cgo/render.go#L495

Q3: with some pdf files, the compressed has lost a lot of message

Are you sure that the text that is gone now is actually inside an image? Did you try to store the image to see if the decoding was correct?

ZMbiubiubiu commented 3 days ago

Hello, thank you for your reply.

Q1:

Main issue might be that in Go, no transparency = black, in other implementations, no transparency = white. What can you do, in case of BGR, is fill the image with white before putting in the pixels, so this goes wrong in the encoding, not the decoding. I have had this battle before, please check this: https://github.com/klippa-app/go-pdfium/blob/main/internal/implementation_cgo/render.go#L495

What confused me was that I couldn't get the tranparence information in the image extracted by the pdf. Because I use FPDFBitmap_GetFormat, what I get is 2 == FPDF_BITMAP_FORMAT_BGR, there is no tansparence info. So I'm not sure when to add a white background.

Q2: I have another question about insert a same image in every page In my understanding, insert an image object into each page of the pdf, and then each image object can reference the same image data stream.But what I got, it seemed like every page had an image data stream inserted, resulting in a lot of files that were significantly larger in size.

here is my code

// 打开一个新的PDF文档
        filePdfDoc, err := instance.FPDF_LoadDocument(&requests.FPDF_LoadDocument{
            Path:     &filePath,
            Password: nil,
        })

        pageCount, err := instance.FPDF_GetPageCount(&requests.FPDF_GetPageCount{
            Document: filePdfDoc.Document,
        })

        //  get watermark image (a png file)
        watermarkTemp, err := os.Open(watermarkPath)
        defer watermarkTemp.Close()

        watermark, err := png.Decode(watermarkTemp)

        var rgbaImg *image.RGBA
        if rgba, ok := watermark.(*image.RGBA); ok {
            rgbaImg = rgba
        } else {
            // 如果图像不是RGBA格式,则进行转换
            rgbaImg = image.NewRGBA(watermark.Bounds())
            draw.Draw(rgbaImg, rgbaImg.Bounds(), watermark, image.Point{}, draw.Src)
        }

            watermarkWidth = rgbaImg.Rect.Dx()
            watermarkHeight = rgbaImg.Rect.Dy()

        watermarkBitmap, err := instance.FPDFBitmap_Create(&requests.FPDFBitmap_Create{
            Width:  watermarkWidth,
            Height: watermarkHeight,
            Alpha:  1,
        })

        watermarkBuffer, err := instance.FPDFBitmap_GetBuffer(&requests.FPDFBitmap_GetBuffer{
            Bitmap: watermarkBitmap.Bitmap,
        })

        watermarkStride, err := instance.FPDFBitmap_GetStride(&requests.FPDFBitmap_GetStride{
            Bitmap: watermarkBitmap.Bitmap,
        })

        stride := int(watermarkStride.Stride)
        // 将PNG图像数据复制到FPDF_BITMAP
        for y := 0; y < watermarkHeight; y++ {
            srcStart := y * watermarkWidth * 4
            dstStart := y * stride
            for x := 0; x < watermarkWidth; x++ {
                // 计算源图像和目标缓冲区的索引
                srcIndex := srcStart + x*4
                dstIndex := dstStart + x*4

                // 复制Alpha通道
                watermarkBuffer.Buffer[dstIndex+3] = rgbaImg.Pix[srcIndex+3]

                // 交换红色和蓝色通道,并复制绿色通道
                watermarkBuffer.Buffer[dstIndex] = rgbaImg.Pix[srcIndex+2]   // Blue
                watermarkBuffer.Buffer[dstIndex+1] = rgbaImg.Pix[srcIndex+1] // Green
                watermarkBuffer.Buffer[dstIndex+2] = rgbaImg.Pix[srcIndex]   // Red
            }
        }

        for pageIndex := 0; pageIndex < pageCount.PageCount; pageIndex++ {
            filePdfPageTemp, err := instance.FPDF_LoadPage(&requests.FPDF_LoadPage{
                Document: filePdfDoc.Document,
                Index:    pageIndex,
            })

            pageByIndex := requests.PageByIndex{
                Document: filePdfDoc.Document,
                Index:    pageIndex,
            }
            filePdfPage := requests.Page{
                ByIndex:     &pageByIndex,
                ByReference: &filePdfPageTemp.Page,
            }

            filePageWidth, err := instance.FPDF_GetPageWidth(&requests.FPDF_GetPageWidth{
                Page: filePdfPage,
            })
            // 获取页高
            filePageHeight, err := instance.FPDF_GetPageHeight(&requests.FPDF_GetPageHeight{
                Page: filePdfPage,
            })

            scale := math.Min(filePageHeight.Height, filePageWidth.Width) / 595

            watermarkImageObj, err := instance.FPDFPageObj_NewImageObj(&requests.FPDFPageObj_NewImageObj{
                Document: filePdfDoc.Document,
            })

            // 将图片加载到ImageObject中,ImageObject是Page中的图片对象
            _, err = instance.FPDFImageObj_SetBitmap(&requests.FPDFImageObj_SetBitmap{
                ImageObject: watermarkImageObj.PageObject,
                Bitmap:      watermarkBitmap.Bitmap,
            })

            // 调整图片对象的尺寸和位置
            _, err = instance.FPDFImageObj_SetMatrix(&requests.FPDFImageObj_SetMatrix{
                ImageObject: watermarkImageObj.PageObject,
                Transform: structs.FPDF_FS_MATRIX{
                    A: balabala,
                    B: 0,
                    C: 0,
                    D: balabala,
                    E: balabala,
                    F:balabala,
                },
            })

            _, err = instance.FPDFPage_InsertObject(&requests.FPDFPage_InsertObject{
                Page:       filePdfPage,
                PageObject: watermarkImageObj.PageObject,
            })

            _, err = instance.FPDFPage_GenerateContent(&requests.FPDFPage_GenerateContent{
                Page: filePdfPage,
            })

            _, err = instance.FPDF_ClosePage(&requests.FPDF_ClosePage{
                Page: filePdfPageTemp.Page,
            })
        }

        _, err = instance.FPDF_SaveAsCopy(&requests.FPDF_SaveAsCopy{
            Document: filePdfDoc.Document,
            FilePath: &outputPath,
        })
jerbob92 commented 3 days ago

What confused me was that I couldn't get the tranparence information in the image extracted by the pdf. Because I use FPDFBitmap_GetFormat, what I get is 2 == FPDF_BITMAP_FORMAT_BGR, there is no tansparence info. So I'm not sure when to add a white background.

The problem is that you're trying to put in a color model that has no transparency (FPDF_BITMAP_FORMAT_BGR) on an image that has transparency (image.NewRGBA). You can fix this by filling the image with white pixels first (by calling draw.Draw(img, img.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src) before setting the pixel) , or by using an image model that is similar to yours (this also allows you to copy the buffer, and not setting the pixels one by one). For example is here a package that implements the BGR model https://github.com/c3sr/image/blob/master/types/bgr_image.go#L11

In that case you can just copy data into the Pix array.

In my understanding, insert an image object into each page of the pdf, and then each image object can reference the same image data stream.But what I got, it seemed like every page had an image data stream inserted, resulting in a lot of files that were significantly larger in size.

I wouldn't know about that, that's Pdfium internals, this is just a library implementing it in Go. What you can try is calling FPDF_SaveAsCopy with Flags set to SaveFlagNoIncremental (I think this is default, but I'm not sure).

ZMbiubiubiu commented 3 days ago

I'm sorry for not making it clear, the problem I'm having is this: First of all, the image in the pdf originally had transparency information (because I inserted a png image into each page as a watermark), and this png was added to the attachment

convert

After adding watermark, I get this: WX20241120-195347@2x

Then I read this image from the pdf by getbitmap, and found that its format is FPDF_BITMAP_FORMAT_BGR, and the transparency information is lost. I get this: WX20241120-195559@2x

Then I use your method - add white background

img := image.NewRGBA(image.Rect(0, 0, width, height))
for y := 0; y < height; y++ {
    for x := 0; x < width; x++ {

        var r, g, b, a uint8
        index := y*stride + x*3 // 每个像素有 3 个字节(BGR)

        if index+2 < len(data) { // 确保不越界
            b = data[index]
            g = data[index+1]
            r = data[index+2]
            a = 0 // set zero 
        }
        img.Set(x, y, color.RGBA{r, g, b, a})
    }

imageWithWhiteBackground := image.NewRGBA(img.Bounds())
draw.Draw(imageWithWhiteBackground, imageWithWhiteBackground.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src)
draw.Draw(imageWithWhiteBackground, imageWithWhiteBackground.Bounds(), img, img.Bounds().Min, draw.Over)
img = imageWithWhiteBackground
}

after this , I use loadJpegInline, I got this: image

yes, I did get decent results.

But I really do have two questions.

1.I can do this by adding a white background, but I don't know when or under what circumstances to do this, because what images info I get that not give me transparence information 2.By adding a white background, there are some colorful impurities in the picture

logo.pdf logo-compress-50quality-without-white-background.pdf logo-compress-50quality-add-white-background.pdf

Wish you have a good day.

jerbob92 commented 3 days ago

First of all, the image in the pdf originally had transparency information (because I inserted a png image into each page as a watermark), and this png was added to the attachment

That doesn't really matter if you insert it as JPEG, it will lose it's alpha channel. I think you would have to use FPDFBitmap_Create (with Alpha > 0) and FPDFImageObj_SetBitmap to keep the alpha channel.

1.I can do this by adding a white background, but I don't know when or under what circumstances to do this, because what images info I get that not give me transparence information

I think you should do it when the image format is BGR, and not when it's BGRA, but I'm not sure, probably best to try and find out.

2.By adding a white background, there are some colorful impurities in the picture

I'm not sure where that is coming from. Perhaps they are just compression artifacts, quality 50 is really low, did you try a higher quality? Did you also try the other option? (using a BGR compatible image and just copying the image data over?)

ZMbiubiubiu commented 3 days ago

Thank you for your reply, I will try it tomorrow. Thank you again.

ZMbiubiubiu commented 2 days ago

I think I may have found the problem, according to this pose : How do I extract images from PDFs with no black background?

Use FPDFPageObj_GetType, there is no type named FPDF_PAGEOBJ_IMAGE_MASK. So I can't get the mask image, So every time I get a black image like this image So do you know the type of image_mask? How can I get it?

jerbob92 commented 2 days ago

I wouldn't know. Did you confirm with a PDF inspection tool (for example iText's RUPS) to check if that's actually the case? It may very well be that pdfium doesn't support it.

ZMbiubiubiu commented 2 days ago

Yes, I use the pdfimages -- a tool of xpdf tools, I can extract the two images extract-002 extract-003

I think this is the reason why I get a format 2 == FPDF_BITMAP_FORMAT_BGR, because FPDFImageObj_GetBitmap does not take into the image mask

    // FPDFImageObj_GetBitmap returns a bitmap rasterization of the given image object. FPDFImageObj_GetBitmap() only
    // operates on the image object and does not take the associated image mask into
    // account. It also ignores the matrix for the image object.
    // The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy()
    // must be called on the returned bitmap when it is no longer needed.
    FPDFImageObj_GetBitmap(request *requests.FPDFImageObj_GetBitmap) (*responses.FPDFImageObj_GetBitmap, error)

But I notice that FPDFImageObj_GetRenderedBitmap maybe support

    // FPDFImageObj_GetRenderedBitmap returns a bitmap rasterization of the given image object that takes the image mask and
    // image matrix into account. To render correctly, the caller must provide the
    // document associated with the image object. If there is a page associated
    // with the image object the caller should provide that as well.
    // The returned bitmap will be owned by the caller, and FPDFBitmap_Destroy()
    // must be called on the returned bitmap when it is no longer needed.
    // Experimental API.
    FPDFImageObj_GetRenderedBitmap(request *requests.FPDFImageObj_GetRenderedBitmap) (*responses.FPDFImageObj_GetRenderedBitmap, error)
ZMbiubiubiu commented 2 days ago

I run with FPDFImageObj_GetRenderedBitmap, but I meet this error:

go run -tags pdfium_experimental . github.com/klippa-app/go-pdfium/internal/implementation_cgo ../../../../../go/pkg/mod/github.com/klippa-app/go-pdfium@v1.13.0/internal/implementation_cgo/fpdf_catalog.go:53:13: could not determine kind of name for C.FPDFCatalog_SetLanguage

jerbob92 commented 2 days ago

You need to update your pdfium, FPDFCatalog_SetLanguage has only been recently added to pdfium, if you use the experimental flag then you need to be sure that your installed pdfium binary version matches the one that go-pdfium supports, see: https://github.com/klippa-app/go-pdfium?tab=readme-ov-file#experimental-cgo

Version v1.13.0 of go-pdfium is compatible with pdfium 6721, see https://github.com/klippa-app/go-pdfium/releases/tag/v1.13.0