Golang 编写的图片压缩程序,质量、尺寸压缩,批量、单张压缩

目录:

  前序

  效果图

  简介

  全部代码

前序:

接触 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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏人工智能LeadAI

使用Python解析MNIST数据集

最近在学习Keras,要使用到LeCun大神的MNIST手写数字数据集,直接从官网上下载了4个压缩包:

1144
来自专栏腾讯云商业智能分析团队的专栏

BI 产品中过滤器设置

在展示数据的组件上均可实现对数据的过滤,对已绑定数据的组件才能设置过滤条件。这里介绍一下常见过滤器的设置。

1941
来自专栏安恒网络空间安全讲武堂

适合破解新手的160个crackme练手之03

适合破解新手的160个crackme练手之03 拿到exe,首先用peid查壳吧 ? 检测发现没壳,而且是用vb写的,那么来运行一下先,运行过程中发现程序会先在...

3127
来自专栏有趣的Python

7- vue django restful framework 打造生鲜超市 -商品类别数据展示(上)

Vue+Django REST framework实战 搭建一个前后端分离的生鲜超市网站 Django rtf 完成 商品列表页 并没有将列表页的数据j...

40612
来自专栏JadePeng的技术博客

Latex 公式在线可视化编辑器

寻觅 最近的一个demo需要用到Latex公式在线编辑器,从搜索引擎一般会得到类似http://latex.codecogs.com/eqneditor/edi...

3026
来自专栏FreeBuf

企业壳的反调试及Hook检测分析

*本文原创作者:y0nLandroid,本文属FreeBuf原创奖励计划,未经许可禁止转载 1.写在开始 最近在学习梆梆壳,在调试的过程中遇到了反调试,很是苦恼...

2748
来自专栏james大数据架构

在ASPNET中使用JS集锦

(一).确认删除用法: 1. BtnDel.Attributes.Add("onclick","return confirm('"+"确认删除?"+"')")...

1827
来自专栏华章科技

纯干货:手把手教你用Python做数据可视化(附代码)

导读:制作提供信息的可视化(有时称为绘图)是数据分析中的最重要任务之一。可视化可能是探索过程的一部分,例如,帮助识别异常值或所需的数据转换,或者为建模提供一些想...

972
来自专栏算法+

不用第三方解码库取得图片宽高 附完整C++算法实现代码

在特定的应用场景下,有时候我们只是想获取图片的宽高, 但不想通过解码图片才取得这个信息。 预先知道图片的宽高信息,进而提速图片加载,预处理等相关操作以提升体验。...

3916
来自专栏FreeBuf

秒爆十万字典:奇葩技巧快速枚举“一句话后门”密码

对于一句话大家都不陌生,有时会需要爆破。爆破的速度和目标的响应速度就有很大的关系了。那如果我们爆破的速度可以提升至少1000倍呢? 首先如下图↓ ? 变量=ec...

1897

扫码关注云+社区