完成基础版GIF生成,待调优

This commit is contained in:
白茶清欢 2021-11-29 15:30:06 +08:00
parent 6601f4e3c7
commit e2ebb2ad75
9 changed files with 261 additions and 0 deletions

29
image/define.go Normal file
View File

@ -0,0 +1,29 @@
// Package image ...
//
// Description : image ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2021-11-29 2:27 下午
package image
const (
// TypeGIF ...
TypeGIF = "gif"
// TypePNG ...
TypePNG = "png"
// TypeJPG ...
TypeJPG = "jpg"
// TypeJPEG ...
TypeJPEG = "jpeg"
)
// ImgInfo ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2:45 下午 2021/11/29
type ImgInfo struct {
Path string
Type string
}

195
image/gif.go Normal file
View File

@ -0,0 +1,195 @@
// Package image ...
//
// Description : GIF相关操作
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2021-11-29 12:43 下午
package image
import (
"errors"
"image"
"image/color"
"image/color/palette"
"image/draw"
"image/gif"
"image/jpeg"
"image/png"
"os"
"git.zhangdeman.cn/zhangdeman/gopkg/util"
)
var (
// GIF ...
GIF *GIFConvert
)
func init() {
GIF = &GIFConvert{}
}
// GIFConvert gif转换
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 12:44 下午 2021/11/29
type GIFConvert struct {
}
// Generate 生成
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2:13 下午 2021/11/29
func (g *GIFConvert) Generate(sourceImageList []string, savePath string) error {
if len(sourceImageList) == 0 {
return errors.New("source image list is empty")
}
var (
err error
formatImgList []ImgInfo
disposals []byte
images []*image.Paletted
// 播放速度设置 , 100 次多少秒
delays []int
)
if formatImgList, err = g.checkImageType(sourceImageList); nil != err {
return err
}
for _, imgInfo := range formatImgList {
img, gErr := g.loadImage(imgInfo)
if nil != gErr {
return gErr
}
cp := g.getPalette(img)
//cp:=append(palette.WebSafe,color.Transparent)
disposals = append(disposals, gif.DisposalBackground) //透明图片需要设置
p := image.NewPaletted(image.Rect(0, 0, 640, 996), cp)
draw.Draw(p, p.Bounds(), img, image.ZP, draw.Src)
images = append(images, p)
delays = append(delays, 100)
}
gifInstance := &gif.GIF{
Image: images,
Delay: delays,
LoopCount: -1,
Disposal: disposals,
}
f, fErr := os.Create(savePath)
if fErr != nil {
return fErr
}
defer func() { _ = f.Close() }()
return gif.EncodeAll(f, gifInstance)
}
// checkImageType 检测图片类型
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2:16 下午 2021/11/29
func (g *GIFConvert) checkImageType(sourceImageList []string) ([]ImgInfo, error) {
result := make([]ImgInfo, 0)
for _, item := range sourceImageList {
imgType := util.GetFileType(item)
if len(imgType) == 0 {
return result, errors.New(item + " parse img type fail!")
}
result = append(result, ImgInfo{
Path: item,
Type: imgType,
})
}
return result, nil
}
// loadImage 加载图片内容
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2:52 下午 2021/11/29
func (g *GIFConvert) loadImage(imgInfo ImgInfo) (image.Image, error) {
var (
err error
f *os.File
)
if f, err = os.Open(imgInfo.Path); nil != err {
return nil, err
}
defer func() {
_ = f.Close()
}()
switch imgInfo.Type {
case TypePNG:
if img, imgErr := png.Decode(f); nil != err {
return nil, imgErr
} else {
return img, nil
}
case TypeJPEG:
fallthrough
case TypeJPG:
if img, imgErr := jpeg.Decode(f); nil != err {
return nil, imgErr
} else {
return img, nil
}
case TypeGIF:
if img, imgErr := gif.Decode(f); nil != err {
return nil, imgErr
} else {
return img, nil
}
default:
return nil, errors.New(imgInfo.Type + " is not support!")
}
}
// getPalette ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 3:01 下午 2021/11/29
func (g *GIFConvert) getPalette(m image.Image) color.Palette {
p := color.Palette{color.RGBA{0x00, 0x00, 0x00, 0x00}}
p9 := color.Palette(palette.Plan9)
b := m.Bounds()
black := false
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
c := m.At(x, y)
cc := p9.Convert(c)
if cc == p9[0] {
black = true
}
if g.isInPalette(p, cc) == -1 {
p = append(p, cc)
}
}
}
if len(p) < 256 && black == true {
p[0] = color.RGBA{0x00, 0x00, 0x00, 0x00} // transparent
p = append(p, p9[0])
}
return p
}
// isInPalette ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 3:01 下午 2021/11/29
func (g *GIFConvert) isInPalette(p color.Palette, c color.Color) int {
ret := -1
for i, v := range p {
if v == c {
return i
}
}
return ret
}

24
image/gif_test.go Normal file
View File

@ -0,0 +1,24 @@
// Package image ...
//
// Description : image ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2021-11-29 3:10 下午
package image
import (
"fmt"
"testing"
)
// TestGIFConvert_Generate ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 3:10 下午 2021/11/29
func TestGIFConvert_Generate(t *testing.T) {
sourceImgList := []string{"./test/1.jpeg", "./test/2.jpeg", "./test/3.jpeg", "./test/4.jpeg"}
savePath := "./test/test.gif"
fmt.Println(GIF.Generate(sourceImgList, savePath))
}

BIN
image/test/1.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

BIN
image/test/2.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

BIN
image/test/3.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
image/test/4.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 KiB

BIN
image/test/test.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

View File

@ -85,3 +85,16 @@ func IsFileExist(filePath string) (bool, bool) {
f, err := os.Stat(filePath) f, err := os.Stat(filePath)
return nil == err || os.IsExist(err), (nil == err || os.IsExist(err)) && !f.IsDir() return nil == err || os.IsExist(err), (nil == err || os.IsExist(err)) && !f.IsDir()
} }
// GetFileType 获取文件类型
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2:30 下午 2021/11/29
func GetFileType(fileName string) string {
fileArr := strings.Split(fileName, ".")
if len(fileArr) < 2 {
return ""
}
return strings.ToLower(fileArr[len(fileArr)-1])
}