目录:
前序
效果图
简介
全部代码
前序:
接触 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"] = 1 10 mapRule["jpeg"] = 1 11 // 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 err 11 } 12 if f.IsDir() { /** 是否是目录 */ 13 return nil 14 } 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)+"."+format 23 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 nil 40 }) 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 false 15 } 16 var origin image.Image 17 var config image.Config 18 var temp io.Reader 19 /** 读取尺寸 */ 20 temp, err = getReadSizeFile() 21 if err != nil { 22 fmt.Println("os.Open(temp)"); 23 log.Fatal(err) 24 return false 25 } 26 var typeImage int64 27 format = strings.ToLower(format) 28 /** jpg 格式 */ 29 if format=="jpg" || format =="jpeg" { 30 typeImage = 1 31 origin, err = jpeg.Decode(file_origin) 32 if err != nil { 33 fmt.Println("jpeg.Decode(file_origin)"); 34 log.Fatal(err) 35 return false 36 } 37 temp, err = getReadSizeFile() 38 if err != nil { 39 fmt.Println("os.Open(temp)"); 40 log.Fatal(err) 41 return false 42 } 43 config,err = jpeg.DecodeConfig(temp); 44 if err != nil { 45 fmt.Println("jpeg.DecodeConfig(temp)"); 46 return false 47 } 48 }else if format=="png" { 49 typeImage = 0 50 origin, err = png.Decode(file_origin) 51 if err != nil { 52 fmt.Println("png.Decode(file_origin)"); 53 log.Fatal(err) 54 return false 55 } 56 temp, err = getReadSizeFile() 57 if err != nil { 58 fmt.Println("os.Open(temp)"); 59 log.Fatal(err) 60 return false 61 } 62 config,err = png.DecodeConfig(temp); 63 if err != nil { 64 fmt.Println("png.DecodeConfig(temp)"); 65 return false 66 } 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 false 78 } 79 if typeImage==0 { 80 err = png.Encode(file_out, canvas) 81 if err!=nil { 82 fmt.Println("压缩图片失败"); 83 return false 84 } 85 }else{ 86 err = jpeg.Encode(file_out, canvas, &jpeg.Options{Quality}) 87 if err!=nil { 88 fmt.Println("压缩图片失败"); 89 return false 90 } 91 } 92 93 return true 94 }
全部代码
gitHub: https://github.com/af913337456/golang_image_compress