农妇山泉一亩田 发表于 2024-8-30 23:52:41

Go发布自定义包

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
https://i-blog.csdnimg.cn/direct/0aa11113a83d4f5993a9ed6dbfc355d3.png
https://i-blog.csdnimg.cn/direct/c03db389574344829f4f3a481ec52829.png
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企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Go发布自定义包