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, "压缩成功")
}
相关推荐
hkNaruto7 小时前
【P2P】【Go】采用go语言实现udp hole punching 打洞 传输速度测试 ping测试
golang·udp·p2p
入 梦皆星河7 小时前
go中常用的处理json的库
golang
海绵波波1079 小时前
Gin-vue-admin(2):项目初始化
vue.js·golang·gin
每天写点bug9 小时前
【go每日一题】:并发任务调度器
开发语言·后端·golang
一个不秃头的 程序员9 小时前
代码加入SFTP Go ---(小白篇5)
开发语言·后端·golang
基哥的奋斗历程10 小时前
初识Go语言
开发语言·后端·golang
ZVAyIVqt0UFji16 小时前
go-zero负载均衡实现原理
运维·开发语言·后端·golang·负载均衡
唐墨12320 小时前
golang自定义MarshalJSON、UnmarshalJSON 原理和技巧
开发语言·后端·golang
老大白菜21 小时前
FastAPI vs Go 性能对比分析
开发语言·golang·fastapi
千年死缓1 天前
golang结构体转map
开发语言·后端·golang