Go发布自定义包

1、初始化go.mod

go mod init github.com/xumeng03/images

2、编写包内容

这里只是一个简单的压缩jpg/jpeg图片例子,代码参考 https://github.com/disintegration/imaging

2.1、fs.go

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

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

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

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、使用

shell 复制代码
go get -u github.com/xumeng03/images
go 复制代码
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, "压缩成功")
}
相关推荐
Pandaconda3 分钟前
【Golang 面试题】每日 3 题(三十九)
开发语言·经验分享·笔记·后端·面试·golang·go
加油,旭杏7 分钟前
【go语言】变量和常量
服务器·开发语言·golang
行路见知8 分钟前
3.3 Go 返回值详解
开发语言·golang
编程小筑38 分钟前
R语言的编程范式
开发语言·后端·golang
技术的探险家41 分钟前
Elixir语言的文件操作
开发语言·后端·golang
Ai 编码助手1 小时前
Golang 中强大的重试机制,解决瞬态错误
开发语言·后端·golang
齐雅彤2 小时前
Lisp语言的区块链
开发语言·后端·golang
齐雅彤2 小时前
Lisp语言的循环实现
开发语言·后端·golang
梁雨珈2 小时前
Lisp语言的物联网
开发语言·后端·golang
邓熙榆3 小时前
Logo语言的网络编程
开发语言·后端·golang