Golang压缩Jpeg图片和PNG图片的操作

博主一直在维护一个导出PDF的服务,但是这个服务导出的PDF文件是真的巨大,动辄就上百MB。这里面主要是图片占据了大多数体积,所以考虑在导出前压缩一下图片。

Jpeg的图片压缩是很好做的,因为jpeg这个协议本身就支持调整图片质量的。在golang中,我们只需要使用标准库的image/jpeg,将图片从二进制数据解码后,降低质量再编码为二进制数据即可实现压缩。而且质量和压缩比例相对而言还不错。

func compressImageResource(data []byte) []byte {
 img, _, err := image.Decode(bytes.NewReader(data))
 if err != nil {
 return data
 }
 buf := bytes.Buffer{}
 err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: 40})
 if err != nil {
 return data
 }
 if buf.Len() > len(data) {
 return data
 }
 return buf.Bytes()
}

比较麻烦的是压缩PNG图片,在网上找了很多相关的库,感觉都没什么即可以保持质量,又可以尽可能压缩的办法。

//下面这两个库都比较偏重于转换图片大小,在保持宽高不变的情况下,压缩比例很一般
https://github.com/discord/lilliput   //这个库是一家海外公司基于C语言的一个开源图片处理库,但是封装的很好,不需要安装额外依赖
https://github.com/disintegration/imaging
//下面这个库可以对PNG图片进行较大的压缩,可惜压缩比例过大时会严重失真
https://github.com/foobaz/lossypng/

后来,借鉴一篇博客的做法,还是先把PNG图片转换为Jpeg图片,然后再将jpeg图片的质量降低。相对上边这些库,压缩比例和质量都比较令人满意

func compressImageResource(data []byte) []byte {
 imgSrc, _, err := image.Decode(bytes.NewReader(data))
 if err != nil {
 return data
 }
    newImg := image.NewRGBA(imgSrc.Bounds())
 draw.Draw(newImg, newImg.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src)
 draw.Draw(newImg, newImg.Bounds(), imgSrc, imgSrc.Bounds().Min, draw.Over)
 buf := bytes.Buffer{}
 err = jpeg.Encode(&buf, newImg, &jpeg.Options{Quality: 40})
 if err != nil {
 return data
 }
 if buf.Len() > len(data) {
 return data
 }
 return buf.Bytes()
}

最后给大家分享一个超级好用PDF处理的golang 库: https://github.com/unidoc/unipdf。一开始使用这个库将生成后的PDF压缩的,可以将一个200M的PDF(里面都是图片)直接压缩到7M左右。可惜的是这个库商用需要购买商业版权,所以最后只能采取了导出前压缩图片的做法。

这个库没有授权的情况下会在处理后的PDF中加上水印,这个想去掉也简单,fork下来改一下代码就好了。虽然我这里因为是商业的场景不能这么用,但是我还是尝试了下,仓库在这:https://github.com/lianggx6/unipdf。然后再在go.mod文件中将依赖替换即可。大家如果有个人开发实践需要的可以直接这样拿来用,商用务必购买版权。

replace (
 github.com/unidoc/unipdf/v3 => github.com/lianggx6/unipdf v0.0.0-20200409043947-1c871b2c4951
)

补充:golang中image/jpeg包和image/png包用法

jpeg包实现了jpeg图片的编码和解码

func Decode(r io.Reader) (image.Image, error)  //Decode读取一个jpeg文件,并将他作为image.Image返回
func DecodeConfig(r io.Reader) (image.Config, error)  //无需解码整个图像,DecodeConfig变能够返回整个图像的尺寸和颜色(Config具体定义查看gif包中的定义)
func Encode(w io.Writer, m image.Image, o *Options) error  //按照4:2:0的基准格式将image写入w中,如果options为空的话,则传递默认参数
type Options struct {
 Quality int
}

Options是编码参数,它的取值范围是1-100,值越高质量越好

type FormatError //用来报告一个输入不是有效的jpeg格式
type FormatError string
func (e FormatError) Error() string
type Reader //不推荐使用Reader
type Reader interface {
 io.ByteReader
 io.Reader
}
type UnsupportedError
func (e UnsupportedError) Error() string  //报告输入使用一个有效但是未实现的jpeg功能

利用程序画一条直线,代码如下:

package main

import (
 "fmt"
 "image"
 "image/color"
 "image/jpeg"
 "math"
 "os"
)

const (
 dx = 500
 dy = 300
)

type Putpixel func(x, y int)

func drawline(x0, y0, x1, y1 int, brush Putpixel) {
 dx := math.Abs(float64(x1 - x0))
 dy := math.Abs(float64(y1 - y0))
 sx, sy := 1, 1
 if x0 >= x1 {
 sx = -1
 }
 if y0 >= y1 {
 sy = -1
 }
 err := dx - dy
 for {
 brush(x0, y0)
 if x0 == x1 && y0 == y1 {
  return
 }
 e2 := err * 2
 if e2 > -dy {
  err -= dy
  x0 += sx
 }
 if e2 < dx {
  err += dx
  y0 += sy
 }
 }
}
func main() {
 file, err := os.Create("test.jpg")
 if err != nil {
 fmt.Println(err)
 }
 defer file.Close()
 nrgba := image.NewNRGBA(image.Rect(0, 0, dx, dy))
 drawline(1, 1, dx-2, dy-2, func(x, y int) {
 nrgba.Set(x, y, color.RGBA{uint8(x), uint8(y), 0, 255})
 })
 for y := 0; y < dy; y++ {
 nrgba.Set(1, y, color.White)
 nrgba.Set(dx-1, y, color.White)
 }
 err = jpeg.Encode(file, nrgba, &jpeg.Options{100})   //图像质量值为100,是最好的图像显示
 if err != nil {
 fmt.Println(err)
 }
}

根据已经得到的图像test.jpg,我们创建一个新的图像test1.jpg

package main

import (
 "fmt"
 "image/jpeg"
 "os"
)

func main() {
 file, err := os.Open("test.jpg")
 if err != nil {
 fmt.Println(err)
 }
 defer file.Close()

 file1, err := os.Create("test1.jpg")
 if err != nil {
 fmt.Println(err)
 }
 defer file1.Close()

 img, err := jpeg.Decode(file) //解码
 if err != nil {
 fmt.Println(err)
 }
 jpeg.Encode(file1, img, &jpeg.Options{5}) //编码,但是将图像质量从100改成5

}

对比图像质量为100和5的图像:

image/png包用法:

image/png实现了png图像的编码和解码

png和jpeg实现方法基本相同,都是对图像进行了编码和解码操作。

func Decode(r io.Reader) (image.Image, error)   //Decode从r中读取一个图片,并返回一个image.image,返回image类型取决于png图片的内容
func DecodeConfig(r io.Reader) (image.Config, error)  //无需解码整个图像变能够获取整个图片的尺寸和颜色
func Encode(w io.Writer, m image.Image) error  //Encode将图片m以PNG的格式写到w中。任何图片都可以被编码,但是哪些不是image.NRGBA的图片编码可能是有损的。
type FormatError
func (e FormatError) Error() string     //FormatError会提示一个输入不是有效PNG的错误。
type UnsupportedError
func (e UnsupportedError) Error() string //UnsupportedError会提示输入使用一个合法的,但是未实现的PNG特性。

利用png包实现一个png的图像,代码如下:

package main
import (
 "fmt"
 "image"
 "image/color"
 "image/png"
 "os"
)

const (
 dx = 256
 dy = 256
)

func Pic(dx, dy int) [][]uint8 {
 pic := make([][]uint8, dx)
 for i := range pic {
 pic[i] = make([]uint8, dy)
 for j := range pic[i] {
  pic[i][j] = uint8(i * j % 255)
 }
 }
 return pic
}
func main() {
 file, err := os.Create("test.png")
 if err != nil {
 fmt.Println(err)
 }
 defer file.Close()
 rgba := image.NewRGBA(image.Rect(0, 0, dx, dy))
 for x := 0; x < dx; x++ {
 for y := 0; y < dy; y++ {
  rgba.Set(x, y, color.RGBA{uint8(x * y % 255), uint8(x * y % 255), 0, 255})
 }
 }
 err = png.Encode(file, rgba)
 if err != nil {
 fmt.Println(err)
 }
}

图像如下:

由此可见,png和jpeg使用方法类似,只是两种不同的编码和解码方式。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • 浅谈golang中的&^位清空操作

    如下所示: c = a &^ b 含义:b 转为二进制时,值为1 的位置对应c的位置值为0:c中剩余位置值与a对应位置值相同(即:所谓的位清空操作,把b中1对应位置在c位置上清空),详见下面代码 package main import "fmt" func main() { a := 12 b := 4 c := a &^ b fmt.Printf("a: %08b\n", a) fmt.Printf("b:%08b\n", b)

  • 如何判断Golang接口是否实现的操作

    前言 在看一个底层库的的时候,看到了一个比较奇怪的写法,于是乎有了本文. 主要探讨两个问题: 1.利用编译来判断Golang接口是否实现 2.延伸出的make和new的区别 正文 1.利用编译来判断Golang接口是否实现 看了一个底层通用链接池的库,有这么一行代码: var _ Pooler = new(WeightedRoundRobin) 需要解释的是:Pooler是一个接口类型. type Pooler interface { // ... } 刚开始看是疑惑的,为什么new了之后是要抛

  • golang解析yaml文件操作

    首先安装解析的第三方包: go get gopkg.in/yaml.v2 示例: package main import ( "os" "log" "fmt" "encoding/json" "gopkg.in/yaml.v2" ) type Config struct { Test Test `yaml:"test"` } type Test struct { User []strin

  • Golang获取目录下的文件及目录信息操作

    一.获取当前目录下的文件或目录信息(不包含多级子目录) func main() { pwd,_ := os.Getwd() //获取文件或目录相关信息 fileInfoList,err := ioutil.ReadDir(pwd) if err != nil { log.Fatal(err) } fmt.Println(len(fileInfoList)) for i := range fileInfoList { fmt.Println(fileInfoList[i].Name()) //打印

  • 对Golang中的runtime.Caller使用说明

    如下所示: func Caller(skip int) (pc uintptr, file string, line int, ok bool) 参数:skip是要提升的堆栈帧数,0-当前函数,1-上一层函数,.... 返回值: pc是uintptr这个返回的是函数指针 file是函数所在文件名目录 line所在行号 ok 是否可以获取到信息 示例: 我们分别打印skip为0-3的相关信息 package main import ( "fmt" "runtime"

  • 利用golang的字符串解决leetcode翻转字符串里的单词

    题目 给定一个字符串,逐个翻转字符串中的每个单词. 示例 1: 输入: "the sky is blue" 输出: "blue is sky the" 示例 2: 输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括. 示例 3: 输入: "a good example" 输出: "exampl

  • Golang使用zlib压缩和解压缩字符串

    在python的时候就习惯使用zlib进行网页压缩. golang下同样使用zlib进行压缩解压缩.  zlib官方给出的方法很简单,这里权当一个补充. zlib.NewWriter() 只能传递 []byte类型数据.   NewWriterLevel 可以传递压缩的等级. package main import ( "bytes" "compress/zlib" "fmt" "io" ) func main() { var

  • Golang压缩Jpeg图片和PNG图片的操作

    博主一直在维护一个导出PDF的服务,但是这个服务导出的PDF文件是真的巨大,动辄就上百MB.这里面主要是图片占据了大多数体积,所以考虑在导出前压缩一下图片. Jpeg的图片压缩是很好做的,因为jpeg这个协议本身就支持调整图片质量的.在golang中,我们只需要使用标准库的image/jpeg,将图片从二进制数据解码后,降低质量再编码为二进制数据即可实现压缩.而且质量和压缩比例相对而言还不错. func compressImageResource(data []byte) []byte { im

  • C#图片切割、图片压缩、缩略图生成代码汇总

    本文为大家整理了C#图片切割.图片压缩.缩略图生成的实现代码,大家可以收藏,方便以后使用,具体内容如下 /// 图片切割函数 /// </summary> /// <param name="sourceFile">原始图片文件</param> /// <param name="xNum">在X轴上的切割数量</param> /// <param name="yNum">在Y轴

  • android调用原生图片裁剪后图片尺寸缩放的解决方法

    在安卓开发中,如果对拍照后的图片进行图片裁剪,如果是调用系统的裁剪,如下: /* * 裁剪图片 */ private void cropPhoto() { Intent intent = new Intent("com.android.camera.action.CROP"); Uri uri = Uri.parse("file://" + picSavePath); intent.setDataAndType(uri, "image/*");

  • Asp.net开发之webform图片水印和图片验证码的实现方法

    两者都需要引入命名空间:using System.Drawing; 一.图片水印 前台Photoshuiyin.aspx代码: <div> <asp:FileUpload ID="FileUpload1" runat="server" /> <asp:Button ID="Button1" runat="server" Text="上传" /><br /> &

  • php等比例缩放图片及剪切图片代码分享

    php等比例缩放图片及剪切图片代码分享 /** * 图片缩放函数(可设置高度固定,宽度固定或者最大宽高,支持gif/jpg/png三种类型) * Author : Specs * * @param string $source_path 源图片 * @param int $target_width 目标宽度 * @param int $target_height 目标高度 * @param string $fixed_orig 锁定宽高(可选参数 width.height或者空值) * @ret

  • Android实现拍照、选择图片并裁剪图片功能

    一. 实现拍照.选择图片并裁剪图片效果 按照之前博客的风格,首先看下实现效果. 二. uCrop项目应用 想起之前看到的Yalantis/uCrop效果比较绚,但是研究源码之后发现在定制界面方面还是有一点的限制,于是在它的基础上做了修改Android-Crop,把定制界面独立出来,让用户去自由设置.下图为使用Android-Crop实现的模仿微信选择图片并裁剪Demo. 三. 实现思路 比较简单的选择设备图片裁剪,并将裁剪后的图片保存到指定路径: 调用系统拍照,将拍照图片保存在SD卡,然后裁剪图

  • PHP图片处理之图片旋转和图片翻转实例

    图片的旋转和翻转也是Web项目中比较常见的功能,但这是两个不同的概念,图片的旋转是按特定的角度来转动图片,而图片的翻转则是将图片的内容按特定的方向对调.图片翻转需要自己编写函数来实现,而旋转图片则可以直接借助GD库中提供的imagerotate()函数完成.该函数的原型如下所示: 复制代码 代码如下: resource  imagerotate(resource src_im ,    float angle,    int bgd_color    [,int ignore_transpatr

  • PHP图片处理之图片背景、画布操作

    像验证码或根据动态数据生成统计图标,以及前面介绍的一些GD库操作等都属于动态绘制图像.而在web开发中,也会经常去处理服务器中已存在的图片.例如,根据一些需求对图片进行缩放.加水印.裁剪.翻转和旋转等改图的操作.在web应用中,经常使用的图片格式有GIF.JPEG和PNG中的一种或几种,当然GD库也可以处理其他格式的图片,但都很少用到.所以安装GD库时,至少安装GIF.JPEG或PNG三种格式中的一种. 在前面介绍的画布管理中,使用imagecreate()和imageCreateTrueCol

  • C#实现字符串与图片的Base64编码转换操作示例

    本文实例讲述了C#实现字符串与图片的Base64编码转换操作.分享给大家供大家参考,具体如下: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using System.Drawing

  • Android图片添加水印图片并把图片保存到文件存储的实现代码

    具体代码如下所示: package zhangphil.test; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.os.Bundle; import android.os.Environment; import android.support.annotation.Nullable; import andro

随机推荐