Go发布自定义包

news2024/11/24 8:38:28

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, "压缩成功")
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2086835.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利用通义灵码实现我的第一次开源贡献

作者&#xff1a;重庆邮电大学计算机学院李逸雄 结缘开源 最早了解开源是从学校的兴趣组织开始的。2023 年 10 月 21 日&#xff0c;openSUSE 亚洲峰会在我们学校召开&#xff0c;这次会议汇聚了许多来自 openSUSE 社区贡献者以及对开源感兴趣的爱好者们。我第一次知道有这么…

postman使用记录

输入密码&#xff0c;地址 然后输入格式为json 在 body里写入传参 然后点击发送即可

裸机:SD卡启动详解

内存和外存的区别 内存和外存在计算机系统中扮演着不同的角色&#xff0c;它们之间存在显著的差异。以下是内存和外存之间几个主要方面的区别&#xff1a; 存储特性与易失性 内存&#xff08;Memory&#xff09;&#xff1a;通常指的是随机存取存储器&#xff08;RAM&#x…

Java面试题·区别题·JavaSE部分

系列文章目录 总章 Java区别题 文章目录 系列文章目录前言private/默认/protected/public权限修饰符的区别&和&&区别和联系&#xff0c;I和II区别和联系if和switch的不同之处和equals的区别和联系数组做形参和可变参数做形参联系和区别接口和抽象类的异同之处面向…

Android设备如何异地访问本地部署的code-server随时随地远程开发

文章目录 前言1.Ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址6.结语 前言 本文主要介绍如何在Linux Ubuntu系统安装code-server&#xff0c;并结合cpolar内网穿透工具配置公网地址&#xff0c;轻松实现使用安…

企业级开发——Git使用

一 Git介绍 1 什么是版本控制 版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理&#xff0c;是软件配置管理的核心思想之一。 2 为什么使用版本控制 采用手动复制的方式管理版本&#xff0c;会造成版本管理混乱&#xff0c;而通过版本控制管…

测试使用开源异构迁移工具dbswitch

dbswitch: 异构数据库迁移同步(搬家)工具 (base) rootnode13:~# cat /etc/issue Ubuntu 20.04.5 LTS \n \l (base) rootnode13:~# curl -k -sSL https://gitee.com/dromara/dbswitch/attach_files/1878800/download > /tmp/dbswitch_install.sh && bash /tmp/dbsw…

地级市地理相邻矩阵(地级市名称版、行政区划代码版)

地级市地理相邻矩阵&#xff08;地级市名称版、行政区划代码版&#xff09; 范围&#xff1a;294个地级市 格式&#xff1a;地级市名称版、行政区划代码版 说明&#xff1a;数据为同省下城市之间的相邻矩阵&#xff0c;表示同一省份内各个城市相互之间邻近关系。如果同一省份…

VTK+Qt+Cmake+VS的环境搭建

VTKQtCmakeVS的环境搭建 一、准备工作二、VTK源码安装过程三、错误排查四、Cmake中引用VTK五、代码示例 本文的主要内容&#xff1a;简单介绍如何使用Cmake编译安装VTK源代码&#xff1b;如何配置VTK在Qt中的使用环境&#xff1b;如何以VS作为IDE在C下使用QtVTK。 哪些人适合阅…

android studio .android和.gradle迁移到其他盘

操作 可以看到gradle和android占用不小 .android 将C盘的.android迁移到D盘 切换到.android下面的avd目录&#xff0c;修改ini文件 .gradle 将.gradle复制到D盘 在Android studio的文件夹下面新建一个文件夹&#xff0c;我这里命名androidcache。接着在Android studio的…

「bug」nvitop ERROR: Failed to initialize curses

nvitop 作为一个优秀个 Nvidia显卡查询库&#xff0c;简单易用且显示信息十分丰富&#xff0c;相比 Nvidia-smi 更方便&#xff0c;简直是每个 开发人员必备的库&#xff0c;安装也十分方便&#xff0c;直接采用 pip install nvitop 即可&#xff0c;调用的时候也是直接在 Term…

51单片机波特率的计算方法

通过51单片机的波特率&#xff0c;来计算定时器的初始值。 定时器的溢出率公式 &#xff1a; 1 / ov 1/f * 12 *&#xff08;256 - init) &#xff08;ov为溢出率&#xff0c;溢出频率&#xff1b; init 为初始值; f为时钟频率&#xff0c; 比如12M或者11.0592M等&#xff09…

自己开发完整项目一、登录注册功能-01

一、创建spingboot项目框架 1.首先创建一个空的项目作为父项目&#xff0c;之后的所有都在此基础上创建模块进行开发。 2.创建负责登录注册功能模块 二、启动项目 1.出现如下错误&#xff0c;代表着端口号被占用&#xff0c;这个时候&#xff0c;我们可以进行端口号的修改。 …

如何从笔记本电脑或台式电脑恢复丢失的照片和视频

意外删除或丢失笔记本电脑或 PC 上的照片和视频是一个常见问题。不用担心&#xff0c;在此博客中&#xff0c;我们解释了从笔记本电脑或 PC 恢复丢失的照片和视频的各种方法。专业的数据恢复软件&#xff0c;例如奇客 数据恢复工具&#xff0c;可以帮助用户找回丢失的文件。 提…

微信小程序uni :class不支持xxx语法

问题代码&#xff1a; <view class"cellTop"><view>{{list.payTime}}</view><view :class"payStatusClass${list.payStatus}">{{payStatusDe[list.payStatus]}}</view></view> .payStatusClass1{color: rgb(246, 122,…

传输层协议-UDP数据报

UDP协议的特点 面向数据报&#xff0c;无连接&#xff0c;不可靠&#xff0c;全双工 面向数据报&#xff1a;是指该协议在传输数据的时候使用的是数据报&#xff1b; 无连接&#xff1a;指的是发送数据不需要两个进程连接在一起&#xff0c;类似生活中我们发送短信&#xff0…

代码随想录跟练第九天——LeetCode 232.用栈实现队列、225. 用队列实现栈、20. 有效的括号、1047. 删除字符串中的所有相邻重复项

拔了智齿后&#xff0c;好久没有总结了&#xff0c;先补一点 232.用栈实现队列 力扣题目链接(opens new window) 使用栈实现队列的下列操作&#xff1a; push(x) -- 将一个元素放入队列的尾部。 pop() -- 从队列首部移除元素。 peek() -- 返回队列首部的元素。 empty() -- …

解锁App推广新姿势,Xinstall带你玩转投放查看

在移动互联网时代&#xff0c;App推广和运营成为了各大企业和开发者关注的焦点。然而&#xff0c;在这个过程中&#xff0c;推广者常常面临一些痛点&#xff0c;比如无法实时查看投放效果、数据不透明、难以精准定位目标用户等。这些问题不仅影响了推广效果&#xff0c;还可能导…

这些网络设备知名厂商你都不知道?白干这行了

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 你们好啊&#xff0c;我的网工朋友。 信息技术的快速发展&#xff0c;网络行业已经成为推动全球经济和社会进步的重要力量之一。无论是企业还是个…

CI/CD实践(五)Jenkins Docker 自动化构建部署Node服务

微服务CI/CD实践系列&#xff1a; 微服务CI/CD实践&#xff08;一&#xff09;环境准备及虚拟机创建 微服务CI/CD实践&#xff08;二&#xff09;服务器先决准备 微服务CI/CD实践&#xff08;三&#xff09;gitlab部署及nexus3部署 微服务CI/CD实践&#xff08;四&#xff09…