1、初始化go.mod
- go mod init github.com/xumeng03/images
复制代码 2、编写包内容
这里只是一个简单的压缩jpg/jpeg图片例子,代码参考 https://github.com/disintegration/imaging
2.1、fs.go
- package images
- import (
- "image"
- "io"
- "os"
- "path"
- "strings"
- )
- type FileSystem interface {
- Create(string) (io.WriteCloser, error)
- Open(string) (io.ReadCloser, error)
- }
- type LocalFileSystem struct{}
- func (fs LocalFileSystem) Create(name string) (io.WriteCloser, error) {
- return os.Create(name)
- }
- func (fs LocalFileSystem) Open(name string) (io.ReadCloser, error) {
- return os.Open(name)
- }
- var fs FileSystem = LocalFileSystem{}
- func Open(filename string) (image.Image, error) {
- file, err := fs.Open(filename)
- if err != nil {
- return nil, err
- }
- defer file.Close()
- return Decode(file)
- }
- func Close(img image.Image, filename string, quality int) error {
- file, err := fs.Create(filename)
- if err != nil {
- return err
- }
- ext := path.Ext(filename)
- err = Encode(file, img, strings.ReplaceAll(ext, ".", ""), quality)
- if err != nil {
- return err
- }
- err = file.Close()
- return err
- }
复制代码 2.2、image.go
- package images
- import (
- "fmt"
- "image"
- "image/jpeg"
- _ "image/jpeg"
- _ "image/png"
- "io"
- )
- func Decode(reader io.Reader) (image.Image, error) {
- // 数据写入 PipeWriter 对象后,可以通过相应的 PipeReader 对象进行读取;
- pr, pw := io.Pipe()
- // 创建了一个新的 io.Reader 对象,这个对象能够将从其读取的数据同时写入到另一个 io.Writer 中(如同包装类)
- reader = io.TeeReader(reader, pw)
- done := make(chan struct{})
- var orient orientation
- go func() {
- defer close(done)
- orient = readOrientation(pr)
- io.Copy(io.Discard, pr)
- }()
- img, _, err := image.Decode(reader)
- pw.Close()
- <-done
- fmt.Println(orient)
- if err != nil {
- return nil, err
- }
- return img, nil
- }
- func Encode(w io.Writer, img image.Image, t string, quality int) error {
- switch t {
- case "jpg":
- fallthrough
- case "jpeg":
- if nrgba, ok := img.(*image.NRGBA); ok && nrgba.Opaque() {
- rgba := &image.RGBA{
- Pix: nrgba.Pix,
- Stride: nrgba.Stride,
- Rect: nrgba.Rect,
- }
- return jpeg.Encode(w, rgba, &jpeg.Options{Quality: quality})
- }
- return jpeg.Encode(w, img, &jpeg.Options{Quality: quality})
- default:
- println("type error!")
- return nil
- }
- }
复制代码 2.3、exif.go
- package images
- import (
- "encoding/binary"
- "io"
- )
- type orientation int
- const (
- // 方向未指定
- orientationUnspecified = 0
- // 正常方向
- orientationNormal = 1
- // 需水平翻转
- orientationFlipH = 2
- // 需旋转180度
- orientationRotate180 = 3
- // 需垂直翻转
- orientationFlipV = 4
- // 需对角线翻转(左上到右下)
- orientationTranspose = 5
- // 需逆时针旋转270度
- orientationRotate270 = 6
- // 需对角线翻转(右上到左下)
- orientationTransverse = 7
- // 需顺时针旋转90度
- orientationRotate90 = 8
- )
- const (
- // Start Of Image:表示 JPEG 图片流的起始
- markerSOI = 0xffd8
- // Application Segment 1:表示 APP1 区块,EXIF 信息通常存储在 APP1 区块内
- markerAPP1 = 0xffe1
- // Exif Header:表示 APP1 区块确实包含了 EXIF 信息(紧跟在 APP1 区块标识后),且后面通常跟着两个填充字节
- exifHeader = 0x45786966
- // Big Endian byte order mark:如果 EXIF 段使用大端字节序,那么其字节序标记为 'MM' (0x4D4D),即(高位字节排在前)
- byteOrderBE = 0x4d4d
- // Little Endian byte order mark:如果 EXIF 段使用小端字节序,那么其字节序标记为 'II' (0x4949),即(低位字节排在前)
- byteOrderLE = 0x4949
- // Orientation Tag:表示图像的方向
- orientationTag = 0x0112
- )
- func readOrientation(reader io.Reader) orientation {
- // 检查 JPEG 开始标记(PNG 和 GIF 等格式不是传统意义上的摄影,图像元数据一般不包括拍摄方向信息。处理这些图像文件时,通常没有必要读取或调整图像方向)
- var soi uint16
- if binary.Read(reader, binary.BigEndian, &soi) != nil {
- return orientationUnspecified
- }
- if soi != markerSOI {
- return orientationUnspecified
- }
- for {
- var marker, size uint16
- if err := binary.Read(reader, binary.BigEndian, &marker); err != nil {
- return orientationUnspecified
- }
- if err := binary.Read(reader, binary.BigEndian, &size); err != nil {
- return orientationUnspecified
- }
- // 检查是否是有效的 JPEG 标记
- if marker>>8 != 0xff {
- return orientationUnspecified
- }
- // 检查是否为 APP1 标记
- if marker == markerAPP1 {
- break
- }
- // 对于任何 JPEG 数据块,其报告的大小应至少为2字节
- if size < 2 {
- return orientationUnspecified
- }
- // 这里的减2表示减去size本身占用的2字节(size表示的是从size开始这个段还有几个字节)
- if _, err := io.CopyN(io.Discard, reader, int64(size-2)); err != nil {
- return orientationUnspecified
- }
- }
- // 检查 exifHeader 标记
- var header uint32
- if err := binary.Read(reader, binary.BigEndian, &header); err != nil {
- return orientationUnspecified
- }
- if header != exifHeader {
- return orientationUnspecified
- }
- if _, err := io.CopyN(io.Discard, reader, 2); err != nil {
- return orientationUnspecified
- }
- // 从文件中读取的字节序标识
- var byteOrderTag uint16
- var byteOrder binary.ByteOrder
- if err := binary.Read(reader, binary.BigEndian, &byteOrderTag); err != nil {
- return orientationUnspecified
- }
- switch byteOrderTag {
- case byteOrderBE:
- byteOrder = binary.BigEndian
- case byteOrderLE:
- byteOrder = binary.LittleEndian
- default:
- return orientationUnspecified
- }
- if _, err := io.CopyN(io.Discard, reader, 2); err != nil {
- return orientationUnspecified
- }
- // 跳过 exif 段
- var offset uint32
- if err := binary.Read(reader, binary.BigEndian, &offset); err != nil {
- return orientationUnspecified
- }
- if offset < 8 {
- // 在 TIFF 格式中,如果 offset 小于 8(byteOrderTag、填充字节、offset字节),那么它指向的位置是不合逻辑的,表明可能是一个损坏或非法格式的文件。
- return orientationUnspecified
- }
- if _, err := io.CopyN(io.Discard, reader, int64(offset-8)); err != nil {
- return orientationUnspecified
- }
- // 获取标签数
- var numTags uint16
- if err := binary.Read(reader, byteOrder, &numTags); err != nil {
- return orientationUnspecified
- }
- for i := 0; i < int(numTags); i++ {
- var tag uint16
- if err := binary.Read(reader, binary.BigEndian, &tag); err != nil {
- return orientationUnspecified
- }
- if tag != orientationTag {
- // 10 = 2(数据类型)+ 4(计数)+ 4(值或值偏移量)
- if _, err := io.CopyN(io.Discard, reader, 10); err != nil {
- return orientationUnspecified
- }
- continue
- }
- // 跳过2字节(数据类型)+ 4字节(计数)
- if _, err := io.CopyN(io.Discard, reader, 6); err != nil {
- return orientationUnspecified
- }
- // 读取方向值(在 TIFF 中,实际的方向值可以直接存放在“值或值偏移量”的位置,并且仅占用前两字节,剩余的两字节则不会包含任何重要信息)
- var direction uint16
- if err := binary.Read(reader, binary.BigEndian, &direction); err != nil {
- return orientationUnspecified
- }
- if direction < 1 || direction > 8 {
- // EXIF 规范定义的图像方向值应该在 1 到 8 之间
- return orientationUnspecified
- }
- return orientation(direction)
- }
- return orientationUnspecified
- }
复制代码 3、测试
3.1、fs_test.go
- package images
- import (
- "fmt"
- "testing"
- )
- func TestOpen(t *testing.T) {
- fileName := "test.jpeg"
- _, err := Open(fileName)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Println(fileName, "读取成功")
- }
- func TestClose(t *testing.T) {
- fileName := "test.jpeg"
- quality := 50
- img, err := Open(fileName)
- if err != nil {
- fmt.Println(err)
- return
- }
- err = Close(img, "compress_"+fileName, quality)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Println(fileName, "保存成功")
- }
复制代码 4、发布
创建一个新的tag:v0.0.1
5、使用
- go get -u github.com/xumeng03/images
复制代码- package main
- import (
- "fmt"
- "github.com/xumeng03/images"
- )
- func main() {
- fileName := "test.jpeg"
- quality := 50
- img, err := images.Open(fileName)
- if err != nil {
- fmt.Println(err)
- return
- }
- err = images.Close(img, "compress_"+fileName, quality)
- if err != nil {
- fmt.Println(err)
- return
- }
- fmt.Println(fileName, "压缩成功")
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |