跳转到内容

内存 Buffer、Raw 像素与标准库图像互转

复制本页

在 Web 服务、消息队列和 AI 推理管线中,图像往往已驻留在内存中。libvips 生态下的“内存 I/O”分为两类截然不同的路径:压缩容器字节(Compressed Buffer)(如内存中的 JPEG/WebP 字节流)与未压缩像素内存(Raw Pixel Memory)(如 width × height × bands 连续排列的 RGB/RGBA 字节数组)。

读入内存字节切片的苏州东方之门夜景 JPEG 原图
输入:JPEG 字节缓冲区1938 × 1292,610,191 字节
在内存中完成 WebP 编码后导出的夜景图像
输出:WebP 字节缓冲区1938 × 1292,399,106 字节(govips 参考输出)

当输入是完整的 JPEG、PNG、WebP 或 AVIF 字节切片时,可以直接从内存构造图像并在内存中完成重编码,全程不产生临时文件。

govips 的 ExportWebp(以及 ExportJpeg、ExportPng、ExportAvif 等)返回三个值:编码后的 []byte、导出后的 *vips.ImageMetadata,以及 error。sharp 则提供 .toBuffer()(或 .toBuffer({ resolveWithObject: true }) 同时返回 { data, info })和 .toUint8Array()。

package main
import (
"fmt"
"os"
"github.com/davidbyttow/govips/v2/vips"
)
func transcodeBuffer(inputBytes []byte) ([]byte, *vips.ImageMetadata, error) {
params := vips.NewImportParams()
params.Access.Set(vips.AccessSequential)
image, err := vips.LoadImageFromBuffer(inputBytes, params)
if err != nil {
return nil, nil, err
}
defer image.Close()
export := vips.NewWebpExportParams()
export.Quality = 82
return image.ExportWebp(export)
}
func main() {
if err := vips.Startup(nil); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer vips.Shutdown()
src, err := os.ReadFile("input.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
out, meta, err := transcodeBuffer(src)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("encoded %d bytes (%dx%d)\n", len(out), meta.Width, meta.Height)
}

当上游或下游是自研渲染器、视频帧解码器或深度学习张量时,可以先跳过 PNG/JPEG 编解码开销,直接传递按行连续交错排列的未压缩像素内存(Interleaved Raw Pixels)。

从 govips v2.19.0 起,新增了公开函数 vips.NewImageFromMemory(pixels, width, height, bands, format, interpretation),与已有的 image.ToBytes() 配对完成 Raw 像素导入与导出。

// 构造一张 64×64、3 通道 (RGB)、8-bit 无符号整型的 Raw 像素缓冲
width, height, bands := 64, 64, 3
rawPixels := make([]byte, width*height*bands)
for i := 0; i < len(rawPixels); i += 3 {
rawPixels[i] = 29 // R
rawPixels[i+1] = 45 // G
rawPixels[i+2] = 53 // B
}
// govips v2.19.0+: 直接从 Raw 像素切片创建 ImageRef(需指定 BandFormat 与 Interpretation)
img, err := vips.NewImageFromMemory(rawPixels, width, height, bands, vips.BandFormatUchar, vips.InterpretationSRGB)
if err != nil {
return err
}
defer img.Close()
// 将处理后的像素重新导出为未压缩 Raw 字节切片
outRaw, err := img.ToBytes()
if err != nil {
return err
}
_ = outRaw

与宿主语言生态对象零编码互转

Section titled “与宿主语言生态对象零编码互转”

在 Go 和 Python 中,经常需要在 libvips 图像与语言生态通用对象(Go 标准库的 image.Image、Python 的 numpy.ndarray 或 PIL.Image)之间切换。切勿先编码成 PNG 再解码——直接使用内存级互转接口:

生态对象 推荐入口 说明
Go image.Image → govips vips.NewImageFromGoImage(goImg) 直接读取像素构造 *vips.ImageRef,无需 PNG/JPEG 编码往返
govips → Go image.Image img.ToGoImage() 直接将 ImageRef 像素导出为标准库 image.Image
Python numpy.ndarray ↔ pyvips Image.new_from_array(arr) / np.asarray(img) 支持 PEP 3118 缓冲协议与 __array_interface__,直接映射像素内存
Node.js TypedArray ↔ sharp sharp(uint8Array, { raw }) / .raw().toUint8Array() 直接在 Uint8Array 与未压缩像素管线之间传输
// Go 标准库 image.Image -> govips ImageRef
vipsImg, err := vips.NewImageFromGoImage(stdImg)
if err != nil {
return err
}
defer vipsImg.Close()
// govips ImageRef -> Go 标准库 image.Image
outStdImg, err := vipsImg.ToGoImage()
if err != nil {
return err
}
_ = outStdImg