使用Go語(yǔ)言帶你搞定圖片壓縮指南
痛點(diǎn)催生的靈感
話說(shuō)某天,我正在幫家里的大朋友處理"學(xué)生綜合素質(zhì)評(píng)價(jià)信息管理系統(tǒng)"的資料上傳。系統(tǒng)有個(gè)令人無(wú)語(yǔ)的限制:圖片大小不能超過(guò)200KB。
可是現(xiàn)在的手機(jī)拍照功能太強(qiáng)大了啊!隨隨便便一張照片都是幾MB大小。于是我就陷入了一個(gè)循環(huán):
- 拍照片 → 太大上傳失敗
- 找在線壓縮網(wǎng)站 → 廣告彈窗滿天飛
- 打開(kāi)Photoshop → 等半天,然后點(diǎn)半天,最后忘了保存格式
- 最終:心態(tài)爆炸
于是我想:“我可是個(gè)程序員啊!為什么不自己寫(xiě)一個(gè)工具呢?” 于是這個(gè)20分鐘速成的圖片壓縮小工具就誕生了。
Go語(yǔ)言:程序員的瑞士軍刀
為什么用Go語(yǔ)言?因?yàn)樗娴奶『?!用!了?/p>
- 開(kāi)箱即用:標(biāo)準(zhǔn)庫(kù)自帶圖像處理功能,完全不用找第三方包
- 編譯超快:寫(xiě)完代碼,"go build"一下,瞬間得到一個(gè)可執(zhí)行文件
- 跨平臺(tái):一次編寫(xiě),到處運(yùn)行,Windows、Mac、Linux通吃
- 代碼簡(jiǎn)潔:同樣的功能,Go代碼比其他語(yǔ)言至少短一半
工作原理:雙管齊下的壓縮策略
這個(gè)工具的核心思想很簡(jiǎn)單:先調(diào)質(zhì)量,再縮尺寸。
第一階段:質(zhì)量壓縮
JPEG圖片有個(gè)質(zhì)量參數(shù)(1-100),我們從高質(zhì)量開(kāi)始,逐步降低:
- 從95%質(zhì)量開(kāi)始嘗試
- 如果文件大小還是超過(guò)目標(biāo),降到90%
- 繼續(xù)降到85%…直到10%
- 如果某個(gè)質(zhì)量下的文件大小滿足要求,直接返回
這種方法的優(yōu)點(diǎn)是不會(huì)改變圖片尺寸,只是犧牲一點(diǎn)點(diǎn)視覺(jué)質(zhì)量。
第二階段:尺寸壓縮
如果單純降低質(zhì)量還不夠,我們就開(kāi)始縮小圖片尺寸:
- 使用二分法智能尋找最佳縮放比例
- 從25%到100%之間搜索
- 每次縮放后都檢查文件大小
- 確保圖像不會(huì)被縮得太?。ㄔO(shè)置最小尺寸保護(hù))
技術(shù)亮點(diǎn)
- 二分查找算法:比線性搜索更高效,幾輪迭代就能找到合適的尺寸
- 邊界保護(hù):防止圖像被壓縮得太小導(dǎo)致無(wú)法識(shí)別
- 錯(cuò)誤處理:友好的錯(cuò)誤提示,不會(huì)讓用戶一臉懵逼
- 多種格式支持:輸入支持JPEG、PNG、GIF等多種格式
編譯方法:三步搞定
Go語(yǔ)言的編譯超級(jí)簡(jiǎn)單,只需要幾個(gè)簡(jiǎn)單的步驟:
1. 確保已安裝Go環(huán)境
首先,確保你的電腦上已經(jīng)安裝了Go。打開(kāi)命令行,輸入:
go version
如果顯示了Go的版本信息,說(shuō)明已經(jīng)安裝好了。如果沒(méi)有,請(qǐng)先去Go官網(wǎng)下載安裝。
2. 編譯程序
進(jìn)入到包含main.go的目錄,執(zhí)行以下命令:
# Windows系統(tǒng) go build -o 圖片壓縮.exe main.go # Mac/Linux系統(tǒng) go build -o 圖片壓縮 main.go
編譯完成后,當(dāng)前目錄下會(huì)生成一個(gè)可執(zhí)行文件。
3. 運(yùn)行程序
編譯完成后,使用方法超級(jí)簡(jiǎn)單:
# Windows 圖片壓縮.exe <輸入文件路徑> <輸出文件路徑> <目標(biāo)大?。↘B)> # Mac/Linux ./圖片壓縮 <輸入文件路徑> <輸出文件路徑> <目標(biāo)大?。↘B)>
例如,要把一張照片壓縮到200KB以內(nèi):
# Windows 圖片壓縮.exe 學(xué)生照片.jpg 壓縮后的照片.jpg 200 # Mac/Linux ./圖片壓縮 學(xué)生照片.jpg 壓縮后的照片.jpg 200
運(yùn)行后,工具會(huì)告訴你壓縮結(jié)果,包括最終大小和壓縮率。
總結(jié):編程解決實(shí)際問(wèn)題的快樂(lè)
這個(gè)小工具雖然簡(jiǎn)單,但它真正解決了實(shí)際問(wèn)題?,F(xiàn)在,我再也不用為了上傳一張照片而煩惱了。
這也正是編程的魅力所在:用幾行代碼,解決生活中的一個(gè)小痛點(diǎn)。而且Go語(yǔ)言讓這個(gè)過(guò)程變得如此簡(jiǎn)單和高效。
如果你也有類(lèi)似的需求,不妨試試這個(gè)工具,或者根據(jù)源碼自己定制一個(gè)適合你的版本!
完整源碼
package main
import (
"bytes"
"flag"
"fmt"
"image"
"image/jpeg"
"os"
"path/filepath"
"strconv"
)
// resizeNearest 使用最近鄰插值算法縮放圖像
// 最近鄰插值是最簡(jiǎn)單的圖像縮放算法,通過(guò)直接映射源圖像像素來(lái)實(shí)現(xiàn)縮放
// 參數(shù):
// - src: 源圖像
// - newWidth: 目標(biāo)寬度
// - newHeight: 目標(biāo)高度
// 返回值:
// - *image.NRGBA: 縮放后的圖像
func resizeNearest(src image.Image, newWidth, newHeight int) *image.NRGBA {
// 獲取源圖像的邊界和尺寸
srcBounds := src.Bounds()
srcW, srcH := srcBounds.Dx(), srcBounds.Dy()
// 創(chuàng)建目標(biāo)圖像,使用NRGBA格式(無(wú)預(yù)乘alpha的RGBA)
dst := image.NewNRGBA(image.Rect(0, 0, newWidth, newHeight))
// 遍歷目標(biāo)圖像的每個(gè)像素
for y := 0; y < newHeight; y++ {
for x := 0; x < newWidth; x++ {
// 計(jì)算源圖像中對(duì)應(yīng)的坐標(biāo)
srcX := int(float64(x) * float64(srcW) / float64(newWidth))
srcY := int(float64(y) * float64(srcH) / float64(newHeight))
// 邊界檢查,確保不會(huì)越界
if srcX >= srcW {
srcX = srcW - 1
}
if srcY >= srcH {
srcY = srcH - 1
}
// 將源圖像像素顏色復(fù)制到目標(biāo)圖像
dst.Set(x, y, src.At(srcX, srcY))
}
}
return dst
}
// encodeJPEG 將圖像編碼為JPEG格式的字節(jié)數(shù)據(jù)
// 參數(shù):
// - img: 要編碼的圖像
// - quality: JPEG壓縮質(zhì)量(1-100),值越高質(zhì)量越好
// 返回值:
// - []byte: 編碼后的JPEG字節(jié)數(shù)據(jù)
func encodeJPEG(img image.Image, quality int) []byte {
var buf bytes.Buffer
// 使用Go標(biāo)準(zhǔn)庫(kù)的jpeg.Encode函數(shù)進(jìn)行編碼
jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
return buf.Bytes()
}
// forceCompressToSize 強(qiáng)制將圖像壓縮到指定大小
// 使用兩階段壓縮策略:先嘗試調(diào)整質(zhì)量,若不行再進(jìn)行尺寸縮放
// 參數(shù):
// - img: 原始圖像
// - targetSize: 目標(biāo)文件大小(字節(jié))
// 返回值:
// - []byte: 壓縮后的圖像數(shù)據(jù)
// - error: 可能的錯(cuò)誤信息
func forceCompressToSize(img image.Image, targetSize int64) ([]byte, error) {
// 獲取原始圖像的尺寸
originalBounds := img.Bounds()
origW, origH := originalBounds.Dx(), originalBounds.Dy()
// 階段1:僅調(diào)整JPEG質(zhì)量參數(shù)(不改變圖像尺寸)
// 從高質(zhì)量開(kāi)始嘗試,逐步降低質(zhì)量
for q := 95; q >= 10; q -= 5 {
data := encodeJPEG(img, q)
// 如果當(dāng)前質(zhì)量下的文件大小已滿足要求,直接返回
if int64(len(data)) <= targetSize {
return data, nil
}
}
// 階段2:如果僅調(diào)整質(zhì)量無(wú)法達(dá)到目標(biāo)大小,則使用二分法縮放圖像尺寸
lowScale, highScale := 0.25, 1.0 // 縮放比例范圍 (25% - 100%)
bestData := make([]byte, 0) // 存儲(chǔ)最佳壓縮結(jié)果
minDimension := 100 // 最小維度,防止圖像被過(guò)度縮小
const maxIterations = 100 // 最大迭代次數(shù),避免死循環(huán)
for i := 0; i < maxIterations; i++ {
// 計(jì)算當(dāng)前的縮放比例(二分法)
scale := (lowScale + highScale) / 2
// 計(jì)算新的圖像尺寸
newW := int(float64(origW) * scale)
newH := int(float64(origH) * scale)
// 確保圖像不會(huì)小于最小尺寸
if newW < minDimension || newH < minDimension {
newW, newH = minDimension, minDimension
}
// 縮放圖像并以固定質(zhì)量(75)編碼
smallImg := resizeNearest(img, newW, newH)
data := encodeJPEG(smallImg, 75)
fileSize := int64(len(data))
// 根據(jù)當(dāng)前文件大小調(diào)整搜索范圍
if fileSize <= targetSize {
// 如果符合要求,記錄最佳結(jié)果并嘗試更大的尺寸
if len(bestData) == 0 || fileSize < int64(len(bestData)) {
bestData = data
}
lowScale = scale // 嘗試更大的尺寸
} else {
highScale = scale // 嘗試更小的尺寸
}
// 當(dāng)縮放比例的精度足夠時(shí)退出循環(huán)
if highScale-lowScale < 0.01 { // 1%的精度
break
}
}
// 如果找到合適的壓縮結(jié)果,返回最佳數(shù)據(jù)
if len(bestData) > 0 {
return bestData, nil
}
// 最后兜底策略:如果沒(méi)有找到合適的尺寸,則返回最小尺寸的圖片
finalImg := resizeNearest(img, minDimension, minDimension)
return encodeJPEG(finalImg, 75), nil
}
func main() {
// 解析命令行參數(shù)
flag.Parse()
args := flag.Args()
// 檢查參數(shù)數(shù)量是否正確
if len(args) != 3 {
fmt.Println("用法: go run main.go <輸入文件路徑> <輸出文件路徑> <目標(biāo)大?。↘B)>")
fmt.Println("功能: 將輸入圖像壓縮到指定大小,輸出始終為JPEG格式")
os.Exit(1)
}
// 提取參數(shù)值
inFile := args[0]
outFile := args[1]
// 解析目標(biāo)大小
targetKB, err := strconv.ParseInt(args[2], 10, 64)
if err != nil {
fmt.Println("請(qǐng)?zhí)峁┯行У哪繕?biāo)大?。↘B)")
os.Exit(1)
}
// 將KB轉(zhuǎn)換為字節(jié)
target := targetKB * 1024
// 檢查輸入文件是否存在
if _, err := os.Stat(inFile); os.IsNotExist(err) {
fmt.Printf("錯(cuò)誤: 輸入文件 '%s' 不存在\n", inFile)
os.Exit(1)
}
// 打開(kāi)輸入文件
f, err := os.Open(inFile)
if err != nil {
fmt.Printf("錯(cuò)誤: 無(wú)法打開(kāi)輸入文件: %v\n", err)
os.Exit(1)
}
defer f.Close() // 確保在函數(shù)結(jié)束時(shí)關(guān)閉文件
// 解碼圖像(支持多種格式)
img, format, err := image.Decode(f)
if err != nil {
fmt.Printf("錯(cuò)誤: 無(wú)法解碼圖像: %v\n", err)
os.Exit(1)
}
fmt.Printf("成功讀取 %s 格式圖像\n", format)
fmt.Printf("開(kāi)始?jí)嚎s,目標(biāo)大小: %d 字節(jié)\n", target)
// 執(zhí)行壓縮
data, err := forceCompressToSize(img, target)
if err != nil {
fmt.Printf("錯(cuò)誤: 壓縮過(guò)程中出錯(cuò): %v\n", err)
os.Exit(1)
}
// 確保輸出目錄存在
outDir := filepath.Dir(outFile)
if outDir != "." {
if err := os.MkdirAll(outDir, 0755); err != nil {
fmt.Printf("錯(cuò)誤: 無(wú)法創(chuàng)建輸出目錄: %v\n", err)
os.Exit(1)
}
}
// 寫(xiě)入壓縮后的圖像數(shù)據(jù)
err = os.WriteFile(outFile, data, 0644)
if err != nil {
fmt.Printf("錯(cuò)誤: 無(wú)法寫(xiě)入輸出文件: %v\n", err)
os.Exit(1)
}
// 輸出壓縮結(jié)果信息
fmt.Printf("壓縮完成!\n")
fmt.Printf("最終大小: %d 字節(jié) (目標(biāo): %d 字節(jié))\n", len(data), target)
fmt.Printf("壓縮率: %.2f%%\n", float64(len(data))/float64(target)*100)
}
到此這篇關(guān)于使用Go語(yǔ)言帶你搞定圖片壓縮指南的文章就介紹到這了,更多相關(guān)Go語(yǔ)言圖片壓縮內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言中進(jìn)行API限流的實(shí)戰(zhàn)詳解
API?限流是控制和管理應(yīng)用程序訪問(wèn)量的重要手段,旨在防止惡意濫用、保護(hù)后端服務(wù)的穩(wěn)定性和可用性,下面我們就來(lái)看看如何在Go語(yǔ)言中具體實(shí)現(xiàn)吧2025-01-01
Golang關(guān)鍵字select的常用用法總結(jié)
這篇文章主要為大家詳細(xì)介紹了golang中select關(guān)鍵字的常用用法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-10-10
Golang中g(shù)oroutine和channel使用介紹深入分析
一次只做一件事情并不是完成任務(wù)最快的方法,一些大的任務(wù)可以拆解成若干個(gè)小任務(wù),goroutine可以讓程序同時(shí)處理幾個(gè)不同的任務(wù),goroutine使用channel來(lái)協(xié)調(diào)它們的工作,channel允許goroutine互相發(fā)送數(shù)據(jù)并同步,這樣一個(gè)goroutine就不會(huì)領(lǐng)先于另一個(gè)goroutine2023-01-01
GPT回答:go語(yǔ)言和C語(yǔ)言切片對(duì)比
這篇文章主要為大家介紹了GPT回答:go語(yǔ)言和C語(yǔ)言切片對(duì)比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
從零封裝Gin框架實(shí)現(xiàn)數(shù)據(jù)庫(kù)初始化GORM
這篇文章主要為大家介紹了從零封裝Gin框架實(shí)現(xiàn)數(shù)據(jù)庫(kù)初始化GORM,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
Go語(yǔ)言中Struct與繼承與匿名字段和內(nèi)嵌結(jié)構(gòu)體全面詳解
這篇文章主要介紹了Go語(yǔ)言中Struct與繼承與匿名字段和內(nèi)嵌結(jié)構(gòu)體,Go語(yǔ)言中通過(guò)結(jié)構(gòu)體的內(nèi)嵌再配合接口比面向?qū)ο缶哂懈叩臄U(kuò)展性和靈活性,感興趣的可以了解一下2023-04-04
Go 實(shí)現(xiàn)一次性打包各個(gè)平臺(tái)的可執(zhí)行程序
這篇文章主要介紹了Go 實(shí)現(xiàn)一次性打包各個(gè)平臺(tái)的可執(zhí)行程序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
基于Go語(yǔ)言實(shí)現(xiàn)類(lèi)似tree命令的小程序
tree?命令是一個(gè)小型的跨平臺(tái)命令行程序,用于遞歸地以樹(shù)狀格式列出或顯示目錄的內(nèi)容。本文將通過(guò)Go語(yǔ)言實(shí)現(xiàn)類(lèi)似tree命令的小程序,需要的可以參考一下2022-10-10

