Go image/jpeg 包详解
概述
image/jpeg 包提供 JPEG 图像的编码和解码功能。JPEG 是一种广泛使用的有损压缩图像格式,特别适合照片和连续色调图像。该包提供了 Encode、Decode 等核心函数,以及 Reader、Writer 类型和 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)
}
字段说明:
| 字段 | 类型 | 范围 | 默认值 | 描述 |
|---|---|---|---|---|
Quality | int | 1-100 | 75 | JPEG 压缩质量 |
质量级别建议:
| 质量值 | 文件大小 | 图像质量 | 使用场景 |
|---|---|---|---|
| 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"))
}
七、快速参考
函数总览
| 函数名 | 参数 | 返回值 | 描述 |
|---|---|---|---|
Decode | r io.Reader | (image.Image, error) | 解码 JPEG 图像 |
DecodeConfig | r io.Reader | (image.Config, error) | 解码 JPEG 配置信息 |
Encode | w io.Writer, img image.Image, o *Options | error | 编码为 JPEG |
结构体总览
| 结构体名 | 字段 | 描述 |
|---|---|---|
Options | Quality int | JPEG 编码选项 |
类型总览
| 类型名 | 底层类型 | 描述 |
|---|---|---|
Reader | io.Reader | JPEG 解码输入接口 |
Writer | io.Writer | JPEG 编码输出接口 |
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