Go image/png 包详解
概述
image/png 包提供 PNG 图像的编码和解码功能。PNG(Portable Network Graphics)是一种无损压缩的位图图像格式,支持透明度(Alpha 通道)、伽马校正和颜色校正。该包提供了 Encode、Decode 等核心函数,以及 Encoder、Decoder 结构体,广泛用于需要高质量图像和透明度支持的场景。
包导入
import "image/png"
基本使用
1. 编码 PNG 图像
package main
import (
"image"
"image/color"
"image/png"
"os"
)
func main() {
// 创建图像
img := image.NewRGBA(image.Rect(0, 0, 200, 200))
// 填充渐变(带透明度)
for y := 0; y < 200; y++ {
for x := 0; x < 200; x++ {
img.Set(x, y, color.RGBA{
R: uint8(x),
G: uint8(y),
B: 128,
A: uint8(255 * x / 200), // 渐变透明度
})
}
}
// 编码为 PNG
file, _ := os.Create("output.png")
defer file.Close()
png.Encode(file, img)
}
2. 解码 PNG 图像
package main
import (
"fmt"
"image/png"
"os"
)
func main() {
// 打开 PNG 文件
file, _ := os.Open("input.png")
defer file.Close()
// 解码 PNG
img, err := png.Decode(file)
if err != nil {
fmt.Println("解码失败:", err)
return
}
// 获取图像信息
bounds := img.Bounds()
fmt.Printf("图像尺寸:%dx%d\n", bounds.Dx(), bounds.Dy())
fmt.Printf("图像类型:%T\n", img)
}
3. 使用 Encoder 设置压缩级别
package main
import (
"image/png"
"os"
)
func main() {
img := loadYourImage() // 假设已定义
file, _ := os.Create("compressed.png")
defer file.Close()
// 创建编码器并设置压缩级别
encoder := png.Encoder{CompressionLevel: png.BestCompression}
encoder.Encode(file, img)
}
一、核心函数
Decode
定义:
func Decode(r io.Reader) (image.Image, error)
说明:
- 功能:解码 PNG 图像
- 参数:
r- io.Reader(如文件、字节流) - 返回值:
image.Image- 解码后的图像(可能是*image.RGBA、*image.NRGBA、*image.Gray等)error- 错误信息
- 特点:自动处理 PNG 的各种颜色类型和透明度
示例:
package main
import (
"fmt"
"image"
"image/png"
"os"
)
func main() {
file, err := os.Open("input.png")
if err != nil {
fmt.Println("打开失败:", err)
return
}
defer file.Close()
img, err := png.Decode(file)
if err != nil {
fmt.Println("解码失败:", err)
return
}
// 检查图像类型
switch v := img.(type) {
case *image.RGBA:
fmt.Println("RGBA 图像(带 alpha)")
case *image.NRGBA:
fmt.Println("NRGBA 图像(非预乘 alpha)")
case *image.Gray:
fmt.Println("灰度图像")
case *image.Paletted:
fmt.Println("调色板图像")
}
fmt.Printf("尺寸:%dx%d\n", img.Bounds().Dx(), img.Bounds().Dy())
}
DecodeConfig
定义:
func DecodeConfig(r io.Reader) (image.Config, error)
说明:
- 功能:解码 PNG 配置信息(不解码图像数据)
- 参数:
r- io.Reader - 返回值:
image.Config- 图像配置(尺寸、颜色模型)error- 错误信息
- 用途:快速获取图像尺寸等信息,无需加载完整图像
- 性能:比
Decode快,因为只读取 PNG 头部信息
示例:
package main
import (
"fmt"
"image/png"
"os"
)
func main() {
file, _ := os.Open("photo.png")
defer file.Close()
config, err := png.DecodeConfig(file)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Printf("尺寸:%dx%d\n", config.Width, config.Height)
fmt.Printf("颜色模型:%v\n", config.ColorModel)
}
Encode
定义:
func Encode(w io.Writer, img image.Image) error
说明:
- 功能:将图像编码为 PNG 格式
- 参数:
w- io.Writer(如文件、字节流)img- 要编码的图像
- 返回值:
error- 错误信息 - 默认设置:使用默认压缩级别(
png.DefaultCompression) - 自动处理:自动处理透明度、颜色空间转换
示例:
package main
import (
"image"
"image/color"
"image/png"
"os"
)
func main() {
// 创建带透明度的图像
img := image.NewRGBA(image.Rect(0, 0, 100, 100))
// 填充半透明红色
for y := 0; y < 100; y++ {
for x := 0; x < 100; x++ {
img.Set(x, y, color.RGBA{
R: 255,
G: 0,
B: 0,
A: 128, // 50% 透明
})
}
}
// 编码为 PNG(保留透明度)
file, _ := os.Create("transparent.png")
defer file.Close()
err := png.Encode(file, img)
if err != nil {
panic(err)
}
}
二、结构体
Decoder
定义:
type Decoder struct {
// 包含未导出的字段
}
说明:
- 功能:PNG 解码器
- 用途:提供更细粒度的解码控制
- 方法:
Decode(img image.Image) error- 解码到指定图像DecodeConfig() (image.Config, error)- 解码配置信息
示例:
package main
import (
"image"
"image/png"
"os"
)
func main() {
file, _ := os.Open("input.png")
defer file.Close()
// 创建解码器
decoder := png.NewDecoder(file)
// 先获取配置
config, err := decoder.DecodeConfig()
if err != nil {
panic(err)
}
// 创建目标图像
img := image.NewRGBA(image.Rect(0, 0, config.Width, config.Height))
// 解码到目标图像
err = decoder.Decode(img)
if err != nil {
panic(err)
}
}
Encoder
定义:
type Encoder struct {
CompressionLevel CompressionLevel // 压缩级别
}
字段说明:
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
CompressionLevel | CompressionLevel | DefaultCompression | 压缩级别 |
方法:
Encode(w io.Writer, img image.Image) error- 编码图像
示例:
package main
import (
"image"
"image/png"
"os"
)
func main() {
img := loadYourImage() // 假设已定义
// 创建编码器
encoder := &png.Encoder{
CompressionLevel: png.BestCompression,
}
// 编码
file, _ := os.Create("optimized.png")
defer file.Close()
err := encoder.Encode(file, img)
if err != nil {
panic(err)
}
}
三、类型
CompressionLevel
定义:
type CompressionLevel int
说明:
- 功能:定义 PNG 压缩级别
- 类型:整数类型
- 用途:控制压缩速度和文件大小之间的平衡
常量值:
| 常量 | 值 | 描述 | 使用场景 |
|---|---|---|---|
DefaultCompression | 0 | 默认压缩 | 一般用途 |
NoCompression | -2 | 不压缩 | 快速测试 |
BestSpeed | -1 | 最快速度 | 实时处理 |
BestCompression | -3 | 最佳压缩 | 存档、网络传输 |
HuffmanOnly | -4 | 仅 Huffman 编码 | 特殊情况 |
详细对比:
| 压缩级别 | 压缩速度 | 解压速度 | 文件大小 | CPU 使用 |
|---|---|---|---|---|
NoCompression | 最快 | 最快 | 最大 | 最低 |
BestSpeed | 快 | 快 | 较大 | 低 |
DefaultCompression | 中等 | 中等 | 中等 | 中等 |
BestCompression | 慢 | 中等 | 最小 | 高 |
HuffmanOnly | 慢 | 快 | 小 | 中等 |
示例 - 不同压缩级别对比:
package main
import (
"bytes"
"fmt"
"image/png"
"os"
)
func testCompression(img image.Image) {
levels := map[string]png.CompressionLevel{
"无压缩": png.NoCompression,
"最快速度": png.BestSpeed,
"默认压缩": png.DefaultCompression,
"最佳压缩": png.BestCompression,
"仅 Huffman": png.HuffmanOnly,
}
for name, level := range levels {
var buf bytes.Buffer
encoder := &png.Encoder{CompressionLevel: level}
err := encoder.Encode(&buf, img)
if err != nil {
panic(err)
}
fmt.Printf("%-10s: %8d 字节\n", name, buf.Len())
}
}
func main() {
// 加载测试图像
file, _ := os.Open("test.png")
defer file.Close()
img, _ := png.Decode(file)
testCompression(img)
}
Reader
定义:
type Reader interface {
io.Reader
}
说明:
- 功能:PNG 解码器的输入接口
- 嵌入:
io.Reader接口 - 实现:任何实现
io.Reader的类型都可以使用
常见类型:
*os.File- 文件*bytes.Reader- 字节切片*strings.Reader- 字符串http.Request.Body- HTTP 请求体
示例:
package main
import (
"bytes"
"fmt"
"image/png"
)
func decodeFromBytes(data []byte) error {
reader := bytes.NewReader(data)
img, err := png.Decode(reader)
if err != nil {
return err
}
fmt.Printf("解码成功:%dx%d\n", img.Bounds().Dx(), img.Bounds().Dy())
return nil
}
Writer
定义:
type Writer interface {
io.Writer
}
说明:
- 功能:PNG 编码器的输出接口
- 嵌入:
io.Writer接口 - 实现:任何实现
io.Writer的类型都可以使用
常见类型:
*os.File- 文件*bytes.Buffer- 字节缓冲区http.ResponseWriter- HTTP 响应*bufio.Writer- 缓冲写入器
示例:
package main
import (
"bytes"
"image/png"
)
func encodeToBytes(img image.Image) ([]byte, error) {
var buf bytes.Buffer
err := png.Encode(&buf, img)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func serveImage(w http.ResponseWriter, r *http.Request) {
img := generateImage() // 假设已定义
// 直接写入 HTTP 响应
w.Header().Set("Content-Type", "image/png")
png.Encode(w, img)
}
四、常量
压缩级别常量
定义:
const (
DefaultCompression CompressionLevel = 0
NoCompression CompressionLevel = -2
BestSpeed CompressionLevel = -1
BestCompression CompressionLevel = -3
HuffmanOnly CompressionLevel = -4
)
使用示例:
package main
import (
"image/png"
"os"
)
func main() {
img := loadYourImage()
// 场景 1:快速预览(速度优先)
file1, _ := os.Create("fast.png")
encoder1 := &png.Encoder{CompressionLevel: png.BestSpeed}
encoder1.Encode(file1, img)
// 场景 2:网络传输(大小优先)
file2, _ := os.Create("small.png")
encoder2 := &png.Encoder{CompressionLevel: png.BestCompression}
encoder2.Encode(file2, img)
// 场景 3:一般用途(平衡)
file3, _ := os.Create("normal.png")
png.Encode(file3, img) // 使用 DefaultCompression
}
五、典型示例
示例 1:JPEG 转 PNG(保留质量)
package main
import (
"fmt"
"image/jpeg"
"image/png"
"os"
)
func jpegToPNG(inputPath, outputPath string) error {
// 打开 JPEG
file, err := os.Open(inputPath)
if err != nil {
return err
}
defer file.Close()
// 解码 JPEG
img, err := jpeg.Decode(file)
if err != nil {
return err
}
// 编码 PNG
outFile, err := os.Create(outputPath)
if err != nil {
return err
}
defer outFile.Close()
err = png.Encode(outFile, img)
if err != nil {
return err
}
fmt.Printf("已转换:%s -> %s\n", inputPath, outputPath)
return nil
}
示例 2:创建带透明度的 PNG
package main
import (
"image"
"image/color"
"image/png"
"os"
)
func createTransparentPNG() {
// 创建 NRGBA 图像(支持透明度)
img := image.NewNRGBA(image.Rect(0, 0, 200, 200))
// 填充透明背景
for y := 0; y < 200; y++ {
for x := 0; x < 200; x++ {
img.Set(x, y, color.NRGBA{
R: 0,
G: 0,
B: 0,
A: 0, // 完全透明
})
}
}
// 绘制不透明圆形
cx, cy, r := 100, 100, 50
for y := 0; y < 200; y++ {
for x := 0; x < 200; x++ {
dx := x - cx
dy := y - cy
if dx*dx+dy*dy <= r*r {
img.Set(x, y, color.NRGBA{
R: 255,
G: 0,
B: 0,
A: 255, // 完全不透明
})
}
}
}
// 保存 PNG(保留透明度)
file, _ := os.Create("transparent_circle.png")
defer file.Close()
png.Encode(file, img)
}
示例 3:PNG 优化(选择最佳压缩)
package main
import (
"bytes"
"fmt"
"image/png"
"os"
)
func optimizePNG(inputPath, outputPath string) error {
// 打开源文件
file, err := os.Open(inputPath)
if err != nil {
return err
}
defer file.Close()
// 解码
img, err := png.Decode(file)
if err != nil {
return err
}
// 尝试不同压缩级别
var bestBuf *bytes.Buffer
bestSize := int64(-1)
levels := []png.CompressionLevel{
png.BestSpeed,
png.DefaultCompression,
png.BestCompression,
}
for _, level := range levels {
var buf bytes.Buffer
encoder := &png.Encoder{CompressionLevel: level}
err := encoder.Encode(&buf, img)
if err != nil {
return err
}
if bestSize < 0 || int64(buf.Len()) < bestSize {
bestSize = int64(buf.Len())
bestBuf = &buf
}
}
// 保存最佳结果
outFile, err := os.Create(outputPath)
if err != nil {
return err
}
defer outFile.Close()
_, err = outFile.Write(bestBuf.Bytes())
origSize, _ := os.Stat(inputPath)
fmt.Printf("原始:%d 字节 -> 优化:%d 字节 (节省 %.1f%%)\n",
origSize.Size(), bestSize,
float64(origSize.Size()-bestSize)/float64(origSize.Size())*100)
return err
}
示例 4:批量转换图像为 PNG
package main
import (
"fmt"
"image"
_ "image/gif" // 支持 GIF
_ "image/jpeg" // 支持 JPEG
"image/png"
"os"
"path/filepath"
)
func batchConvertToPNG(inputDir, outputDir string) error {
// 创建输出目录
os.MkdirAll(outputDir, 0755)
// 遍历输入目录
return filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
ext := filepath.Ext(path)
if ext == ".jpg" || ext == ".jpeg" || ext == ".gif" {
// 打开源文件
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// 解码(自动识别格式)
img, _, err := image.Decode(file)
if err != nil {
return err
}
// 创建输出路径
relPath, _ := filepath.Rel(inputDir, path)
outputPath := filepath.Join(outputDir, filepath.Base(relPath))
outputPath = outputPath[:len(outputPath)-len(ext)] + ".png"
// 编码 PNG
outFile, err := os.Create(outputPath)
if err != nil {
return err
}
defer outFile.Close()
err = png.Encode(outFile, img)
if err != nil {
return err
}
fmt.Printf("已转换:%s -> %s\n", path, outputPath)
}
return nil
})
}
示例 5:PNG 元数据读取
package main
import (
"fmt"
"image/png"
"os"
)
func readPNGMetadata(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// 解码配置
config, err := png.DecodeConfig(file)
if err != nil {
return err
}
fmt.Printf("文件:%s\n", path)
fmt.Printf("尺寸:%dx%d\n", config.Width, config.Height)
fmt.Printf("颜色模型:%v\n", config.ColorModel)
// 重新打开文件读取完整信息
file.Seek(0, 0)
// 解码图像
img, err := png.Decode(file)
if err != nil {
return err
}
bounds := img.Bounds()
fmt.Printf("实际尺寸:%dx%d\n", bounds.Dx(), bounds.Dy())
fmt.Printf("起点坐标:(%d, %d)\n", bounds.Min.X, bounds.Min.Y)
return nil
}
示例 6:内存中的 PNG 处理
package main
import (
"bytes"
"image"
"image/png"
)
// ImageProcessor 图像处理器
type ImageProcessor struct {
data []byte
}
// NewImageProcessor 从字节创建处理器
func NewImageProcessor(data []byte) *ImageProcessor {
return &ImageProcessor{data: data}
}
// Decode 从内存解码
func (p *ImageProcessor) Decode() (image.Image, error) {
reader := bytes.NewReader(p.data)
return png.Decode(reader)
}
// Encode 编码到内存
func (p *ImageProcessor) Encode(img image.Image) ([]byte, error) {
var buf bytes.Buffer
err := png.Encode(&buf, img)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// GetSize 获取图像尺寸(不解码完整图像)
func (p *ImageProcessor) GetSize() (int, int, error) {
reader := bytes.NewReader(p.data)
config, err := png.DecodeConfig(reader)
if err != nil {
return 0, 0, err
}
return config.Width, config.Height, nil
}
// 使用示例
func main() {
// 假设已有 PNG 数据
var pngData []byte
// 创建处理器
proc := NewImageProcessor(pngData)
// 获取尺寸(快速)
width, height, _ := proc.GetSize()
// 解码
img, _ := proc.Decode()
// 处理图像...
// 重新编码
newData, _ := proc.Encode(img)
}
示例 7:HTTP 服务中的 PNG 处理
package main
import (
"image"
"image/draw"
"image/png"
"net/http"
)
// 提供 PNG 图像
func imageHandler(w http.ResponseWriter, r *http.Request) {
img := generateImage() // 假设已定义
// 设置响应头
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "public, max-age=3600")
// 编码并发送
err := png.Encode(w, img)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
// 处理 PNG 上传
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 限制大小(10MB)
r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
// 解码上传的 PNG
img, err := png.Decode(r.Body)
if err != nil {
http.Error(w, "Invalid PNG", http.StatusBadRequest)
return
}
// 处理图像(例如添加水印)
watermarked := addWatermark(img)
// 返回处理后的图像
png.Encode(w, watermarked)
}
// addWatermark 添加水印
func addWatermark(img image.Image) image.Image {
// 实现水印逻辑
return img
}
func main() {
http.HandleFunc("/image", imageHandler)
http.HandleFunc("/upload", uploadHandler)
http.ListenAndServe(":8080", nil)
}
六、最佳实践
1. 选择合适的压缩级别
// 场景 1:开发/测试(速度优先)
encoder1 := &png.Encoder{CompressionLevel: png.BestSpeed}
// 场景 2:生产环境(平衡)
encoder2 := &png.Encoder{CompressionLevel: png.DefaultCompression}
// 场景 3:网络传输(大小优先)
encoder3 := &png.Encoder{CompressionLevel: png.BestCompression}
// 场景 4:存档保存(最佳压缩)
encoder4 := &png.Encoder{CompressionLevel: png.BestCompression}
2. 透明度处理
// PNG 支持完整的 Alpha 通道透明度
// 使用 image.NRGBA 或 image.RGBA
// 创建带透明度的图像
img := image.NewNRGBA(bounds)
// 设置透明像素
img.Set(x, y, color.NRGBA{R: 255, G: 0, B: 0, A: 0}) // 完全透明
img.Set(x, y, color.NRGBA{R: 255, G: 0, B: 0, A: 128}) // 半透明
img.Set(x, y, color.NRGBA{R: 255, G: 0, B: 0, A: 255}) // 不透明
// PNG 编码会自动保留透明度
png.Encode(file, img)
3. 内存优化
// 技巧 1:使用 DecodeConfig 先获取信息
config, err := png.DecodeConfig(reader)
if err != nil {
return err
}
// 检查尺寸是否合理
if config.Width > 10000 || config.Height > 10000 {
return errors.New("图像过大")
}
// 技巧 2:使用缓冲 I/O
file, _ := os.Open("large.png")
defer file.Close()
buffered := bufio.NewReader(file)
img, err := png.Decode(buffered)
4. 错误处理
func safeDecode(path string) (image.Image, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("打开文件失败:%w", err)
}
defer file.Close()
img, err := png.Decode(file)
if err != nil {
return nil, fmt.Errorf("解码 PNG 失败:%w", err)
}
return img, nil
}
5. 性能优化
// 技巧 1:批量处理时复用缓冲区
var buf bytes.Buffer
for _, img := range images {
buf.Reset()
png.Encode(&buf, img)
// 使用 buf.Bytes()
}
// 技巧 2:选择合适的压缩级别
// 对于实时处理,使用 BestSpeed
encoder := &png.Encoder{CompressionLevel: png.BestSpeed}
// 技巧 3:考虑使用 goroutine 并行处理
6. 颜色空间处理
// PNG 支持多种颜色类型:
// - Grayscale(灰度)
// - RGB(真彩色)
// - RGBA(带透明度)
// - Palette(调色板)
// 解码后自动转换为合适的类型
img, _ := png.Decode(file)
// 根据需要转换
switch v := img.(type) {
case *image.Gray:
// 灰度图像
case *image.RGBA:
// RGBA 图像
case *image.Paletted:
// 调色板图像
}
七、与其他包配合
1. 与 image/draw 配合
package main
import (
"image"
"image/draw"
"image/png"
"os"
)
func compositeAndSave(bgPath, fgPath, outputPath string) error {
// 加载背景(PNG)
bgFile, err := os.Open(bgPath)
if err != nil {
return err
}
defer bgFile.Close()
bg, err := png.Decode(bgFile)
if err != nil {
return err
}
// 加载前景(PNG,带透明)
fgFile, err := os.Open(fgPath)
if err != nil {
return err
}
defer fgFile.Close()
fg, err := png.Decode(fgFile)
if err != nil {
return err
}
// 创建画布
dst := image.NewRGBA(bg.Bounds())
draw.Draw(dst, dst.Bounds(), bg, bg.Bounds().Min, draw.Src)
draw.Draw(dst, fg.Bounds(), fg, fg.Bounds().Min, draw.Over)
// 保存为 PNG(保留透明度)
outFile, err := os.Create(outputPath)
if err != nil {
return err
}
defer outFile.Close()
return png.Encode(outFile, dst)
}
2. 与 image/color/palette 配合
package main
import (
"image"
"image/color/palette"
"image/draw"
"image/png"
"os"
)
func pngToPaletted(inputPath, outputPath string) error {
// 打开 PNG
file, err := os.Open(inputPath)
if err != nil {
return err
}
defer file.Close()
src, err := png.Decode(file)
if err != nil {
return err
}
// 转换为调色板图像(使用 Floyd-Steinberg 抖动)
dst := image.NewPaletted(src.Bounds(), palette.Plan9)
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.FloydSteinberg)
// 保存为 PNG
outFile, err := os.Create(outputPath)
if err != nil {
return err
}
defer outFile.Close()
return png.Encode(outFile, dst)
}
3. 与 bytes 包配合
package main
import (
"bytes"
"image/png"
)
// 编码到内存
func encodeToMemory(img image.Image) ([]byte, error) {
var buf bytes.Buffer
err := png.Encode(&buf, img)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// 从内存解码
func decodeFromMemory(data []byte) (image.Image, error) {
reader := bytes.NewReader(data)
return png.Decode(reader)
}
// 处理数据库中的图像
func saveImageToDB(img image.Image) error {
data, err := encodeToMemory(img)
if err != nil {
return err
}
// 保存到数据库(作为 []byte)
// db.Exec("INSERT INTO images (data) VALUES (?)", data)
return nil
}
func loadImageFromDB(id int) (image.Image, error) {
// 从数据库读取(作为 []byte)
// var data []byte
// db.QueryRow("SELECT data FROM images WHERE id = ?", id).Scan(&data)
return decodeFromMemory(data)
}
4. 与 net/http 配合
package main
import (
"image/png"
"net/http"
)
// 提供 PNG 图像
func imageHandler(w http.ResponseWriter, r *http.Request) {
img := generateImage() // 假设已定义
// 设置响应头
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "public, max-age=3600")
// 编码并发送
err := png.Encode(w, img)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
// 处理 PNG 上传
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 限制大小(10MB)
r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
// 解码上传的 PNG
img, err := png.Decode(r.Body)
if err != nil {
http.Error(w, "Invalid PNG", http.StatusBadRequest)
return
}
// 处理图像...
_ = img
w.WriteHeader(http.StatusOK)
w.Write([]byte("Upload successful"))
}
八、快速参考
函数总览
| 函数名 | 参数 | 返回值 | 描述 |
|---|---|---|---|
Decode | r io.Reader | (image.Image, error) | 解码 PNG 图像 |
DecodeConfig | r io.Reader | (image.Config, error) | 解码 PNG 配置信息 |
Encode | w io.Writer, img image.Image | error | 编码为 PNG |
结构体总览
| 结构体名 | 字段 | 描述 |
|---|---|---|
Decoder | (未导出字段) | PNG 解码器 |
Encoder | CompressionLevel | PNG 编码器 |
类型总览
| 类型名 | 底层类型 | 描述 |
|---|---|---|
CompressionLevel | int | 压缩级别类型 |
Reader | io.Reader | PNG 解码输入接口 |
Writer | io.Writer | PNG 编码输出接口 |
常量总览
| 常量名 | 值 | 描述 |
|---|---|---|
DefaultCompression | 0 | 默认压缩 |
NoCompression | -2 | 不压缩 |
BestSpeed | -1 | 最快速度 |
BestCompression | -3 | 最佳压缩 |
HuffmanOnly | -4 | 仅 Huffman 编码 |
压缩级别选择指南
| 场景 | 推荐级别 | 理由 |
|---|---|---|
| 开发/测试 | BestSpeed | 快速迭代 |
| 实时处理 | BestSpeed | 低延迟 |
| 网页图片 | DefaultCompression | 平衡 |
| 网络传输 | BestCompression | 节省带宽 |
| 存档保存 | BestCompression | 最小存储 |
| 调试分析 | NoCompression | 快速访问 |
PNG 特性对比
| 特性 | PNG | JPEG | GIF |
|---|---|---|---|
| 压缩类型 | 无损 | 有损 | 无损 |
| 透明度 | ✓ (Alpha) | ✗ | ✓ (1 位) |
| 动画 | ✓ (APNG) | ✗ | ✓ |
| 颜色深度 | 最高 48 位 | 24 位 | 8 位 |
| 适用场景 | 图标、截图 | 照片 | 简单动画 |
九、注意事项
1. 压缩级别选择
// 正确:根据场景选择
encoder1 := &png.Encoder{CompressionLevel: png.BestSpeed} // 快速
encoder2 := &png.Encoder{CompressionLevel: png.DefaultCompression} // 平衡
encoder3 := &png.Encoder{CompressionLevel: png.BestCompression} // 最小
// 错误:超出范围的值
encoder4 := &png.Encoder{CompressionLevel: 100} // ✗ 无效值
2. 透明度支持
// PNG 完整支持 Alpha 通道透明度
img := image.NewNRGBA(bounds)
img.Set(x, y, color.NRGBA{R: 255, G: 0, B: 0, A: 128}) // 50% 透明
// 编码时自动保留透明度
png.Encode(file, img)
// JPEG 不支持透明度,会混合到白色背景
3. 动画支持
// 标准库不支持 APNG(动画 PNG)
// 需要使用第三方库,如:
// - github.com/disintegration/imaging
// - github.com/qmuntal/apng
// 标准库只能处理静态 PNG
img, _ := png.Decode(file) // 只解码第一帧
4. 颜色类型
// PNG 支持多种颜色类型:
// - Grayscale(灰度):1、2、4、8、16 位
// - RGB(真彩色):8、16 位每通道
// - RGBA(带 Alpha):8、16 位每通道
// - Palette(调色板):1、2、4、8 位
// 解码后自动转换
img, _ := png.Decode(file)
// 根据原始 PNG 类型,img 可能是:
// - *image.Gray(灰度)
// - *image.RGBA(RGBA)
// - *image.Paletted(调色板)
5. 性能考虑
// 编码大图像时:
// 1. 选择合适的压缩级别
// 2. 考虑使用 BestSpeed 进行开发
// 3. 使用缓冲 I/O 提高性能
// 4. 考虑并行处理多个图像
// 解码大图像时:
// 1. 先用 DecodeConfig 检查尺寸
// 2. 考虑使用缩略图
// 3. 限制最大尺寸
6. 内存使用
// PNG 解码会分配完整图像数据到内存
// 对于超大图像,注意内存使用
// 建议:
// 1. 使用 DecodeConfig 先检查尺寸
// 2. 设置合理的尺寸限制
// 3. 考虑流式处理(如果可能)
// 4. 及时释放资源
十、完整示例:PNG 处理工具
package main
import (
"flag"
"fmt"
"image"
"image/draw"
"image/png"
"os"
"path/filepath"
)
// PNGTool PNG 处理工具
type PNGTool struct {
inputPath string
outputPath string
compressionLevel png.CompressionLevel
maxWidth int
maxHeight int
}
// NewPNGTool 创建工具实例
func NewPNGTool(input, output string, level png.CompressionLevel, maxW, maxH int) *PNGTool {
return &PNGTool{
inputPath: input,
outputPath: output,
compressionLevel: level,
maxWidth: maxW,
maxHeight: maxH,
}
}
// Compress 压缩 PNG
func (t *PNGTool) Compress() error {
file, err := os.Open(t.inputPath)
if err != nil {
return err
}
defer file.Close()
img, err := png.Decode(file)
if err != nil {
return err
}
// 调整尺寸
if t.maxWidth > 0 || t.maxHeight > 0 {
img = t.resize(img)
}
// 编码
outFile, err := os.Create(t.outputPath)
if err != nil {
return err
}
defer outFile.Close()
encoder := &png.Encoder{CompressionLevel: t.compressionLevel}
return encoder.Encode(outFile, img)
}
// Info 显示 PNG 信息
func (t *PNGTool) Info() error {
file, err := os.Open(t.inputPath)
if err != nil {
return err
}
defer file.Close()
config, err := png.DecodeConfig(file)
if err != nil {
return err
}
fmt.Printf("文件:%s\n", t.inputPath)
fmt.Printf("尺寸:%dx%d\n", config.Width, config.Height)
fmt.Printf("颜色模型:%v\n", config.ColorModel)
// 获取文件大小
info, err := os.Stat(t.inputPath)
if err == nil {
fmt.Printf("文件大小:%d 字节\n", info.Size())
}
return nil
}
// resize 调整图像尺寸
func (t *PNGTool) resize(img image.Image) image.Image {
bounds := img.Bounds()
width := bounds.Dx()
height := bounds.Dy()
// 计算新尺寸
newWidth := width
newHeight := height
if t.maxWidth > 0 && width > t.maxWidth {
newWidth = t.maxWidth
newHeight = height * t.maxWidth / width
}
if t.maxHeight > 0 && newHeight > t.maxHeight {
newHeight = t.maxHeight
newWidth = newWidth * t.maxHeight / newHeight
}
// 创建目标图像
dst := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
// 缩放
draw.Draw(dst, dst.Bounds(), img, bounds, draw.Src)
return dst
}
// BatchCompress 批量压缩
func (t *PNGTool) BatchCompress(dir string) error {
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if filepath.Ext(path) == ".png" {
// 创建输出路径
relPath, _ := filepath.Rel(dir, path)
outputPath := filepath.Join(t.outputPath, relPath)
// 创建目录
os.MkdirAll(filepath.Dir(outputPath), 0755)
// 压缩
tool := NewPNGTool(path, outputPath, t.compressionLevel, t.maxWidth, t.maxHeight)
err := tool.Compress()
if err != nil {
fmt.Printf("压缩失败 %s: %v\n", path, err)
} else {
fmt.Printf("已压缩:%s\n", path)
}
}
return nil
})
}
// ConvertToPNG 转换其他格式为 PNG
func (t *PNGTool) ConvertToPNG() error {
// 打开源文件
file, err := os.Open(t.inputPath)
if err != nil {
return err
}
defer file.Close()
// 解码(自动识别格式)
img, _, err := image.Decode(file)
if err != nil {
return err
}
// 编码 PNG
outFile, err := os.Create(t.outputPath)
if err != nil {
return err
}
defer outFile.Close()
encoder := &png.Encoder{CompressionLevel: t.compressionLevel}
return encoder.Encode(outFile, img)
}
func main() {
// 命令行参数
inputPath := flag.String("i", "", "输入文件路径")
outputPath := flag.String("o", "", "输出文件路径")
level := flag.Int("l", 0, "压缩级别:-3=最佳压缩,-1=最快速度,0=默认")
maxWidth := flag.Int("max-w", 0, "最大宽度")
maxHeight := flag.Int("max-h", 0, "最大高度")
action := flag.String("action", "compress", "操作:compress, info, batch, convert")
batchDir := flag.String("dir", "", "批量处理目录")
flag.Parse()
tool := NewPNGTool(*inputPath, *outputPath, png.CompressionLevel(*level), *maxWidth, *maxHeight)
var err error
switch *action {
case "compress":
err = tool.Compress()
case "info":
err = tool.Info()
case "batch":
if *batchDir == "" {
fmt.Println("错误:需要指定 -dir 参数")
os.Exit(1)
}
err = tool.BatchCompress(*batchDir)
case "convert":
err = tool.ConvertToPNG()
}
if err != nil {
fmt.Fprintf(os.Stderr, "错误:%v\n", err)
os.Exit(1)
}
fmt.Println("完成!")
}
最后更新: 2026-04-04
Go 版本: 1.21+
包文档: https://pkg.go.dev/image/png