前序:
接触 golang 不久,一直是边学边做,边总结,深深感到这门语言的魅力,等下要跟大家分享是最近项目 服务端 用到的图片压缩程序,我单独分离了出来,做成了 exe 程序,可以在 Window 下运行。也可以放到 Linux 环境下编译运行,golang 是一种静态、跨平台的语言。
效果图
-
压缩前 压缩后
开始main:
showTips 做了一些有好提示的文字输出,execute 是核心,压缩函数的调用也在里面
1 func main() {2 showTips()3 execute()4 time.Sleep(5 * time.Minute) /** 如果不是自己点击退出,延时5分钟 */5 }
提示函数
我分离了两种压缩形式,批量和单张,再组合质量和尺寸,压缩100张600K的图片到8~9K,200px宽度,仅用了6秒左右,win 10,12G,i5,ssd。
还可以做完全的,宽和高像素尺寸的限制,只需要改变几个参数,大家先来看看程序运行的时候显示给用户的提示信息:
对于批量压缩,自动遍历用户输入的文件夹里面的所有符合格式的文件,并进行压缩。
1 func showTips() { 2 tips := []string{ 3 "请输入文件夹或图片路径:", 4 "如果输入文件夹,那么该目录的图片将会被批量压缩;", 5 "如果是图片路径,那么将会被单独压缩处理。", 6 "例如:", 7 "C:/Users/lzq/Desktop/headImages/ 75 200", 8 "指桌面 headImages 文件夹,里面的图片质量压缩到75%,宽分辨率为200,高是等比例计算", 9 "C:/Users/lzq/Desktop/headImages/1.jpg 75 200",10 "指桌面的 headImages 文件夹里面的 1.jpg 图片,质量压缩到75%,宽分辨率为200,高是等比例计算 ",11 "请输入:"}12 itemLen := len(tips)13 for i :=0;i<itemLen;i++ {14 if i == itemLen -1 {15 fmt.Printf(tips[i])16 }else{17 fmt.Println(tips[i])18 }19 }20 }
压缩结构体:
这个比较简单,其余添加可以自定义
1 type InputArgs struct {2 OutputPath string /** 输出目录 */3 LocalPath string /** 输入的目录或文件路径 */4 Quality int /** 质量 */5 Width int /** 宽度尺寸,像素单位 */6 }
图片格式验证
自定义支持的文件格式,主要是图片的格式,同时拆分返回一些关键的信息,例如尾缀
1 /** 是否是图片 */ 2 func isPictureFormat(path string) (string,string,string) { 3 temp := strings.Split(path,".") 4 if len(temp) <=1 { 5 return "","","" 6 } 7 mapRule := make(map[string]int64) 8 mapRule["jpg"] = 1 9 mapRule["png"] = 110 mapRule["jpeg"] = 111 // fmt.Println(temp[1]+"---")12 /** 添加其他格式 */13 if mapRule[temp[1]] == 1 {14 println(temp[1])15 return path,temp[1],temp[0]16 }else{17 return "","",""18 }19 }
文件夹遍历
主要用于批量压缩,做了所输入的目录的图片文件遍历,和要保存到的文件夹的创建,和采用纳秒级做压缩后的图片的名称。
1 func getFilelist(path string) { 2 /** 创建输出目录 */ 3 errC := os.MkdirAll(inputArgs.OutputPath, 0777) 4 if errC != nil { 5 fmt.Printf("%s", errC) 6 return 7 } 8 err := filepath.Walk(path, func(pathFound string, f os.FileInfo, err error) error { 9 if ( f == nil ) {10 return err11 }12 if f.IsDir() { /** 是否是目录 */13 return nil14 }15 // println(pathFound)16 /** 找到一个文件 */17 /** 判断是不是图片 */18 localPath,format,_ := isPictureFormat(pathFound)19 /** 随机数 */20 t := time.Now()21 millis := t.Nanosecond() /** 纳秒 */22 outputPath := inputArgs.OutputPath+strconv.FormatInt(int64(millis),10)+"."+format23 if localPath!="" {24 if !imageCompress(25 func() (io.Reader,error){26 return os.Open(localPath)27 },28 func() (*os.File,error) {29 return os.Open(localPath)30 },31 outputPath,32 inputArgs.Quality,33 inputArgs.Width,format) {34 fmt.Println("生成缩略图失败")35 }else{36 fmt.Println("生成缩略图成功 "+outputPath)37 }38 }39 return nil40 })41 if err != nil {42 fmt.Printf("输入的路径信息有误 %v\n", err)43 }44 }
压缩前处理函数:
主要做了压缩结构体数据的配置,和验证用户路径的输入以及最终压缩输出文件目录的路径组合。这里有个坑点,对于控制台的数据获取,最好使用 bufio.NewReader(os.Stdin) 而不是 fmt.Scanf 否则,在fmt.p... 输出错误提示信息的时候也会被当作输入读取了,而不是用户输入的。
func execute() { /** 获取输入 */ //str := "" //fmt.Scanln (&str) /** 不要使用 scanf,它不会并以一个新行结束输入 */ reader := bufio.NewReader(os.Stdin) data, _, _ := reader.ReadLine() /** 分割 */ strPice := strings.Split(string(data)," ") /** 空格 */ if len(strPice) < 3 { fmt.Printf("输入有误,参数数量不足,请重新输入或退出程序:") execute() return } inputArgs.LocalPath = strPice[0] inputArgs.Quality,_ = strconv.Atoi(strPice[1]) inputArgs.Width,_ = strconv.Atoi(strPice[2]) pathTemp,format,top := isPictureFormat(inputArgs.LocalPath) if pathTemp == "" { /** 目录 */ /** 如果输入目录,那么是批量 */ fmt.Println("开始批量压缩...") rs := []rune(inputArgs.LocalPath) end := len(rs) substr := string(rs[end-1:end]) if substr=="/" { /** 有 / */ rs := []rune(inputArgs.LocalPath) end := len(rs) substr := string(rs[0:end-1]) endIndex := strings.LastIndex(substr,"/") inputArgs.OutputPath = string(rs[0:endIndex])+"/LghImageCompress/"; }else { endIndex := strings.LastIndex(inputArgs.LocalPath,"/") inputArgs.OutputPath = string(rs[0:endIndex])+"/LghImageCompress/"; } getFilelist(inputArgs.LocalPath) }else{ /** 单个 */ /** 如果输入文件,那么是单个,允许自定义路径 */ fmt.Println("开始单张压缩...") inputArgs.OutputPath = top+"_compress."+format if !imageCompress( func() (io.Reader,error){ return os.Open(inputArgs.LocalPath) }, func() (*os.File,error) { return os.Open(inputArgs.LocalPath) }, inputArgs.OutputPath, inputArgs.Quality, inputArgs.Width,format) { fmt.Println("生成缩略图失败") }else{ fmt.Println("生成缩略图成功 "+inputArgs.OutputPath) finish() } }}
压缩函数(核心):
基于golang 1.7 自带的 image/jpeg 库。所谓的宽高完全自定义的修改,就在这里,我是采用了等比例缩放,所以只需要传入其中一项。里面分两次读写同一个文件是因为一次用于尺寸读取,而且两次是不能共用的,会出错。
1 func imageCompress( 2 getReadSizeFile func() (io.Reader,error), 3 getDecodeFile func() (*os.File,error), 4 to string, 5 Quality, 6 base int, 7 format string) bool{ 8 /** 读取文件 */ 9 file_origin, err := getDecodeFile()10 defer file_origin.Close()11 if err != nil {12 fmt.Println("os.Open(file)错误");13 log.Fatal(err)14 return false15 }16 var origin image.Image17 var config image.Config18 var temp io.Reader19 /** 读取尺寸 */20 temp, err = getReadSizeFile()21 if err != nil {22 fmt.Println("os.Open(temp)");23 log.Fatal(err)24 return false25 }26 var typeImage int6427 format = strings.ToLower(format)28 /** jpg 格式 */29 if format=="jpg" || format =="jpeg" {30 typeImage = 131 origin, err = jpeg.Decode(file_origin)32 if err != nil {33 fmt.Println("jpeg.Decode(file_origin)");34 log.Fatal(err)35 return false36 }37 temp, err = getReadSizeFile()38 if err != nil {39 fmt.Println("os.Open(temp)");40 log.Fatal(err)41 return false42 }43 config,err = jpeg.DecodeConfig(temp);44 if err != nil {45 fmt.Println("jpeg.DecodeConfig(temp)");46 return false47 }48 }else if format=="png" {49 typeImage = 050 origin, err = png.Decode(file_origin)51 if err != nil {52 fmt.Println("png.Decode(file_origin)");53 log.Fatal(err)54 return false55 }56 temp, err = getReadSizeFile()57 if err != nil {58 fmt.Println("os.Open(temp)");59 log.Fatal(err)60 return false61 }62 config,err = png.DecodeConfig(temp);63 if err != nil {64 fmt.Println("png.DecodeConfig(temp)");65 return false66 }67 }68 /** 做等比缩放 */69 width := uint(base) /** 基准 */70 height := uint(base*config.Height/config.Width)71 72 canvas := resize.Thumbnail(width, height, origin, resize.Lanczos3)73 file_out, err := os.Create(to)74 defer file_out.Close()75 if err != nil {76 log.Fatal(err)77 return false78 }79 if typeImage==0 {80 err = png.Encode(file_out, canvas)81 if err!=nil {82 fmt.Println("压缩图片失败");83 return false84 }85 }else{86 err = jpeg.Encode(file_out, canvas, &jpeg.Options{Quality})87 if err!=nil {88 fmt.Println("压缩图片失败");89 return false90 }91 }92 93 return true94 }