1 Star 0 Fork 0

leonxiong/xtool

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
image.go 14.69 KB
一键复制 编辑 原始数据 按行查看 历史
leonxiong 提交于 2024-12-05 09:49 . update xtool
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
package ximage
import (
"bytes"
"errors"
"fmt"
"gitee.com/xlm516/xtool/dbg"
xfile "gitee.com/xlm516/xtool/file"
xstring "gitee.com/xlm516/xtool/string"
"gitee.com/xlm516/xtool/sys"
"github.com/disintegration/imaging"
"github.com/foobaz/lossypng/lossypng"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/rwcarlsen/goexif/exif"
"golang.org/x/image/bmp"
"image"
"image/color"
"image/draw"
"image/gif"
"image/jpeg"
"image/png"
"io"
"io/ioutil"
"os"
"strings"
)
type RotateType int
const (
orientationUnspecified RotateType = iota
orientationNormal = 1
orientationFlipH = 2
orientationRotate180 = 3
orientationFlipV = 4
orientationTranspose = 5
orientationRotate270 = 6
orientationTransverse = 7
orientationRotate90 = 8
)
type ImageEx struct {
width int
height int
fontSize float64
img *image.NRGBA
orgImage image.Image
currFont *truetype.Font
}
type Circle struct {
p image.Point
r int
}
func (c *Circle) ColorModel() color.Model {
return color.AlphaModel
}
func (c *Circle) Bounds() image.Rectangle {
return image.Rect(c.p.X-c.r, c.p.Y-c.r, c.p.X+c.r, c.p.Y+c.r)
}
func (c *Circle) At(x, y int) color.Color {
xx, yy, rr := float64(x-c.p.X)+0.5, float64(y-c.p.Y)+0.5, float64(c.r)
if xx*xx+yy*yy < rr*rr {
return color.Alpha{255}
}
return color.Alpha{0}
}
type DrawTextOption struct {
FontColor color.Color
FontSize float64
}
func NewImageFromImage(fromImage image.Image) *ImageEx {
img := new(ImageEx)
img.width = fromImage.Bounds().Dx()
img.height = fromImage.Bounds().Dy()
img.init()
img.orgImage = fromImage
img.img, _ = fromImage.(*image.NRGBA)
return img
}
func NewImage(width, height int) *ImageEx {
img := new(ImageEx)
img.width = width
img.height = height
img.init()
return img
}
func (me *ImageEx) init() {
me.fontSize = 15
if me.width > 0 && me.height > 0 {
me.img = image.NewNRGBA(image.Rect(0, 0, me.width, me.height))
} else {
me.img = nil
}
}
func (me *ImageEx) DrawBackground(r, g, b int) {
if me.img == nil {
return
}
for x := 0; x < me.width; x++ {
for y := 0; y < me.height; y++ {
me.img.Set(x, y, color.RGBA{uint8(r), uint8(g), uint8(b), 255})
}
}
}
func (me *ImageEx) SetFont(font *truetype.Font) {
me.currFont = font
}
func (me *ImageEx) SetFontFile(filename string) error {
b, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
font, err := freetype.ParseFont(b)
if err != nil {
return err
}
me.currFont = font
return nil
}
func (me *ImageEx) SubImage(x, y, w, h int) image.Image {
if me.img == nil {
return nil
}
return me.img.SubImage(image.Rect(x, y, x+w, y+h))
}
func (me *ImageEx) SubImageByPos(x0, y0, x1, y1 int) image.Image {
if me.img == nil {
return nil
}
return me.img.SubImage(image.Rect(x0, y0, x1, y1))
}
func (me *ImageEx) DrawText(x int, y int, text string, fontColor color.Color, option *DrawTextOption) error {
if me.currFont == nil {
return errors.New("no set truetype font")
}
if me.img == nil {
fmt.Println("image is null")
return errors.New("image is null")
}
c := freetype.NewContext()
c.SetFont(me.currFont)
//c.SetDPI(72)
c.SetFontSize(me.fontSize)
c.SetClip(me.img.Bounds())
c.SetDst(me.img)
fontSize := me.fontSize
if fontColor == nil {
c.SetSrc(image.Black)
} else {
c.SetSrc(image.NewUniform(fontColor))
}
if option != nil {
if option.FontSize > 0 {
c.SetFontSize(option.FontSize)
fontSize = option.FontSize
}
if option.FontColor != nil {
c.SetSrc(image.NewUniform(option.FontColor))
}
}
pt := freetype.Pt(x, y+int(c.PointToFixed(fontSize).Ceil()))
_, err := c.DrawString(text, pt)
if err != nil {
return err
}
return nil
}
func RgbaToNRGBA(rgba image.Image) *image.NRGBA {
bounds := rgba.Bounds()
nrgba := image.NewNRGBA(bounds)
draw.Draw(nrgba, bounds, rgba, bounds.Min, draw.Src)
return nrgba
}
func (me *ImageEx) LoadFromBuffer(buf []byte) (image.Image, error) {
rd := bytes.NewReader(buf)
img, _, err := image.Decode(rd)
if err != nil {
me.img = nil
return nil, err
}
me.orgImage = img
me.img = RgbaToNRGBA(img)
return img, nil
}
func (me *ImageEx) LoadFromFile(filename string) (image.Image, error) {
img, err := me.loadFromFile(filename)
if err != nil {
me.img = nil
return nil, err
}
return img, err
}
func (me *ImageEx) loadFromFile(filename string) (image.Image, error) {
filename = sys.FormatDir(filename)
fh, err := os.Open(filename)
if err != nil {
return nil, err
}
defer fh.Close()
img, _, err := image.Decode(fh)
return img, err
ext := xstring.GetFileExt(filename)
ext = strings.ToLower(ext)
var tmpImage image.Image
switch ext {
case ".bmp":
tmpImage, err = bmp.Decode(fh)
if err != nil {
return nil, err
}
case ".png":
tmpImage, err = png.Decode(fh)
if err != nil {
return nil, err
}
case ".gif":
tmpImage, err = gif.Decode(fh)
if err != nil {
return nil, err
}
case ".jpg", ".jpeg":
tmpImage, err = jpeg.Decode(fh)
if err != nil {
return nil, err
}
default:
return nil, errors.New("not support" + ext)
}
_, ok := tmpImage.(*image.NRGBA)
if ok {
}
return tmpImage, nil
}
func (me *ImageEx) SaveToFile(filename string, imgType string) error {
data := new(bytes.Buffer)
if imgType == "" {
ext := xstring.GetFileExt(filename)
if ext != "" {
ext = strings.ToLower(ext)
ext = ext[1:]
}
}
var img image.Image
img = me.img
if img == nil {
img = me.orgImage
}
if img == nil {
return errors.New("invalid image")
}
switch imgType {
case "jpg", ".jpg":
err := jpeg.Encode(data, img, nil)
if err != nil {
return err
}
case "gif", ".gif":
err := gif.Encode(data, img, nil)
if err != nil {
return err
}
default:
err := png.Encode(data, img)
if err != nil {
return err
}
}
err := ioutil.WriteFile(filename, data.Bytes(), 0666)
return err
}
func (me *ImageEx) SaveToFileJPG(filename string, quality int) error {
data := new(bytes.Buffer)
var opt *jpeg.Options
if quality > 0 {
opt = new(jpeg.Options)
opt.Quality = quality
}
var img image.Image
img = me.img
if img == nil {
img = me.orgImage
}
err := jpeg.Encode(data, img, opt)
if err != nil {
return err
}
err = ioutil.WriteFile(filename, data.Bytes(), 0666)
return err
}
func (me *ImageEx) SaveToBuffer(w io.Writer, imgType string) error {
var err error
var img image.Image
img = me.img
if img == nil {
img = me.orgImage
}
if img == nil {
return errors.New("invalid image")
}
switch imgType {
case "jpg":
err = jpeg.Encode(w, img, nil)
if err != nil {
return err
}
case "gif":
err = gif.Encode(w, img, nil)
if err != nil {
return err
}
default:
err = png.Encode(w, img)
if err != nil {
return err
}
}
return err
}
func (me *ImageEx) GetImage() image.Image {
return me.img
}
func (me *ImageEx) GetImageRGB() *image.NRGBA {
return me.img
}
type DrawImageOption struct {
Opacity float64
Center bool
}
func (me *ImageEx) DrawImage(x, y int, img image.Image, option *DrawImageOption) *image.NRGBA {
if me.img == nil || sys.IsNil(img) {
return nil
}
pos := image.Point{}
var tmpImage *image.NRGBA
Opacity := float64(-1)
Center := false
if option != nil {
Opacity = option.Opacity
if Opacity == 0 {
Opacity = -1
}
Center = option.Center
}
pos.X = x
pos.Y = y
if Opacity >= 0 {
if Center {
tmpImage = imaging.OverlayCenter(me.img, img, Opacity)
} else {
tmpImage = imaging.Overlay(me.img, img, pos, Opacity)
}
} else {
if Center {
tmpImage = imaging.PasteCenter(me.img, img)
} else {
tmpImage = imaging.Paste(me.img, img, pos)
}
}
me.img = tmpImage
return tmpImage
}
func (me *ImageEx) DrawImageByFile(x, y int, imageFile string, option *DrawImageOption) error {
ext := xstring.GetFileExt(imageFile)
ext = strings.ToLower(ext)
srcImage, err := me.loadFromFile(imageFile)
if err != nil {
return err
}
if srcImage != nil {
me.DrawImage(x, y, srcImage, option)
}
return nil
}
func (me *ImageEx) DrawCircle(src image.Image, srcX, srcY, r int, dstX, dstY int) {
if me.img == nil {
return
}
p := image.Point{srcX, srcY}
dstPt := image.Rect(dstX, dstY, dstX+r*2+1, dstY+r*2+1)
draw.DrawMask(me.img, dstPt, src, image.ZP, &Circle{p, r}, image.ZP, draw.Over)
}
func (me *ImageEx) Resize(width, height int) {
me.width = width
me.height = height
me.img = imaging.Resize(me.img, width, height, imaging.Lanczos)
}
func (me *ImageEx) ResizePercent(per int) {
if me.img == nil {
return
}
me.width = me.img.Bounds().Dx()
me.height = me.img.Bounds().Dy()
width := me.width * per / 100
height := me.height * per / 100
me.Resize(width, height)
}
func (me *ImageEx) DrawHLine(color color.Color, x, fromY, toY int) {
if me.img == nil {
return
}
if x >= me.img.Bounds().Dx() {
return
}
if fromY >= me.img.Bounds().Dy() {
return
}
if me.img.Bounds().Dy()-fromY >= toY {
toY = me.img.Bounds().Dy() - fromY
}
// 遍历画每一个点
for y := fromY; y <= toY; y++ {
me.img.Set(x, y, color)
}
}
func (me *ImageEx) DrawVLine(color color.Color, fromX, toX, y int) {
if me.img == nil {
return
}
if me.img == nil {
return
}
if y >= me.img.Bounds().Dy() {
return
}
if fromX >= me.img.Bounds().Dx() {
return
}
if me.img.Bounds().Dx()-fromX >= toX {
toX = me.img.Bounds().Dy() - fromX
}
// 遍历画每一个点
for x := fromX; x <= toX; x++ {
me.img.Set(x, y, color)
}
}
type PutPixel func(x, y int)
func abs(x int) int {
if x >= 0 {
return x
}
return -x
}
func (me *ImageEx) DrawLine(x0, y0, x1, y1 int, brush PutPixel) {
if me.img == nil {
return
}
if brush == nil {
brush = func(x, y int) {
me.img.Set(x, y, color.RGBA{uint8(x), uint8(y), 0, 255})
}
}
dx := abs(x1 - x0)
dy := abs(y1 - y0)
sx, sy := 1, 1
if x0 >= x1 {
sx = -1
}
if y0 >= y1 {
sy = -1
}
err := dx - dy
for {
brush(x0, y0)
if x0 == x1 && y0 == y1 {
return
}
e2 := err * 2
if e2 > -dy {
err -= dy
x0 += sx
}
if e2 < dx {
err += dx
y0 += sy
}
}
}
func Rotate90(img image.Image) *image.NRGBA {
return imaging.Rotate90(img)
}
func Rotate180(img image.Image) *image.NRGBA {
return imaging.Rotate180(img)
}
func Rotate270(img image.Image) *image.NRGBA {
return imaging.Rotate270(img)
}
func ReadOrientation(filename string) RotateType {
file, err := os.Open(filename)
if err != nil {
fmt.Println("failed to open file, err: ", err)
return 0
}
defer file.Close()
x, err := exif.Decode(file)
if err != nil {
//fmt.Println("failed to decode file, err: ", err)
return 0
}
orientation, err := x.Get(exif.Orientation)
if err != nil {
//fmt.Println("failed to get orientation, err: ", err)
return 0
}
orientVal, err := orientation.Int(0)
if err != nil {
fmt.Println("failed to convert type of orientation, err: ", err)
return 0
}
//fmt.Println("the value of photo orientation is :", orientVal)
return RotateType(orientVal)
}
type ImageOption struct {
JPGQuality int
PNGQuality int
CompressSize int64 // 最大图片文件的大小
WaterMark string // 水印
WaterX int
WaterY int
WaterOpacity float64
}
func imageSaveToFile(filename string, img image.Image, imgType string, opt *ImageOption) error {
if opt.WaterMark != "" {
fh, err := os.Open(opt.WaterMark)
if err == nil {
waterImage, _, err := image.Decode(fh)
fh.Close()
if err == nil {
tmpImage := imaging.Overlay(img, waterImage, image.Point{X: opt.WaterX, Y: opt.WaterY}, opt.WaterOpacity)
if tmpImage != nil {
img = tmpImage
}
}
}
}
data := new(bytes.Buffer)
switch imgType {
case "jpg", "jpeg":
var jpgOpt *jpeg.Options
if opt != nil && opt.JPGQuality > 0 {
jpgOpt = new(jpeg.Options)
jpgOpt.Quality = opt.JPGQuality
}
err := jpeg.Encode(data, img, jpgOpt)
if err != nil {
return err
}
case "gif":
err := gif.Encode(data, img, nil)
if err != nil {
return err
}
case "png":
if opt != nil && opt.PNGQuality > 0 {
img = lossypng.Compress(img, lossypng.NoConversion, opt.PNGQuality)
}
err := png.Encode(data, img)
if err != nil {
return err
}
default:
dbg.Dbg("image type: %s\n", imgType)
err := png.Encode(data, img)
if err != nil {
return err
}
}
xfile.Mkdir(xstring.GetFilePath(filename))
err := xfile.WriteFile(filename, data.Bytes(), 0666)
if err != nil {
fmt.Println("imageSaveToFile: ", err)
}
return err
}
func ResizeByWidth(srcFile string, dstFile string, width int, autoRotate bool, opt *ImageOption) (string, error) {
fh, err := os.Open(srcFile)
if err != nil {
return "", err
}
fileSize := int64(0)
st, err := fh.Stat()
if err == nil && st != nil {
fileSize = st.Size()
}
defer func() {
if fh != nil {
fh.Close()
}
}()
img, imgType, err := image.Decode(fh)
fh.Close()
fh = nil
if err != nil {
return "", err
}
rotateInfo := ReadOrientation(srcFile)
if rotateInfo > 0 && autoRotate {
switch rotateInfo {
case orientationRotate270: //90度图片旋转
img = Rotate270(img)
case orientationRotate180:
img = Rotate180(img)
case orientationRotate90:
img = Rotate90(img)
}
}
if img.Bounds().Dx() > width {
x := width
y := int(float64(img.Bounds().Dy()) / float64(img.Bounds().Dx()) * float64(width))
img = imaging.Resize(img, x, y, imaging.Lanczos)
err = imageSaveToFile(dstFile, img, imgType, opt)
if err != nil {
return imgType, err
}
return imgType, nil
} else {
if (opt.CompressSize > 0 && fileSize > opt.CompressSize) || opt.WaterMark != "" {
err = imageSaveToFile(dstFile, img, imgType, opt)
if err != nil {
return imgType, err
}
} else {
xfile.Mkdir(xstring.GetFilePath(dstFile))
err = xfile.CopyFile(dstFile, srcFile)
if err != nil {
return imgType, err
}
}
return imgType, nil
}
}
func SaveToFile(filename string, imgType string, img image.Image) error {
data := new(bytes.Buffer)
if imgType == "" {
ext := xstring.GetFileExt(filename)
if ext != "" {
ext = strings.ToLower(ext)
ext = ext[1:]
}
}
if img == nil {
return errors.New("invalid image")
}
switch imgType {
case "jpg", ".jpg":
err := jpeg.Encode(data, img, nil)
if err != nil {
return err
}
case "gif", ".gif":
err := gif.Encode(data, img, nil)
if err != nil {
return err
}
default:
err := png.Encode(data, img)
if err != nil {
return err
}
}
err := xfile.WriteFile(filename, data.Bytes(), 0666)
return err
}
func SaveToBuffer(imgType string, img image.Image) ([]byte, error) {
data := new(bytes.Buffer)
if img == nil {
return nil, errors.New("invalid image")
}
switch imgType {
case "jpg", ".jpg":
err := jpeg.Encode(data, img, nil)
if err != nil {
return nil, err
}
case "gif", ".gif":
err := gif.Encode(data, img, nil)
if err != nil {
return nil, err
}
default:
err := png.Encode(data, img)
if err != nil {
return nil, err
}
}
return data.Bytes(), nil
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/xlm516/xtool.git
git@gitee.com:xlm516/xtool.git
xlm516
xtool
xtool
7c1eb0e7fdb5

搜索帮助

0d507c66 1850385 C8b1a773 1850385