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/gif 包详解

概述

image/gif 包提供 GIF 图像的编码和解码功能。它支持静态 GIF 图像和 animated GIF(多帧动画)的处理。该包提供了 EncodeDecode 等核心函数,以及 GIFOptions 等结构体,广泛用于创建和读取 GIF 图像及动画。

包导入

import "image/gif"

基本使用

1. 编码静态 GIF 图像

package main

import (
    "image"
    "image/color"
    "image/gif"
    "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{255, 0, 0, 255})
        }
    }
    
    // 编码为 GIF
    file, _ := os.Create("output.gif")
    defer file.Close()
    gif.Encode(file, img, nil)
}

2. 解码 GIF 图像

package main

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

func main() {
    // 打开 GIF 文件
    file, _ := os.Open("input.gif")
    defer file.Close()
    
    // 解码 GIF
    g, _ := gif.DecodeAll(file)
    
    // 打印帧数
    fmt.Printf("GIF 帧数:%d\n", len(g.Image))
    fmt.Printf("第一帧尺寸:%dx%d\n", g.Image[0].Bounds().Dx(), g.Image[0].Bounds().Dy())
}

3. 创建 GIF 动画

package main

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

func main() {
    // 创建多帧动画
    var frames []*image.Paletted
    var delays []int
    
    // 生成 10 帧
    for i := 0; i < 10; i++ {
        img := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.WebSafe)
        // 填充不同颜色
        c := color.RGBA{uint8(i * 25), 0, 0, 255}
        for y := 0; y < 100; y++ {
            for x := 0; x < 100; x++ {
                img.Set(x, y, c)
            }
        }
        frames = append(frames, img)
        delays = append(delays, 10) // 每帧 10ms
    }
    
    // 编码动画
    file, _ := os.Create("animation.gif")
    defer file.Close()
    
    gif.EncodeAll(file, &gif.GIF{
        Image: frames,
        Delay: delays,
    })
}

一、核心函数

Decode

定义:

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

说明:

  • 功能:解码 GIF 图像,返回第一帧
  • 参数r - io.Reader(如文件、字节流)
  • 返回值
    • image.Image - 解码后的图像(第一帧)
    • error - 错误信息
  • 注意:只返回 GIF 的第一帧,不返回动画信息

示例:

package main

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

func main() {
    file, _ := os.Open("input.gif")
    defer file.Close()
    
    // 解码第一帧
    img, err := gif.Decode(file)
    if err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    // 获取图像尺寸
    bounds := img.Bounds()
    fmt.Printf("图像尺寸:%dx%d\n", bounds.Dx(), bounds.Dy())
}

DecodeAll

定义:

func DecodeAll(r io.Reader) (*GIF, error)

说明:

  • 功能:解码完整的 GIF 图像(包括所有帧和动画信息)
  • 参数r - io.Reader
  • 返回值
    • *GIF - 包含所有帧和动画信息的结构体指针
    • error - 错误信息
  • 用途:读取 GIF 动画、获取所有帧、延迟信息等

示例:

package main

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

func main() {
    file, _ := os.Open("animation.gif")
    defer file.Close()
    
    // 解码所有帧
    g, err := gif.DecodeAll(file)
    if err != nil {
        fmt.Println("解码失败:", err)
        return
    }
    
    // 打印动画信息
    fmt.Printf("总帧数:%d\n", len(g.Image))
    fmt.Printf("循环次数:%d (0=无限循环)\n", g.LoopCount)
    fmt.Printf("背景色索引:%d\n", g.BackgroundIndex)
    
    // 打印每帧信息
    for i, frame := range g.Image {
        fmt.Printf("帧 %d: 尺寸=%dx%d, 延迟=%dms\n", 
            i, 
            frame.Bounds().Dx(), 
            frame.Bounds().Dy(),
            g.Delay[i]*10) // 延迟单位是 1/100 秒
    }
}

Encode

定义:

func Encode(w io.Writer, img image.Image, o *Options) error

说明:

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

示例:

package main

import (
    "image"
    "image/color"
    "image/gif"
    "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,
            })
        }
    }
    
    // 编码为 GIF(使用默认选项)
    file, _ := os.Create("gradient.gif")
    defer file.Close()
    err := gif.Encode(file, img, nil)
    if err != nil {
        panic(err)
    }
}

EncodeAll

定义:

func EncodeAll(w io.Writer, g *GIF) error

说明:

  • 功能:编码完整的 GIF 动画(多帧)
  • 参数
    • w - io.Writer
    • g - 包含所有帧和动画信息的 *GIF 结构体
  • 返回值error - 错误信息
  • 用途:创建 GIF 动画

示例:

package main

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

func main() {
    // 创建动画帧
    var frames []*image.Paletted
    var delays []int
    
    // 加载源图像
    src := loadYourImage() // 假设已定义
    
    // 创建 5 帧旋转动画
    for i := 0; i < 5; i++ {
        frame := image.NewPaletted(src.Bounds(), palette.Plan9)
        // 这里应该进行图像旋转,简化示例
        draw.Draw(frame, frame.Bounds(), src, image.Point{}, draw.Src)
        frames = append(frames, frame)
        delays = append(delays, 20) // 20ms = 0.2 秒
    }
    
    // 编码动画
    file, _ := os.Create("rotation.gif")
    defer file.Close()
    
    err := gif.EncodeAll(file, &gif.GIF{
        Image: frames,
        Delay: delays,
    })
    if err != nil {
        panic(err)
    }
}

二、结构体

GIF

定义:

type GIF struct {
    Image           []*image.Paletted // 图像帧数组
    Delay           []int             // 每帧延迟(单位:1/100 秒)
    Disposal        []byte            // 每帧处理方式
    BackgroundIndex byte              // 背景色索引
    LoopCount       int               // 循环次数(0=无限循环)
    Config          image.Config      // 图像配置
}

字段说明:

字段类型描述示例值
Image[]*image.Paletted所有帧的图像数据3 帧动画 = 3 个元素
Delay[]int每帧延迟时间(1/100 秒)10 = 0.1 秒
Disposal[]byte每帧处理方式0=不处理,1=保留,2=恢复背景色
BackgroundIndexbyte背景色在调色板中的索引0
LoopCountint循环次数0=无限循环,1=播放 1 次
Configimage.Config图像配置(尺寸等)-

Disposal 取值说明:

名称说明
0DisposalNone不处理,新帧叠加在旧帧上
1DisposalBackground用背景色填充帧区域
2DisposalPrevious恢复到上一帧状态

示例 - 创建完整 GIF 动画:

package main

import (
    "image"
    "image/color"
    "image/color/palette"
    "image/gif"
    "os"
)

func main() {
    // 创建 3 帧动画
    frames := make([]*image.Paletted, 3)
    delays := make([]int, 3)
    disposal := make([]byte, 3)
    
    for i := 0; i < 3; i++ {
        // 创建帧
        img := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.WebSafe)
        
        // 绘制不同位置的红点
        cx := 50 + int(float64(i-1)*30)
        for y := 45; y <= 55; y++ {
            for x := cx - 5; x <= cx + 5; x++ {
                img.SetColorIndex(x, y, 1) // 红色索引
            }
        }
        
        frames[i] = img
        delays[i] = 50 // 0.5 秒
        disposal[i] = 1 // 每帧后恢复背景
    }
    
    // 创建 GIF
    g := &gif.GIF{
        Image:           frames,
        Delay:           delays,
        Disposal:        disposal,
        BackgroundIndex: 0,
        LoopCount:       0, // 无限循环
    }
    
    // 编码
    file, _ := os.Create("bouncing_ball.gif")
    defer file.Close()
    gif.EncodeAll(file, g)
}

Options

定义:

type Options struct {
    NumColors int     // 调色板中的颜色数量(最多 256)
    Quantizer color.Quantizer // 颜色量化器
    Drawer    color.Drawer    // 颜色绘制器
}

字段说明:

字段类型默认值描述
NumColorsint256调色板颜色数量(1-256)
Quantizercolor.Quantizernil颜色量化器(nil 使用中位切割)
Drawercolor.Drawernil颜色绘制器(nil 使用默认)

使用示例:

package main

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

func main() {
    // 加载真彩色图像
    src := loadYourImage()
    
    // 创建选项
    opts := &gif.Options{
        NumColors: 64, // 只使用 64 色
        Quantizer: nil, // 使用默认量化器
        Drawer:    draw.FloydSteinberg, // 使用抖动
    }
    
    // 编码
    file, _ := os.Create("optimized.gif")
    defer file.Close()
    gif.Encode(file, src, opts)
}

三、常量

Disposal 常量

定义:

const (
    DisposalNone       = 0x00 // 不处理
    DisposalBackground = 0x01 // 恢复背景色
    DisposalPrevious   = 0x02 // 恢复上一帧
)

使用示例:

package main

import (
    "image"
    "image/gif"
    "os"
)

func main() {
    // 创建两帧
    frame1 := createFrame1()
    frame2 := createFrame2()
    
    // 设置不同的 Disposal 方式
    g := &gif.GIF{
        Image:    []*image.Paletted{frame1, frame2},
        Delay:    []int{50, 50},
        Disposal: []byte{gif.DisposalNone, gif.DisposalBackground},
        LoopCount: 0,
    }
    
    file, _ := os.Create("animation.gif")
    defer file.Close()
    gif.EncodeAll(file, g)
}

四、典型示例

示例 1:将 PNG 转换为 GIF

package main

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

func pngToGIF(inputPath, outputPath string) 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
    }
    
    // 编码为 GIF
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return gif.Encode(outFile, img, &gif.Options{
        NumColors: 256,
    })
}

示例 2:创建简单的加载动画

package main

import (
    "image"
    "image/color"
    "image/color/palette"
    "image/gif"
    "os"
)

func createLoadingAnimation() {
    frames := make([]*image.Paletted, 8)
    delays := make([]int, 8)
    
    // 创建 8 帧旋转动画
    for i := 0; i < 8; i++ {
        img := image.NewPaletted(image.Rect(0, 0, 64, 64), palette.WebSafe)
        
        // 绘制旋转的点
        angle := float64(i) * 3.14159 / 4
        cx, cy := 32, 32
        radius := 20
        
        for j := 0; j < 4; j++ {
            a := angle + float64(j)*3.14159/2
            x := cx + int(float64(radius)*math.Cos(a))
            y := cy + int(float64(radius)*math.Sin(a))
            
            if x >= 0 && x < 64 && y >= 0 && y < 64 {
                img.SetColorIndex(x, y, uint8(j+1))
            }
        }
        
        frames[i] = img
        delays[i] = 10 // 0.1 秒
    }
    
    file, _ := os.Create("loading.gif")
    defer file.Close()
    
    gif.EncodeAll(file, &gif.GIF{
        Image:     frames,
        Delay:     delays,
        LoopCount: 0, // 无限循环
    })
}

示例 3:读取 GIF 并提取所有帧

package main

import (
    "fmt"
    "image/gif"
    "image/png"
    "os"
    "path/filepath"
)

func extractGIFFrames(gifPath, outputDir string) error {
    // 打开 GIF
    file, err := os.Open(gifPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 解码所有帧
    g, err := gif.DecodeAll(file)
    if err != nil {
        return err
    }
    
    // 保存每一帧为 PNG
    for i, frame := range g.Image {
        framePath := filepath.Join(outputDir, fmt.Sprintf("frame_%03d.png", i))
        frameFile, err := os.Create(framePath)
        if err != nil {
            return err
        }
        
        err = png.Encode(frameFile, frame)
        frameFile.Close()
        if err != nil {
            return err
        }
        
        fmt.Printf("已保存:%s\n", framePath)
    }
    
    return nil
}

示例 4:优化 GIF 文件大小

package main

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

func optimizeGIF(inputPath, outputPath string) error {
    // 打开 GIF
    file, err := os.Open(inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // 解码
    g, err := gif.DecodeAll(file)
    if err != nil {
        return err
    }
    
    // 重新编码(减少颜色数量)
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    // 使用更少的颜色和优化选项
    opts := &gif.Options{
        NumColors: 128, // 减少到 128 色
        Quantizer: nil, // 默认量化器
    }
    
    // 如果是单帧
    if len(g.Image) == 1 {
        return gif.Encode(outFile, g.Image[0], opts)
    }
    
    // 如果是多帧,需要手动处理
    // 这里简化处理,实际应该保留所有帧
    return gif.Encode(outFile, g.Image[0], opts)
}

示例 5:创建文字滚动动画

package main

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

func createScrollingText(text string) {
    const (
        width  = 200
        height = 50
        frames = 20
    )
    
    images := make([]*image.Paletted, frames)
    delays := make([]int, frames)
    
    for i := 0; i < frames; i++ {
        img := image.NewPaletted(image.Rect(0, 0, width, height), palette.WebSafe)
        
        // 填充黑色背景
        draw.Draw(img, img.Bounds(), &image.Uniform{color.RGBA{0, 0, 0, 255}}, image.Point{}, draw.Src)
        
        // 这里应该使用 font 包绘制文字
        // 简化示例:绘制移动的白色方块
        x := (width - i*10) % (width + 20)
        if x > width-20 {
            x = -20
        }
        
        for y := 15; y <= 35; y++ {
            for dx := 0; dx < 20; dx++ {
                img.Set(x+dx, y, color.RGBA{255, 255, 255, 255})
            }
        }
        
        images[i] = img
        delays[i] = 5 // 0.05 秒
    }
    
    file, _ := os.Create("scrolling.gif")
    defer file.Close()
    
    gif.EncodeAll(file, &gif.GIF{
        Image:     images,
        Delay:     delays,
        LoopCount: 0,
    })
}

示例 6:GIF 帧信息分析

package main

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

func analyzeGIF(gifPath string) error {
    file, err := os.Open(gifPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    g, err := gif.DecodeAll(file)
    if err != nil {
        return err
    }
    
    fmt.Println("=== GIF 信息 ===")
    fmt.Printf("尺寸:%dx%d\n", g.Config.Width, g.Config.Height)
    fmt.Printf("总帧数:%d\n", len(g.Image))
    fmt.Printf("循环次数:%d", g.LoopCount)
    if g.LoopCount == 0 {
        fmt.Println(" (无限循环)")
    } else {
        fmt.Println()
    }
    fmt.Printf("背景色索引:%d\n", g.BackgroundIndex)
    
    fmt.Println("\n=== 帧详情 ===")
    totalDuration := 0
    for i, frame := range g.Image {
        bounds := frame.Bounds()
        delay := g.Delay[i] * 10 // 转换为毫秒
        totalDuration += delay
        
        fmt.Printf("帧 %d:\n", i)
        fmt.Printf("  尺寸:%dx%d\n", bounds.Dx(), bounds.Dy())
        fmt.Printf("  偏移:(%d, %d)\n", bounds.Min.X, bounds.Min.Y)
        fmt.Printf("  延迟:%dms\n", delay)
        if i < len(g.Disposal) {
            fmt.Printf("  处理方式:%d\n", g.Disposal[i])
        }
    }
    
    fmt.Printf("\n总时长:%dms (%.2f 秒)\n", totalDuration, float64(totalDuration)/1000)
    
    return nil
}

五、最佳实践

1. 选择合适的颜色数量

// 场景 1:简单图形/图标 -> 较少颜色
opts1 := &gif.Options{NumColors: 32}

// 场景 2:照片/复杂图像 -> 更多颜色
opts2 := &gif.Options{NumColors: 256}

// 场景 3:平衡文件大小和质量
opts3 := &gif.Options{NumColors: 128}

2. 使用抖动优化质量

import (
    "image/draw"
    "image/gif"
)

// 使用 Floyd-Steinberg 抖动
opts := &gif.Options{
    NumColors: 256,
    Drawer:    draw.FloydSteinberg,
}
gif.Encode(file, img, opts)

3. 优化动画文件大小

// 技巧 1:减少帧数
// 从 60fps 降到 24fps 或 15fps

// 技巧 2:减少颜色数量
opts := &gif.Options{NumColors: 64}

// 技巧 3:减小图像尺寸
// 在编码前缩放图像

// 技巧 4:使用合适的 Disposal 方式
// 避免不必要的背景恢复

4. 正确处理动画循环

// 无限循环
g.LoopCount = 0

// 播放指定次数
g.LoopCount = 3 // 播放 3 次

// 只播放一次
g.LoopCount = 1

5. 内存管理

// 对于大型 GIF,注意内存使用
file, _ := os.Open("large.gif")
defer file.Close()

g, err := gif.DecodeAll(file)
if err != nil {
    // 处理错误
}

// 使用完后及时释放
// Go 的垃圾回收会自动处理

六、与其他包配合

1. 与 image/draw 配合

package main

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

func jpegToAnimatedGIF(jpegPaths []string, outputPath string) error {
    frames := make([]*image.Paletted, len(jpegPaths))
    delays := make([]int, len(jpegPaths))
    
    for i, path := range jpegPaths {
        // 打开 JPEG
        file, err := os.Open(path)
        if err != nil {
            return err
        }
        defer file.Close()
        
        src, err := jpeg.Decode(file)
        if err != nil {
            return err
        }
        
        // 转换为调色板图像
        frame := image.NewPaletted(src.Bounds(), palette.Plan9)
        draw.Draw(frame, frame.Bounds(), src, src.Bounds().Min, draw.FloydSteinberg)
        
        frames[i] = frame
        delays[i] = 10 // 0.1 秒
    }
    
    // 编码 GIF
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return gif.EncodeAll(outFile, &gif.GIF{
        Image:     frames,
        Delay:     delays,
        LoopCount: 0,
    })
}

2. 与 image/color/palette 配合

package main

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

func createWithCustomPalette(inputPath, outputPath string) error {
    // 打开图像
    file, err := os.Open(inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    src, err := image.Decode(file)
    if err != nil {
        return err
    }
    
    // 使用 Plan9 调色板
    dst := image.NewPaletted(src.Bounds(), palette.Plan9)
    draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.FloydSteinberg)
    
    // 编码为 GIF
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return gif.Encode(outFile, dst, nil)
}

3. 与 image/png 配合

package main

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

func pngToGIF(inputPath, outputPath string) 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
    }
    
    // 编码为 GIF
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return gif.Encode(outFile, img, &gif.Options{
        NumColors: 256,
    })
}

4. 与 bytes 包配合(内存操作)

package main

import (
    "bytes"
    "image"
    "image/gif"
)

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

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

七、快速参考

函数总览

函数名参数返回值描述
Decoder io.Reader(image.Image, error)解码 GIF 第一帧
DecodeAllr io.Reader(*GIF, error)解码完整 GIF(所有帧)
Encodew io.Writer, img image.Image, o *Optionserror编码静态 GIF
EncodeAllw io.Writer, g *GIFerror编码 GIF 动画

结构体总览

结构体名字段描述
GIFImage, Delay, Disposal, BackgroundIndex, LoopCount, ConfigGIF 动画数据结构
OptionsNumColors, Quantizer, Drawer编码选项

常量总览

常量名描述
DisposalNone0x00不处理
DisposalBackground0x01恢复背景色
DisposalPrevious0x02恢复上一帧

GIF 结构体字段详解

字段类型单位/范围说明
Image[]*image.Paletted-所有帧的图像数据
Delay[]int1/100 秒每帧延迟时间
Disposal[]byte0-2每帧处理方式
BackgroundIndexbyte0-255背景色索引
LoopCountint0=无限循环次数
Configimage.Config-图像配置

Options 配置建议

场景NumColorsQuantizerDrawer
简单图标16-32nilnil
图形/图表32-64nilnil
照片128-256nilFloydSteinberg
高质量照片256nilFloydSteinberg

八、注意事项

1. 颜色限制

// GIF 最多支持 256 色
opts := &gif.Options{
    NumColors: 256, // ✓ 最大 256
}

opts := &gif.Options{
    NumColors: 512, // ✗ 错误:超过 256
}

2. 延迟时间单位

// Delay 的单位是 1/100 秒(厘秒)
g.Delay = []int{10}  // 0.1 秒 = 100ms
g.Delay = []int{50}  // 0.5 秒 = 500ms
g.Delay = []int{100} // 1.0 秒 = 1000ms

// 转换公式:毫秒 = Delay * 10

3. 帧尺寸一致性

// 所有帧应该有相同的尺寸
frame1 := image.NewPaletted(image.Rect(0, 0, 100, 100), palette)
frame2 := image.NewPaletted(image.Rect(0, 0, 100, 100), palette) // ✓ 相同
frame3 := image.NewPaletted(image.Rect(0, 0, 50, 50), palette)   // ✗ 不同,可能导致问题

4. LoopCount 语义

g.LoopCount = 0 // ✓ 无限循环(GIF89a 规范)
g.LoopCount = 1 // ✓ 播放 1 次(总共播放 1 次)
g.LoopCount = 2 // ✓ 播放 2 次(总共播放 2 次)

5. 内存使用

// 大型 GIF 可能占用大量内存
// 例如:100 帧 1920x1080 的 GIF

// 建议:
// 1. 减少帧数
// 2. 减小尺寸
// 3. 使用流式处理(如果可能)
// 4. 及时释放资源

6. 透明度处理

// GIF 支持 1 位透明度(完全透明或完全不透明)
// 不支持 alpha 通道的半透明

// 在调色板中设置透明色索引
// 使用 TransparentIndex 字段(在编码器内部处理)

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

package main

import (
    "flag"
    "fmt"
    "image"
    "image/color/palette"
    "image/draw"
    "image/gif"
    "image/jpeg"
    "image/png"
    "os"
)

// GIFTool GIF 处理工具
type GIFTool struct {
    inputPath  string
    outputPath string
    numColors  int
    useDither  bool
}

// NewGIFTool 创建工具实例
func NewGIFTool(inputPath, outputPath string, numColors int, useDither bool) *GIFTool {
    return &GIFTool{
        inputPath:  inputPath,
        outputPath: outputPath,
        numColors:  numColors,
        useDither:  useDither,
    }
}

// ConvertJPEGToGIF 转换 JPEG 到 GIF
func (t *GIFTool) ConvertJPEGToGIF() error {
    // 打开 JPEG
    file, err := os.Open(t.inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    img, err := jpeg.Decode(file)
    if err != nil {
        return err
    }
    
    // 创建选项
    opts := &gif.Options{
        NumColors: t.numColors,
    }
    
    if t.useDither {
        opts.Drawer = draw.FloydSteinberg
    }
    
    // 编码 GIF
    outFile, err := os.Create(t.outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return gif.Encode(outFile, img, opts)
}

// ConvertPNGToGIF 转换 PNG 到 GIF
func (t *GIFTool) ConvertPNGToGIF() 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
    }
    
    opts := &gif.Options{
        NumColors: t.numColors,
    }
    
    if t.useDither {
        opts.Drawer = draw.FloydSteinberg
    }
    
    outFile, err := os.Create(t.outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return gif.Encode(outFile, img, opts)
}

// Info 显示 GIF 信息
func (t *GIFTool) Info() error {
    file, err := os.Open(t.inputPath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    g, err := gif.DecodeAll(file)
    if err != nil {
        return err
    }
    
    fmt.Printf("文件:%s\n", t.inputPath)
    fmt.Printf("尺寸:%dx%d\n", g.Config.Width, g.Config.Height)
    fmt.Printf("帧数:%d\n", len(g.Image))
    fmt.Printf("循环:%d", g.LoopCount)
    if g.LoopCount == 0 {
        fmt.Println(" (无限)")
    } else {
        fmt.Println()
    }
    
    totalDuration := 0
    for i, delay := range g.Delay {
        totalDuration += delay * 10
        fmt.Printf("帧 %d: %dms\n", i, delay*10)
    }
    fmt.Printf("总时长:%dms\n", totalDuration)
    
    return nil
}

// CreateAnimation 从图像序列创建动画
func (t *GIFTool) CreateAnimation(imagePaths []string) error {
    frames := make([]*image.Paletted, len(imagePaths))
    delays := make([]int, len(imagePaths))
    
    for i, path := range imagePaths {
        file, err := os.Open(path)
        if err != nil {
            return err
        }
        
        img, err := png.Decode(file)
        file.Close()
        if err != nil {
            return err
        }
        
        // 转换为调色板图像
        frame := image.NewPaletted(img.Bounds(), palette.Plan9)
        draw.Draw(frame, frame.Bounds(), img, img.Bounds().Min, draw.FloydSteinberg)
        
        frames[i] = frame
        delays[i] = 10 // 0.1 秒
    }
    
    outFile, err := os.Create(t.outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()
    
    return gif.EncodeAll(outFile, &gif.GIF{
        Image:     frames,
        Delay:     delays,
        LoopCount: 0,
    })
}

func main() {
    // 命令行参数
    inputPath := flag.String("i", "", "输入文件路径")
    outputPath := flag.String("o", "", "输出文件路径")
    numColors := flag.Int("colors", 256, "颜色数量")
    useDither := flag.Bool("dither", false, "使用抖动")
    action := flag.String("action", "convert", "操作:convert, info, animate")
    
    flag.Parse()
    
    tool := NewGIFTool(*inputPath, *outputPath, *numColors, *useDither)
    
    var err error
    switch *action {
    case "convert":
        err = tool.ConvertPNGToGIF()
    case "info":
        err = tool.Info()
    case "animate":
        // 需要额外的图像路径参数
        imagePaths := flag.Args()
        err = tool.CreateAnimation(imagePaths)
    }
    
    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/gif