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


压缩格式 Buffer 的读取与导出
Section titled “压缩格式 Buffer 的读取与导出”当输入是完整的 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)}import { readFile } from 'node:fs/promises';import sharp from 'sharp';
const inputBuffer = await readFile('input.jpg');
const { data, info } = await sharp(inputBuffer, { sequentialRead: true }) .webp({ quality: 82 }) .toBuffer({ resolveWithObject: true });
console.log(`encoded ${data.byteLength} bytes (${info.width}x${info.height})`);from pathlib import Pathimport pyvips
input_bytes = Path("input.jpg").read_bytes()
image = pyvips.Image.new_from_buffer(input_bytes, "", access="sequential")output_bytes = image.write_to_buffer(".webp", Q=82)print(f"encoded {len(output_bytes)} bytes ({image.width}x{image.height})")#!/usr/bin/env bashset -euo pipefail
# CLI 通过标准输入/输出流配合格式后缀完成无临时文件管线vips thumbnail_source '[descriptor=0]' '.webp[Q=82]' 1938 < input.jpg > output.webp未压缩 Raw 像素内存直读与导出
Section titled “未压缩 Raw 像素内存直读与导出”当上游或下游是自研渲染器、视频帧解码器或深度学习张量时,可以先跳过 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, 3rawPixels := 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}_ = outRawimport sharp from 'sharp';
const width = 64;const height = 64;const channels = 3;const rawPixels = Buffer.alloc(width * height * channels, 0x2d);
const { data, info } = await sharp(rawPixels, { raw: { width, height, channels },}) .raw() .toBuffer({ resolveWithObject: true });
console.log(info.width, info.height, info.channels, data.byteLength);import pyvips
width, height, bands = 64, 64, 3raw_pixels = bytes([29, 45, 53] * (width * height))
img = pyvips.Image.new_from_memory(raw_pixels, width, height, bands, "uchar")out_raw = img.write_to_memory()# 将图像导出为无头部的未压缩 Raw 像素文件,再按指定宽高、通道数和位深读回vips rawsave input.jpg raw.binvips rawload raw.bin from-raw.webp 1938 1292 3 --format uchar与宿主语言生态对象零编码互转
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 ImageRefvipsImg, err := vips.NewImageFromGoImage(stdImg)if err != nil { return err}defer vipsImg.Close()
// govips ImageRef -> Go 标准库 image.ImageoutStdImg, err := vipsImg.ToGoImage()if err != nil { return err}_ = outStdImg// sharp 导出为 Uint8Array 供 WebAssembly / Canvas / WebGL 消费const { data, info } = await sharp('input.jpg') .ensureAlpha() .raw() .toBuffer({ resolveWithObject: true });
const rgbaView = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);console.log(rgbaView.length, info.width, info.height);import numpy as npimport pyvips
image = pyvips.Image.new_from_file("input.jpg")# pyvips Image -> NumPy ndarray (H, W, C)arr = np.asarray(image)
# NumPy ndarray -> pyvips Imagerebuilt = pyvips.Image.new_from_array(arr)# CLI 面向文件与管道,不直接操作宿主语言内存对象vips rawsave input.jpg - | wc -c