Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go语言图片压缩

使用Go语言带你搞定图片压缩指南

作者:九江Mgx

这篇文章主要为大家详细介绍了如何使用Go语言实现图片压缩功能,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下

痛点催生的灵感

话说某天,我正在帮家里的大朋友处理"学生综合素质评价信息管理系统"的资料上传。系统有个令人无语的限制:图片大小不能超过200KB

可是现在的手机拍照功能太强大了啊!随随便便一张照片都是几MB大小。于是我就陷入了一个循环:

于是我想:“我可是个程序员啊!为什么不自己写一个工具呢?” 于是这个20分钟速成的图片压缩小工具就诞生了。

Go语言:程序员的瑞士军刀

为什么用Go语言?因为它真的太!好!用!了!

工作原理:双管齐下的压缩策略

这个工具的核心思想很简单:先调质量,再缩尺寸

第一阶段:质量压缩

JPEG图片有个质量参数(1-100),我们从高质量开始,逐步降低:

这种方法的优点是不会改变图片尺寸,只是牺牲一点点视觉质量。

第二阶段:尺寸压缩

如果单纯降低质量还不够,我们就开始缩小图片尺寸:

技术亮点

编译方法:三步搞定

Go语言的编译超级简单,只需要几个简单的步骤:

1. 确保已安装Go环境

首先,确保你的电脑上已经安装了Go。打开命令行,输入:

go version

如果显示了Go的版本信息,说明已经安装好了。如果没有,请先去Go官网下载安装。

2. 编译程序

进入到包含main.go的目录,执行以下命令:

# Windows系统
go build -o 图片压缩.exe main.go

# Mac/Linux系统
go build -o 图片压缩 main.go

编译完成后,当前目录下会生成一个可执行文件。

3. 运行程序

编译完成后,使用方法超级简单:

# Windows
图片压缩.exe <输入文件路径> <输出文件路径> <目标大小(KB)>

# Mac/Linux
./图片压缩 <输入文件路径> <输出文件路径> <目标大小(KB)>

例如,要把一张照片压缩到200KB以内:

# Windows
图片压缩.exe 学生照片.jpg 压缩后的照片.jpg 200

# Mac/Linux
./图片压缩 学生照片.jpg 压缩后的照片.jpg 200

运行后,工具会告诉你压缩结果,包括最终大小和压缩率。

总结:编程解决实际问题的快乐

这个小工具虽然简单,但它真正解决了实际问题。现在,我再也不用为了上传一张照片而烦恼了。

这也正是编程的魅力所在:用几行代码,解决生活中的一个小痛点。而且Go语言让这个过程变得如此简单和高效。

如果你也有类似的需求,不妨试试这个工具,或者根据源码自己定制一个适合你的版本!

完整源码

package main

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

// resizeNearest 使用最近邻插值算法缩放图像
// 最近邻插值是最简单的图像缩放算法,通过直接映射源图像像素来实现缩放
// 参数:
// - src: 源图像
// - newWidth: 目标宽度
// - newHeight: 目标高度
// 返回值:
// - *image.NRGBA: 缩放后的图像
func resizeNearest(src image.Image, newWidth, newHeight int) *image.NRGBA {
	// 获取源图像的边界和尺寸
	srcBounds := src.Bounds()
	srcW, srcH := srcBounds.Dx(), srcBounds.Dy()

	// 创建目标图像,使用NRGBA格式(无预乘alpha的RGBA)
	dst := image.NewNRGBA(image.Rect(0, 0, newWidth, newHeight))

	// 遍历目标图像的每个像素
	for y := 0; y < newHeight; y++ {
		for x := 0; x < newWidth; x++ {
			// 计算源图像中对应的坐标
			srcX := int(float64(x) * float64(srcW) / float64(newWidth))
			srcY := int(float64(y) * float64(srcH) / float64(newHeight))
			
			// 边界检查,确保不会越界
			if srcX >= srcW {
				srcX = srcW - 1
			}
			if srcY >= srcH {
				srcY = srcH - 1
			}
			
			// 将源图像像素颜色复制到目标图像
			dst.Set(x, y, src.At(srcX, srcY))
		}
	}
	return dst
}

// encodeJPEG 将图像编码为JPEG格式的字节数据
// 参数:
// - img: 要编码的图像
// - quality: JPEG压缩质量(1-100),值越高质量越好
// 返回值:
// - []byte: 编码后的JPEG字节数据
func encodeJPEG(img image.Image, quality int) []byte {
	var buf bytes.Buffer
	// 使用Go标准库的jpeg.Encode函数进行编码
	jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
	return buf.Bytes()
}

// forceCompressToSize 强制将图像压缩到指定大小
// 使用两阶段压缩策略:先尝试调整质量,若不行再进行尺寸缩放
// 参数:
// - img: 原始图像
// - targetSize: 目标文件大小(字节)
// 返回值:
// - []byte: 压缩后的图像数据
// - error: 可能的错误信息
func forceCompressToSize(img image.Image, targetSize int64) ([]byte, error) {
	// 获取原始图像的尺寸
	originalBounds := img.Bounds()
	origW, origH := originalBounds.Dx(), originalBounds.Dy()

	// 阶段1:仅调整JPEG质量参数(不改变图像尺寸)
	// 从高质量开始尝试,逐步降低质量
	for q := 95; q >= 10; q -= 5 {
		data := encodeJPEG(img, q)
		// 如果当前质量下的文件大小已满足要求,直接返回
		if int64(len(data)) <= targetSize {
			return data, nil
		}
	}

	// 阶段2:如果仅调整质量无法达到目标大小,则使用二分法缩放图像尺寸
	lowScale, highScale := 0.25, 1.0 // 缩放比例范围 (25% - 100%)
	bestData := make([]byte, 0)       // 存储最佳压缩结果
	minDimension := 100               // 最小维度,防止图像被过度缩小
	const maxIterations = 100         // 最大迭代次数,避免死循环

	for i := 0; i < maxIterations; i++ {
		// 计算当前的缩放比例(二分法)
		scale := (lowScale + highScale) / 2
		// 计算新的图像尺寸
		newW := int(float64(origW) * scale)
		newH := int(float64(origH) * scale)

		// 确保图像不会小于最小尺寸
		if newW < minDimension || newH < minDimension {
			newW, newH = minDimension, minDimension
		}

		// 缩放图像并以固定质量(75)编码
		smallImg := resizeNearest(img, newW, newH)
		data := encodeJPEG(smallImg, 75)
		fileSize := int64(len(data))

		// 根据当前文件大小调整搜索范围
		if fileSize <= targetSize {
			// 如果符合要求,记录最佳结果并尝试更大的尺寸
			if len(bestData) == 0 || fileSize < int64(len(bestData)) {
				bestData = data
			}
			lowScale = scale // 尝试更大的尺寸
		} else {
			highScale = scale // 尝试更小的尺寸
		}

		// 当缩放比例的精度足够时退出循环
		if highScale-lowScale < 0.01 { // 1%的精度
			break
		}
	}

	// 如果找到合适的压缩结果,返回最佳数据
	if len(bestData) > 0 {
		return bestData, nil
	}

	// 最后兜底策略:如果没有找到合适的尺寸,则返回最小尺寸的图片
	finalImg := resizeNearest(img, minDimension, minDimension)
	return encodeJPEG(finalImg, 75), nil
}

func main() {
	// 解析命令行参数
	flag.Parse()
	args := flag.Args()

	// 检查参数数量是否正确
	if len(args) != 3 {
		fmt.Println("用法: go run main.go <输入文件路径> <输出文件路径> <目标大小(KB)>")
		fmt.Println("功能: 将输入图像压缩到指定大小,输出始终为JPEG格式")
		os.Exit(1)
	}
	
	// 提取参数值
	inFile := args[0]
	outFile := args[1]
	
	// 解析目标大小
	targetKB, err := strconv.ParseInt(args[2], 10, 64)
	if err != nil {
		fmt.Println("请提供有效的目标大小(KB)")
		os.Exit(1)
	}
	
	// 将KB转换为字节
	target := targetKB * 1024

	// 检查输入文件是否存在
	if _, err := os.Stat(inFile); os.IsNotExist(err) {
		fmt.Printf("错误: 输入文件 '%s' 不存在\n", inFile)
		os.Exit(1)
	}

	// 打开输入文件
	f, err := os.Open(inFile)
	if err != nil {
		fmt.Printf("错误: 无法打开输入文件: %v\n", err)
		os.Exit(1)
	}
	defer f.Close() // 确保在函数结束时关闭文件

	// 解码图像(支持多种格式)
	img, format, err := image.Decode(f)
	if err != nil {
		fmt.Printf("错误: 无法解码图像: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("成功读取 %s 格式图像\n", format)
	fmt.Printf("开始压缩,目标大小: %d 字节\n", target)

	// 执行压缩
	data, err := forceCompressToSize(img, target)
	if err != nil {
		fmt.Printf("错误: 压缩过程中出错: %v\n", err)
		os.Exit(1)
	}

	// 确保输出目录存在
	outDir := filepath.Dir(outFile)
	if outDir != "." {
		if err := os.MkdirAll(outDir, 0755); err != nil {
			fmt.Printf("错误: 无法创建输出目录: %v\n", err)
			os.Exit(1)
		}
	}

	// 写入压缩后的图像数据
	err = os.WriteFile(outFile, data, 0644)
	if err != nil {
		fmt.Printf("错误: 无法写入输出文件: %v\n", err)
		os.Exit(1)
	}
	
	// 输出压缩结果信息
	fmt.Printf("压缩完成!\n")
	fmt.Printf("最终大小: %d 字节 (目标: %d 字节)\n", len(data), target)
	fmt.Printf("压缩率: %.2f%%\n", float64(len(data))/float64(target)*100)
}

到此这篇关于使用Go语言带你搞定图片压缩指南的文章就介绍到这了,更多相关Go语言图片压缩内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文