Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Go image/jpeg 包详解

概述

image/jpeg 包提供 JPEG 图像的编码和解码功能。JPEG 是一种广泛使用的有损压缩图像格式,特别适合照片和连续色调图像。该包提供了 EncodeDecode 等核心函数,以及 ReaderWriter 类型和 Options 结构体,支持质量设置和渐进式编码。

包导入

import "image/jpeg"

基本使用

1. 编码 JPEG 图像

package main

import (
    "image"
    "image/color"
    "image/jpeg"
    "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: 255,
            })
        }
    }
    
    // 编码为 JPEG
    file, _ := os.Create("output.jpg")
    defer file.Close()
    jpeg.Encode(file, img, &jpeg.Options{Quality: 90})
}

2. 解码 JPEG 图像

package main

import (
    "fmt"
    "image/jpeg"
    "os"
)

func main() {
    // 打开 JPEG 文件
    file, _ := os.Open("input.jpg")
    defer file.Close()
    
    // 解码 JPEG
    img, err := jpeg.Decode(file)
    if err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    // 获取图像信息
    bounds := img.Bounds()
    fmt.Printf("图像尺寸:%dx%d\n", bounds.Dx(), bounds.Dy())
}

3. 调整 JPEG 质量

package main

import (
    "image/jpeg"
    "image/png"
    "os"
)

func main() {
    // 打开 PNG
    file, _ := os.Open("input.png")
    defer file.Close()
    img, _ := png.Decode(file)
    
    // 以不同质量保存为 JPEG
    for quality := 50; quality <= 100; quality += 10 {
        outFile, _ := os.Create(fmt.Sprintf("output_q%d.jpg", quality))
        defer outFile.Close()
        
        jpeg.Encode(outFile, img, &jpeg.Options{Quality: quality})
    }
}

一、核心函数

Decode

定义:

func Decode(r io.Reader) (image.Image, error)

说明:

  • 功能:解码 JPEG 图像
  • 参数r - io.Reader(如文件、字节流)
  • 返回值
    • image.Image - 解码后的图像(通常是 *image.YCbCr
    • error - 错误信息
  • 返回类型:通常返回 *image.YCbCr 类型,因为 JPEG 使用 YCbCr 颜色空间

示例:

package main

import (
    "fmt"
    "image"
    "image/jpeg"
    "image/png"
    "os"
)

func main() {
    // 打开 JPEG
    file, err := os.Open("input.jpg")
    if err != nil {
        fmt.Println("打开失败:", err)
        return
    }
    defer file.Close()
    
    // 解码
    img, err := jpeg.Decode(file)
    if err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    // 检查图像类型
    switch v := img.(type) {
    case *image.YCbCr:
        fmt.Println("YCbCr 图像")
    case *image.RGBA:
        fmt.Println("RGBA 图像")
    }
    
    // 保存为 PNG
    outFile, _ := os.Create("output.png")
    defer outFile.Close()
    png.Encode(outFile, img)
}

DecodeConfig

定义:

func DecodeConfig(r io.Reader) (image.Config, error)

说明:

  • 功能:解码 JPEG 配置信息(不解码图像数据)
  • 参数r - io.Reader
  • 返回值
    • image.Config - 图像配置(尺寸、颜色模型)
    • error - 错误信息
  • 用途:快速获取图像尺寸等信息,无需加载完整图像
  • 性能:比 Decode 快得多,因为只读取头部信息

示例:

package main

import (
    "fmt"
    "image/jpeg"
    "os"
)

func main() {
    // 快速检查 JPEG 信息
    file, _ := os.Open("photo.jpg")
    defer file.Close()
    
    config, err := jpeg.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, o *Options) error

说明:

  • 功能:将图像编码为 JPEG 格式
  • 参数
    • w - io.Writer(如文件、字节流)
    • img - 要编码的图像
    • o - 编码选项(可为 nil,使用默认质量 75)
  • 返回值error - 错误信息
  • 自动转换:如果图像不是 YCbCr 格式,会自动转换

示例:

package main

import (
    "image"
    "image/color"
    "image/jpeg"
    "os"
)

func main() {
    // 创建 RGBA 图像
    img := image.NewRGBA(image.Rect(0, 0, 1920, 1080))
    
    // 填充内容
    for y := 0; y < 1080; y++ {
        for x := 0; x < 1920; x++ {
            img.Set(x, y, color.RGBA{
                R: uint8(x % 256),
                G: uint8(y % 256),
                B: 128,
                A: 255,
            })
        }
    }
    
    // 编码为 JPEG(使用默认质量)
    file, _ := os.Create("photo.jpg")
    defer file.Close()
    
    err := jpeg.Encode(file, img, nil)
    if err != nil {
        panic(err)
    }
}

二、结构体

Options

定义:

type Options struct {
    Quality int // 图像质量(1-100)
}

字段说明:

字段类型范围默认值描述
Qualityint1-10075JPEG 压缩质量

质量级别建议:

质量值文件大小图像质量使用场景
90-100非常高高质量照片、印刷
80-89中等网页展示、一般用途
70-79较小良好网络传输、缩略图
50-69一般快速加载、预览
1-49很小较差极端压缩需求

示例 - 不同质量对比:

package main

import (
    "fmt"
    "image/jpeg"
    "os"
)

func encodeWithQuality(img image.Image, quality int, filename string) {
    file, _ := os.Create(filename)
    defer file.Close()
    
    opts := &jpeg.Options{Quality: quality}
    err := jpeg.Encode(file, img, opts)
    if err != nil {
        panic(err)
    }
    
    // 获取文件大小
    info, _ := os.Stat(filename)
    fmt.Printf("质量 %3d: %s (%d 字节)\n", quality, filename, info.Size())
}

func main() {
    img := loadYourImage() // 假设已定义
    
    // 测试不同质量
    encodeWithQuality(img, 50, "q50.jpg")
    encodeWithQuality(img, 75, "q75.jpg")
    encodeWithQuality(img, 90, "q90.jpg")
    encodeWithQuality(img, 95, "q95.jpg")
    encodeWithQuality(img, 100, "q100.jpg")
}

三、类型

Reader

定义:

type Reader interface {
    io.Reader
}

说明:

  • 功能:JPEG 解码器的输入接口
  • 嵌入io.Reader 接口
  • 实现:任何实现 io.Reader 的类型都可以使用
  • 常见类型
    • *os.File - 文件
    • *bytes.Reader - 字节切片
    • *strings.Reader - 字符串
    • http.Request.Body - HTTP 请求体

示例:

package main

import (
    "bytes"
    "fmt"
    "image/jpeg"
)

func decodeFromBytes(data []byte) error {
    // 从字节切片解码
    reader := bytes.NewReader(data)
    img, err := jpeg.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
}

说明:

  • 功能:JPEG 编码器的输出接口
  • 嵌入io.Writer 接口
  • 实现:任何实现 io.Writer 的类型都可以使用
  • 常见类型
    • *os.File - 文件
    • *bytes.Buffer - 字节缓冲区
    • http.ResponseWriter - HTTP 响应
    • *bufio.Writer - 缓冲写入器

示例:

package main

import (
    "bytes"
    "image/jpeg"
)

func encodeToBytes(img image.Image) ([]byte, error) {
    // 编码到字节缓冲区
    var buf bytes.Buffer
    err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 90})
    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/jpeg")
    jpeg.Encode(w, img, &jpeg.Options{Quality: 85})
}

四、典型示例

示例 1:PNG 转 JPEG

package main

import (
    "fmt"
    "image/jpeg"
    "image/png"
    "os"
)

func pngToJPEG(inputPath, outputPath string, quality int) error {
    // 打开 PNG
    file, err := os.Open(inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 解码 PNG
    img, err := png.Decode(file)
    if err != nil {
        return err
    }
    
    // 编码 JPEG
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    err = jpeg.Encode(outFile, img, &jpeg.Options{Quality: quality})
    if err != nil {
        return err
    }
    
    fmt.Printf("已转换:%s -> %s (质量:%d)\n", inputPath, outputPath, quality)
    return nil
}

示例 2:批量压缩 JPEG

package main

import (
    "fmt"
    "image/jpeg"
    "os"
    "path/filepath"
)

func compressJPEG(inputPath, outputPath string, quality int) error {
    // 打开源文件
    file, err := os.Open(inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 解码
    img, err := jpeg.Decode(file)
    if err != nil {
        return err
    }
    
    // 编码(新质量)
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return jpeg.Encode(outFile, img, &jpeg.Options{Quality: quality})
}

func batchCompress(dir string, quality int) error {
    // 遍历目录
    return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        
        // 检查是否为 JPEG 文件
        if filepath.Ext(path) == ".jpg" || filepath.Ext(path) == ".jpeg" {
            outputPath := path // 覆盖原文件,或创建新路径
            
            err := compressJPEG(path, outputPath, quality)
            if err != nil {
                fmt.Printf("压缩失败 %s: %v\n", path, err)
            } else {
                fmt.Printf("已压缩:%s\n", path)
            }
        }
        
        return nil
    })
}

示例 3:调整 JPEG 尺寸

package main

import (
    "image"
    "image/draw"
    "image/jpeg"
    "os"
)

func resizeJPEG(inputPath, outputPath string, newWidth, newHeight int) error {
    // 打开源文件
    file, err := os.Open(inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 解码
    src, err := jpeg.Decode(file)
    if err != nil {
        return err
    }
    
    // 创建目标图像
    dst := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
    
    // 缩放(简单缩放,实际应使用更好的算法)
    draw.Draw(dst, dst.Bounds(), src, src.Bounds(), draw.Src)
    
    // 编码
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return jpeg.Encode(outFile, dst, &jpeg.Options{Quality: 90})
}

示例 4:添加 EXIF 信息(使用第三方库)

package main

import (
    "bytes"
    "image/jpeg"
    "os"
    "time"
    
    "github.com/rwcarlsen/goexif/exif"
)

func addEXIF(inputPath, outputPath string) error {
    // 打开源文件
    file, err := os.Open(inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 解码图像
    img, err := jpeg.Decode(file)
    if err != nil {
        return err
    }
    
    // 编码到内存
    var buf bytes.Buffer
    err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: 90})
    if err != nil {
        return err
    }
    
    // 注意:标准库不支持 EXIF,需要使用第三方库
    // 这里仅做示例
    
    // 创建输出文件
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    _, err = outFile.Write(buf.Bytes())
    return err
}

示例 5:从 URL 加载并保存 JPEG

package main

import (
    "fmt"
    "image/jpeg"
    "net/http"
    "os"
)

func downloadJPEG(url, outputPath string) error {
    // HTTP 请求
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("HTTP 状态:%d", resp.StatusCode)
    }
    
    // 检查 Content-Type
    contentType := resp.Header.Get("Content-Type")
    if contentType != "image/jpeg" {
        return fmt.Errorf("不是 JPEG 图像:%s", contentType)
    }
    
    // 创建文件
    file, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 解码并重新编码(验证图像)
    img, err := jpeg.Decode(resp.Body)
    if err != nil {
        return err
    }
    
    return jpeg.Encode(file, img, &jpeg.Options{Quality: 90})
}

示例 6:JPEG 质量比较工具

package main

import (
    "bytes"
    "fmt"
    "image/jpeg"
    "image/png"
    "os"
)

type QualityStats struct {
    Quality    int
    FileSize   int64
    SSIM       float64 // 结构相似性(需要实现)
}

func compareQuality(pngPath string) ([]QualityStats, error) {
    // 打开 PNG(作为参考)
    pngFile, err := os.Open(pngPath)
    if err != nil {
        return nil, err
    }
    defer pngFile.Close()
    
    refImg, err := png.Decode(pngFile)
    if err != nil {
        return nil, err
    }
    
    var stats []QualityStats
    qualities := []int{10, 25, 50, 75, 80, 85, 90, 95, 100}
    
    for _, q := range qualities {
        // 编码为 JPEG
        var buf bytes.Buffer
        err := jpeg.Encode(&buf, refImg, &jpeg.Options{Quality: q})
        if err != nil {
            return nil, err
        }
        
        // 获取文件大小
        fileSize := int64(buf.Len())
        
        // 这里可以计算 SSIM 或其他质量指标
        ssim := 0.0 // 需要实现
        
        stats = append(stats, QualityStats{
            Quality:  q,
            FileSize: fileSize,
            SSIM:     ssim,
        })
        
        fmt.Printf("质量 %3d: %8d 字节\n", q, fileSize)
    }
    
    return stats, nil
}

示例 7:渐进式 JPEG 编码(需要第三方库)

package main

import (
    "image/jpeg"
    "os"
    
    "github.com/chai2010/jpeg"
)

func createProgressiveJPEG(img image.Image, outputPath string) error {
    file, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 使用支持渐进式的库
    opts := &jpeg.Options{
        Quality:      90,
        Progressive: true, // 渐进式编码
    }
    
    return jpeg.Encode(file, img, opts)
}

五、最佳实践

1. 选择合适的质量

// 网页图片:质量 80-85,平衡质量和大小
opts1 := &jpeg.Options{Quality: 85}

// 高质量照片:质量 90-95
opts2 := &jpeg.Options{Quality: 92}

// 缩略图:质量 70-75
opts3 := &jpeg.Options{Quality: 75}

// 极端压缩:质量 50-60
opts4 := &jpeg.Options{Quality: 55}

2. 内存优化

// 使用 DecodeConfig 先获取信息
config, err := jpeg.DecodeConfig(reader)
if err != nil {
    return err
}

// 检查尺寸是否合理
if config.Width > 10000 || config.Height > 10000 {
    return errors.New("图像过大")
}

// 然后再解码
img, err := jpeg.Decode(reader)

3. 错误处理

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 := jpeg.Decode(file)
    if err != nil {
        return nil, fmt.Errorf("解码 JPEG 失败:%w", err)
    }
    
    return img, nil
}

4. 性能优化

// 技巧 1:使用缓冲 I/O
file, _ := os.Open("large.jpg")
defer file.Close()

buffered := bufio.NewReader(file)
img, err := jpeg.Decode(buffered)

// 技巧 2:批量处理时复用缓冲区
var buf bytes.Buffer
for _, img := range images {
    buf.Reset()
    jpeg.Encode(&buf, img, opts)
    // 使用 buf.Bytes()
}

5. 颜色空间处理

// JPEG 使用 YCbCr 颜色空间
// 解码后通常是 *image.YCbCr

img, _ := jpeg.Decode(file)

// 如果需要 RGBA,可以转换
switch v := img.(type) {
case *image.YCbCr:
    // 转换为 RGBA
    rgba := image.NewRGBA(v.Bounds())
    draw.Draw(rgba, rgba.Bounds(), v, v.Bounds().Min, draw.Src)
    img = rgba
case *image.RGBA:
    // 已经是 RGBA
}

六、与其他包配合

1. 与 image/draw 配合

package main

import (
    "image"
    "image/draw"
    "image/jpeg"
    "image/png"
    "os"
)

func compositeAndSave(bgPath, fgPath, outputPath string) error {
    // 加载背景(JPEG)
    bgFile, err := os.Open(bgPath)
    if err != nil {
        return err
    }
    defer bgFile.Close()
    
    bg, err := jpeg.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)
    
    // 保存为 JPEG
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return jpeg.Encode(outFile, dst, &jpeg.Options{Quality: 90})
}

2. 与 image/color/palette 配合

package main

import (
    "image"
    "image/color/palette"
    "image/draw"
    "image/jpeg"
    "os"
)

func jpegToPaletted(inputPath, outputPath string) error {
    // 打开 JPEG
    file, err := os.Open(inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    src, err := jpeg.Decode(file)
    if err != nil {
        return err
    }
    
    // 转换为调色板图像
    dst := image.NewPaletted(src.Bounds(), palette.Plan9)
    draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.FloydSteinberg)
    
    // 注意:不能直接将调色板图像编码为 JPEG
    // 需要转换回 RGBA
    rgba := image.NewRGBA(dst.Bounds())
    draw.Draw(rgba, rgba.Bounds(), dst, dst.Bounds().Min, draw.Src)
    
    // 编码为 JPEG
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return jpeg.Encode(outFile, rgba, &jpeg.Options{Quality: 90})
}

3. 与 bytes 包配合

package main

import (
    "bytes"
    "image/jpeg"
)

// 编码到内存
func encodeToMemory(img image.Image, quality int) ([]byte, error) {
    var buf bytes.Buffer
    err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
    if err != nil {
        return nil, err
    }
    return buf.Bytes(), nil
}

// 从内存解码
func decodeFromMemory(data []byte) (image.Image, error) {
    reader := bytes.NewReader(data)
    return jpeg.Decode(reader)
}

// 处理 HTTP 上传
func handleUpload(r *http.Request) error {
    // 读取上传的数据
    data, err := io.ReadAll(r.Body)
    if err != nil {
        return err
    }
    
    // 从内存解码
    img, err := decodeFromMemory(data)
    if err != nil {
        return err
    }
    
    // 处理图像...
    return nil
}

4. 与 net/http 配合

package main

import (
    "image/jpeg"
    "net/http"
)

// 提供 JPEG 图像
func imageHandler(w http.ResponseWriter, r *http.Request) {
    img := generateImage() // 假设已定义
    
    // 设置响应头
    w.Header().Set("Content-Type", "image/jpeg")
    w.Header().Set("Cache-Control", "public, max-age=3600")
    
    // 编码并发送
    err := jpeg.Encode(w, img, &jpeg.Options{Quality: 85})
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

// 处理 JPEG 上传
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)
    
    // 解码上传的 JPEG
    img, err := jpeg.Decode(r.Body)
    if err != nil {
        http.Error(w, "Invalid JPEG", http.StatusBadRequest)
        return
    }
    
    // 处理图像...
    _ = img
    
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Upload successful"))
}

七、快速参考

函数总览

函数名参数返回值描述
Decoder io.Reader(image.Image, error)解码 JPEG 图像
DecodeConfigr io.Reader(image.Config, error)解码 JPEG 配置信息
Encodew io.Writer, img image.Image, o *Optionserror编码为 JPEG

结构体总览

结构体名字段描述
OptionsQuality intJPEG 编码选项

类型总览

类型名底层类型描述
Readerio.ReaderJPEG 解码输入接口
Writerio.WriterJPEG 编码输出接口

Options.Quality 参考

质量值压缩比图像质量适用场景
100最低无损档案保存
95-99很低极高高质量照片
90-94很高专业摄影
80-89中等网页展示
70-79较高良好网络传输
50-69一般快速加载
1-49很高较差极端压缩

常见错误

错误信息原因解决方案
“invalid JPEG format”不是有效的 JPEG 文件检查文件格式
“unsupported JPEG process”不支持的 JPEG 类型使用标准 JPEG
“missing SOS marker”JPEG 数据损坏重新获取文件
“image is too large”图像尺寸过大缩小图像

八、注意事项

1. 质量值范围

// 正确:1-100
opts1 := &jpeg.Options{Quality: 75}  // ✓
opts2 := &jpeg.Options{Quality: 100} // ✓

// 错误:超出范围
opts3 := &jpeg.Options{Quality: 150} // ✗ 会被截断为 100
opts4 := &jpeg.Options{Quality: 0}   // ✗ 使用默认值 75

2. 颜色空间转换

// JPEG 内部使用 YCbCr
// 解码后通常是 *image.YCbCr

img, _ := jpeg.Decode(file)

// 如果需要访问像素,注意类型
if ycbcr, ok := img.(*image.YCbCr); ok {
    // 直接访问 YCbCr 数据
    y := ycbcr.Y[0]
}

// 或转换为 RGBA
rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Src)

3. 透明度不支持

// JPEG 不支持透明度(Alpha 通道)
// 带透明的图像会被混合到白色背景

img := image.NewRGBA(image.Rect(0, 0, 100, 100))
// 设置透明像素
img.Set(50, 50, color.RGBA{255, 0, 0, 128}) // 半透明

// 编码为 JPEG 后,透明度会丢失
jpeg.Encode(file, img, nil) // ✗ 透明度丢失

4. 渐进式 JPEG

// 标准库不支持渐进式 JPEG
// 需要使用第三方库,如 github.com/chai2010/jpeg

// 标准库编码的是基线 JPEG
opts := &jpeg.Options{Quality: 90}
jpeg.Encode(file, img, opts) // 基线编码

5. EXIF 信息

// 标准库不读取/写入 EXIF 信息
// EXIF 数据会丢失

// 需要保留 EXIF,使用第三方库
// 如:github.com/rwcarlsen/goexif

// 或者手动复制原始数据

6. 性能考虑

// 编码大图像时:
// 1. 使用适当的质量(不要总是 100)
// 2. 考虑先缩小图像
// 3. 使用缓冲 I/O
// 4. 注意内存使用

// 解码大图像时:
// 1. 先用 DecodeConfig 检查尺寸
// 2. 考虑使用缩略图
// 3. 限制最大尺寸

九、完整示例:JPEG 处理工具

package main

import (
    "flag"
    "fmt"
    "image"
    "image/draw"
    "image/jpeg"
    "os"
    "path/filepath"
)

// JPEGTool JPEG 处理工具
type JPEGTool struct {
    inputPath  string
    outputPath string
    quality    int
    maxWidth   int
    maxHeight  int
}

// NewJPEGTool 创建工具实例
func NewJPEGTool(input, output string, quality, maxW, maxH int) *JPEGTool {
    return &JPEGTool{
        inputPath:  input,
        outputPath: output,
        quality:    quality,
        maxWidth:   maxW,
        maxHeight:  maxH,
    }
}

// Compress 压缩 JPEG
func (t *JPEGTool) Compress() error {
    file, err := os.Open(t.inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    img, err := jpeg.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()
    
    return jpeg.Encode(outFile, img, &jpeg.Options{Quality: t.quality})
}

// Info 显示 JPEG 信息
func (t *JPEGTool) Info() error {
    file, err := os.Open(t.inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    config, err := jpeg.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 *JPEGTool) 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 *JPEGTool) BatchCompress(dir string) error {
    return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return err
        }
        
        ext := filepath.Ext(path)
        if ext == ".jpg" || ext == ".jpeg" {
            // 创建输出路径
            relPath, _ := filepath.Rel(dir, path)
            outputPath := filepath.Join(t.outputPath, relPath)
            
            // 创建目录
            os.MkdirAll(filepath.Dir(outputPath), 0755)
            
            // 压缩
            tool := NewJPEGTool(path, outputPath, t.quality, 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
    })
}

func main() {
    // 命令行参数
    inputPath := flag.String("i", "", "输入文件路径")
    outputPath := flag.String("o", "", "输出文件路径")
    quality := flag.Int("q", 85, "JPEG 质量 (1-100)")
    maxWidth := flag.Int("max-w", 0, "最大宽度")
    maxHeight := flag.Int("max-h", 0, "最大高度")
    action := flag.String("action", "compress", "操作:compress, info, batch")
    batchDir := flag.String("dir", "", "批量处理目录")
    
    flag.Parse()
    
    tool := NewJPEGTool(*inputPath, *outputPath, *quality, *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)
    }
    
    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/jpeg