Create & Init Project...
This commit is contained in:
65
app/interface/main/creative/dao/drawimg/BUILD
Normal file
65
app/interface/main/creative/dao/drawimg/BUILD
Normal file
@ -0,0 +1,65 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"dao.go",
|
||||
"draw.go",
|
||||
"freetype.go",
|
||||
"gaussian.go",
|
||||
"imgutil.go",
|
||||
],
|
||||
importpath = "go-common/app/interface/main/creative/dao/drawimg",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/interface/main/creative/conf:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//vendor/github.com/golang/freetype/raster:go_default_library",
|
||||
"//vendor/github.com/golang/freetype/truetype:go_default_library",
|
||||
"//vendor/golang.org/x/image/font:go_default_library",
|
||||
"//vendor/golang.org/x/image/math/fixed:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"dao_test.go",
|
||||
"draw_test.go",
|
||||
"freetype_test.go",
|
||||
"imgutil_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
rundir = ".",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//app/interface/main/creative/conf:go_default_library",
|
||||
"//vendor/github.com/bouk/monkey:go_default_library",
|
||||
"//vendor/github.com/golang/freetype/raster:go_default_library",
|
||||
"//vendor/github.com/golang/freetype/truetype:go_default_library",
|
||||
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
|
||||
"//vendor/golang.org/x/image/font:go_default_library",
|
||||
"//vendor/golang.org/x/image/math/fixed:go_default_library",
|
||||
],
|
||||
)
|
75
app/interface/main/creative/dao/drawimg/dao.go
Normal file
75
app/interface/main/creative/dao/drawimg/dao.go
Normal file
@ -0,0 +1,75 @@
|
||||
package drawimg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go-common/app/interface/main/creative/conf"
|
||||
"go-common/library/log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Dao define
|
||||
type Dao struct {
|
||||
// conf
|
||||
c *conf.Config
|
||||
// watermark
|
||||
dw *DrawImg
|
||||
}
|
||||
|
||||
// New init dao
|
||||
func New(c *conf.Config) (d *Dao) {
|
||||
if !isExist(c.WaterMark.FontFile) {
|
||||
log.Error("font file not exist")
|
||||
return
|
||||
}
|
||||
if !isExist(c.WaterMark.UnameMark) {
|
||||
log.Error("uname image file not exist")
|
||||
return
|
||||
}
|
||||
if !isExist(c.WaterMark.UIDMark) {
|
||||
log.Error("uid image file not exist")
|
||||
return
|
||||
}
|
||||
d = &Dao{
|
||||
c: c,
|
||||
dw: NewDrawImg(c.WaterMark.FontFile, c.WaterMark.FontSize),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Make create watermark.
|
||||
func (d *Dao) Make(c context.Context, mid int64, text string, isUname bool) (dw *DrawImg, err error) {
|
||||
var src string
|
||||
if isUname {
|
||||
src = d.c.WaterMark.UnameMark
|
||||
} else {
|
||||
src = d.c.WaterMark.UIDMark
|
||||
}
|
||||
img, err := d.dw.ReadSrcImg(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if img == nil {
|
||||
return
|
||||
}
|
||||
d.dw.srcImg = img
|
||||
midStr := strconv.FormatInt(mid, 10)
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
file := d.c.WaterMark.SaveImg + midStr + "-" + timestamp + ".png"
|
||||
if err = d.dw.Draw(text, file, isUname); err != nil {
|
||||
log.Error("d.dw.Draw error(%v)", err)
|
||||
return
|
||||
}
|
||||
dw = &DrawImg{
|
||||
CanvasWidth: d.dw.CanvasWidth,
|
||||
CanvasHeight: d.dw.CanvasHeight,
|
||||
File: file,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil || os.IsExist(err)
|
||||
}
|
58
app/interface/main/creative/dao/drawimg/dao_test.go
Normal file
58
app/interface/main/creative/dao/drawimg/dao_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package drawimg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"github.com/smartystreets/goconvey/convey"
|
||||
"go-common/app/interface/main/creative/conf"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
d *Dao
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if os.Getenv("DEPLOY_ENV") != "" {
|
||||
flag.Set("app_id", "main.archive.creative")
|
||||
flag.Set("conf_token", "96b6a6c10bb311e894c14a552f48fef8")
|
||||
flag.Set("tree_id", "2305")
|
||||
flag.Set("conf_version", "docker-1")
|
||||
flag.Set("deploy_env", "uat")
|
||||
flag.Set("conf_host", "config.bilibili.co")
|
||||
flag.Set("conf_path", "/tmp")
|
||||
flag.Set("region", "sh")
|
||||
flag.Set("zone", "sh001")
|
||||
} else {
|
||||
flag.Set("conf", "../../cmd/creative.toml")
|
||||
}
|
||||
flag.Parse()
|
||||
if err := conf.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
d = New(conf.Conf)
|
||||
d = &Dao{
|
||||
c: conf.Conf,
|
||||
dw: &di,
|
||||
}
|
||||
m.Run()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func TestDrawimgMake(t *testing.T) {
|
||||
convey.Convey("Make", t, func(ctx convey.C) {
|
||||
var (
|
||||
c = context.Background()
|
||||
mid = int64(1)
|
||||
text = "123"
|
||||
isUname = true
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
_, err := d.Make(c, mid, text, isUname)
|
||||
ctx.Convey("Then err should be nil.dw should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(err, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
171
app/interface/main/creative/dao/drawimg/draw.go
Normal file
171
app/interface/main/creative/dao/drawimg/draw.go
Normal file
@ -0,0 +1,171 @@
|
||||
package drawimg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go-common/library/log"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// DrawImg create img info.
|
||||
type DrawImg struct {
|
||||
size int
|
||||
CanvasWidth int
|
||||
CanvasHeight int
|
||||
File string
|
||||
txtWidth int
|
||||
srcImg image.Image
|
||||
Canvas *image.NRGBA
|
||||
c *Context
|
||||
f *truetype.Font
|
||||
}
|
||||
|
||||
// NewDrawImg create new img.
|
||||
func NewDrawImg(fontfile string, size int) (w *DrawImg) {
|
||||
w = &DrawImg{
|
||||
size: size,
|
||||
}
|
||||
f, err := w.readFont(fontfile, size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
w.f = f
|
||||
return
|
||||
}
|
||||
|
||||
//ReadSrcImg read an image
|
||||
func (w *DrawImg) ReadSrcImg(path string) (img image.Image, err error) {
|
||||
if img, err = Open(path); err != nil {
|
||||
log.Error("readSrcImg error(%v)", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *DrawImg) readFont(path string, size int) (f *truetype.Font, err error) {
|
||||
fbs, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Error("readFont error(%v)", err)
|
||||
return
|
||||
}
|
||||
f, err = ParseFont(fbs)
|
||||
if err != nil {
|
||||
log.Error("error(%v)", err)
|
||||
}
|
||||
cxt := NewContext()
|
||||
if cxt == nil {
|
||||
return
|
||||
}
|
||||
w.c = cxt
|
||||
w.c.SetFont(f)
|
||||
w.c.SetFontSize(float64(size))
|
||||
return
|
||||
}
|
||||
|
||||
func (w *DrawImg) imgWidth() int {
|
||||
return w.srcImg.Bounds().Max.X
|
||||
}
|
||||
|
||||
func (w *DrawImg) imgHeight() int {
|
||||
return w.srcImg.Bounds().Max.Y
|
||||
}
|
||||
|
||||
func (w *DrawImg) newImgWidth() int {
|
||||
return w.txtWidth + w.imgWidth()
|
||||
}
|
||||
|
||||
func (w *DrawImg) newCanvas(width, height int) *image.NRGBA {
|
||||
return NewNRGBA(width, height, color.RGBA{255, 0, 0, 0})
|
||||
}
|
||||
|
||||
func (w *DrawImg) fillColor(r, g, b, a int32) image.Image {
|
||||
return &image.Uniform{color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)}}
|
||||
}
|
||||
|
||||
func (w *DrawImg) textWidth(text string) (err error) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Error("draw set textWidth text(%s)|error(%v)", text, err)
|
||||
}
|
||||
}()
|
||||
box, err := w.c.FontBox(text, w.pt(3, 5))
|
||||
if err != nil {
|
||||
log.Error("set textWidth text(%s)|error(%v)", text, err)
|
||||
return
|
||||
}
|
||||
widthStr := strings.Split(Int26_6ToString(box.X), ":")[0]
|
||||
wid, err := strconv.ParseInt(widthStr, 10, 64)
|
||||
if err != nil {
|
||||
log.Error("strconv.ParseInt widthStr(%+v)|error(%v)", widthStr, err)
|
||||
return
|
||||
}
|
||||
w.txtWidth = int(wid)
|
||||
return
|
||||
}
|
||||
|
||||
func (w *DrawImg) pt(x, y int) fixed.Point26_6 {
|
||||
return Pt(x, y+int(w.c.PointToFixed(float64(w.size))>>6))
|
||||
}
|
||||
|
||||
func (w *DrawImg) setFont(text string, dstRgba *image.NRGBA, fsrc image.Image, pt fixed.Point26_6) (err error) {
|
||||
w.c.SetClip(w.Canvas.Bounds())
|
||||
w.c.SetDst(dstRgba)
|
||||
w.c.SetSrc(fsrc)
|
||||
_, err = w.c.DrawString(text, pt)
|
||||
if err != nil {
|
||||
log.Error("setFont error(%v)", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *DrawImg) composite(dstCanvas *image.NRGBA, src image.Image, isLeft bool) {
|
||||
var p image.Point
|
||||
if isLeft {
|
||||
p = image.Point{-int(w.txtWidth), 0}
|
||||
} else {
|
||||
p = image.ZP
|
||||
}
|
||||
draw.Draw(dstCanvas, image.Rect(0, 0, w.newImgWidth(), w.imgHeight()), src, p, draw.Over)
|
||||
}
|
||||
|
||||
// Draw write text to the left or right of img.
|
||||
func (w *DrawImg) Draw(text, savepath string, isLeft bool) (err error) {
|
||||
if text == "" {
|
||||
err = errors.New("draw: DrawText called with a empty text")
|
||||
return
|
||||
}
|
||||
w.textWidth(text)
|
||||
w.CanvasWidth = w.newImgWidth()
|
||||
w.CanvasHeight = w.imgHeight()
|
||||
w.Canvas = w.newCanvas(w.CanvasWidth, w.CanvasHeight)
|
||||
if w.c == nil || w.Canvas == nil {
|
||||
err = errors.New("draw: DrawText w.c or w.Canvas is nil")
|
||||
return
|
||||
}
|
||||
draw.Draw(w.Canvas, w.Canvas.Bounds(), w.Canvas, image.ZP, draw.Src)
|
||||
var pt fixed.Point26_6
|
||||
if isLeft {
|
||||
pt = w.pt(3, 5)
|
||||
} else {
|
||||
pt = w.pt(w.imgWidth(), 8)
|
||||
}
|
||||
black := w.fillColor(0, 0, 0, 125)
|
||||
w.setFont(text, w.Canvas, black, pt)
|
||||
blurRgba := Blur(w.Canvas, 6, 3.5)
|
||||
white := w.fillColor(255, 255, 255, 180)
|
||||
w.setFont(text, blurRgba, white, pt)
|
||||
w.composite(blurRgba, w.srcImg, isLeft)
|
||||
Save(blurRgba, savepath)
|
||||
return
|
||||
}
|
257
app/interface/main/creative/dao/drawimg/draw_test.go
Normal file
257
app/interface/main/creative/dao/drawimg/draw_test.go
Normal file
@ -0,0 +1,257 @@
|
||||
package drawimg
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/bouk/monkey"
|
||||
"github.com/golang/freetype/raster"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/smartystreets/goconvey/convey"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
var (
|
||||
di = DrawImg{
|
||||
size: 100,
|
||||
CanvasWidth: 20,
|
||||
CanvasHeight: 20,
|
||||
File: "",
|
||||
txtWidth: 20,
|
||||
srcImg: image.NewAlpha(imgRectangle),
|
||||
Canvas: &image.NRGBA{},
|
||||
c: &c,
|
||||
f: &truetype.Font{},
|
||||
}
|
||||
c = Context{
|
||||
r: nil,
|
||||
f: &truetype.Font{},
|
||||
glyphBuf: truetype.GlyphBuf{},
|
||||
clip: image.Rectangle{},
|
||||
dst: nil,
|
||||
src: nil,
|
||||
fontSize: 0,
|
||||
dpi: 0,
|
||||
scale: 0,
|
||||
hinting: 0,
|
||||
cache: [1024]cacheEntry{},
|
||||
}
|
||||
imgRectangle = image.Rectangle{
|
||||
Min: image.Point{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
},
|
||||
Max: image.Point{
|
||||
X: 1,
|
||||
Y: 1,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestDrawimgNewDrawImg(t *testing.T) {
|
||||
convey.Convey("NewDrawImg", t, func(ctx convey.C) {
|
||||
var (
|
||||
fontfile = ""
|
||||
size = int(10)
|
||||
)
|
||||
monkeyReadFile([]byte{}, nil)
|
||||
monkeyTrueTypeParser(&truetype.Font{}, nil)
|
||||
monkeyFreeTypeSetFont()
|
||||
monkeySetFontSize()
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
w := NewDrawImg(fontfile, size)
|
||||
ctx.Convey("Then w should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(w, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgreadFont(t *testing.T) {
|
||||
convey.Convey("readFont", t, func(ctx convey.C) {
|
||||
var (
|
||||
path = ""
|
||||
size = int(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
monkeyReadFile([]byte{}, nil)
|
||||
monkeyTrueTypeParser(&truetype.Font{}, nil)
|
||||
monkeyFreeTypeSetFont()
|
||||
monkeySetFontSize()
|
||||
f, err := di.readFont(path, size)
|
||||
ctx.Convey("Then err should be nil.f should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(err, convey.ShouldBeNil)
|
||||
ctx.So(f, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgnewCanvas(t *testing.T) {
|
||||
convey.Convey("newCanvas", t, func(ctx convey.C) {
|
||||
var (
|
||||
width = int(0)
|
||||
height = int(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := di.newCanvas(width, height)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgfillColor(t *testing.T) {
|
||||
convey.Convey("fillColor", t, func(ctx convey.C) {
|
||||
var (
|
||||
r = int32(0)
|
||||
g = int32(0)
|
||||
b = int32(0)
|
||||
a = int32(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := di.fillColor(r, g, b, a)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgtextWidth(t *testing.T) {
|
||||
convey.Convey("textWidth", t, func(ctx convey.C) {
|
||||
var (
|
||||
text = "12"
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
monkeyFontBox(fixed.Point26_6{}, nil)
|
||||
err := di.textWidth(text)
|
||||
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
|
||||
ctx.So(err, convey.ShouldBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgpt(t *testing.T) {
|
||||
convey.Convey("pt", t, func(ctx convey.C) {
|
||||
var (
|
||||
x = int(0)
|
||||
y = int(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := di.pt(x, y)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgsetFont(t *testing.T) {
|
||||
convey.Convey("setFont", t, func(ctx convey.C) {
|
||||
var (
|
||||
text = ""
|
||||
dstRgba = &image.NRGBA{}
|
||||
fsrc image.Image
|
||||
pt fixed.Point26_6
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
monkeyDrawString(fixed.Point26_6{}, nil)
|
||||
err := di.setFont(text, dstRgba, fsrc, pt)
|
||||
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
|
||||
ctx.So(err, convey.ShouldBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgcomposite(t *testing.T) {
|
||||
convey.Convey("composite", t, func(ctx convey.C) {
|
||||
var (
|
||||
dstCanvas = &image.NRGBA{}
|
||||
src image.Image
|
||||
isLeft bool
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
monkeyDraw()
|
||||
//monkeybounds(imgRectangle)
|
||||
di.composite(dstCanvas, src, isLeft)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgDraw(t *testing.T) {
|
||||
convey.Convey("Draw", t, func(ctx convey.C) {
|
||||
var (
|
||||
text = "123"
|
||||
savepath = ""
|
||||
isLeft bool
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
//monkeybounds(imgRectangle)
|
||||
monkeyFreeTypeSetFont()
|
||||
err := di.Draw(text, savepath, isLeft)
|
||||
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
|
||||
ctx.So(err, convey.ShouldBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func monkeyReadFile(file []byte, err error) {
|
||||
monkey.Patch(ioutil.ReadFile, func(_ string) ([]byte, error) {
|
||||
return file, err
|
||||
})
|
||||
}
|
||||
|
||||
func monkeyTrueTypeParser(font *truetype.Font, err error) {
|
||||
monkey.Patch(truetype.Parse, func(_ []byte) (*truetype.Font, error) {
|
||||
return font, err
|
||||
})
|
||||
}
|
||||
|
||||
func monkeyFreeTypeSetFont() {
|
||||
monkey.PatchInstanceMethod(reflect.TypeOf(di.c), "SetFont", func(_ *Context, _ *truetype.Font) {})
|
||||
}
|
||||
|
||||
func monkeySetFontSize() {
|
||||
monkey.PatchInstanceMethod(reflect.TypeOf(di.c), "SetFontSize", func(_ *Context, _ float64) {})
|
||||
}
|
||||
|
||||
func monkeyDrawString(p fixed.Point26_6, err error) {
|
||||
monkey.PatchInstanceMethod(reflect.TypeOf(di.c), "DrawString", func(_ *Context, _ string, _ fixed.Point26_6) (fixed.Point26_6, error) {
|
||||
return p, err
|
||||
})
|
||||
}
|
||||
|
||||
func monkeyDraw() {
|
||||
monkey.Patch(draw.Draw, func(_ draw.Image, _ image.Rectangle, _ image.Image, _ image.Point, _ draw.Op) {})
|
||||
}
|
||||
|
||||
func monkeyFontBox(p fixed.Point26_6, err error) {
|
||||
monkey.PatchInstanceMethod(reflect.TypeOf(di.c), "FontBox", func(_ *Context, _ string, _ fixed.Point26_6) (fixed.Point26_6, error) {
|
||||
return p, err
|
||||
})
|
||||
}
|
||||
|
||||
func monkeyLoad() {
|
||||
monkey.PatchInstanceMethod(reflect.TypeOf(&di.c.glyphBuf), "Load", func(_ *truetype.GlyphBuf, _ *truetype.Font, _ fixed.Int26_6, _ truetype.Index, _ font.Hinting) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func monkeyClear() {
|
||||
monkey.PatchInstanceMethod(reflect.TypeOf(di.c.r), "Clear", func(_ *raster.Rasterizer) {})
|
||||
}
|
||||
|
||||
func monkeyRasterizer() {
|
||||
monkey.PatchInstanceMethod(reflect.TypeOf(di.c.r), "Rasterize", func(_ *raster.Rasterizer, _ raster.Painter) {})
|
||||
}
|
316
app/interface/main/creative/dao/drawimg/freetype.go
Normal file
316
app/interface/main/creative/dao/drawimg/freetype.go
Normal file
@ -0,0 +1,316 @@
|
||||
package drawimg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/golang/freetype/raster"
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
const (
|
||||
nGlyphs = 256
|
||||
nXFractions = 4
|
||||
nYFractions = 1
|
||||
)
|
||||
|
||||
type cacheEntry struct {
|
||||
valid bool
|
||||
glyph truetype.Index
|
||||
advanceWidth fixed.Int26_6
|
||||
mask *image.Alpha
|
||||
offset image.Point
|
||||
}
|
||||
|
||||
// ParseFont just calls the Parse function from the freetype/truetype package.
|
||||
func ParseFont(b []byte) (*truetype.Font, error) {
|
||||
return truetype.Parse(b)
|
||||
}
|
||||
|
||||
// Pt converts from a co-ordinate pair measured in pixels to a fixed.Point26_6 co-ordinate pair measured in fixed.Int26_6 units.
|
||||
func Pt(x, y int) fixed.Point26_6 {
|
||||
return fixed.Point26_6{
|
||||
X: fixed.Int26_6(x << 6),
|
||||
Y: fixed.Int26_6(y << 6),
|
||||
}
|
||||
}
|
||||
|
||||
// A Context holds the state for drawing text in a given font and size.
|
||||
type Context struct {
|
||||
r *raster.Rasterizer
|
||||
f *truetype.Font
|
||||
glyphBuf truetype.GlyphBuf
|
||||
clip image.Rectangle
|
||||
|
||||
dst draw.Image
|
||||
src image.Image
|
||||
fontSize, dpi float64
|
||||
scale fixed.Int26_6
|
||||
hinting font.Hinting
|
||||
cache [nGlyphs * nXFractions * nYFractions]cacheEntry
|
||||
}
|
||||
|
||||
// PointToFixed converts the given number of points (as in "a 12 point font") into a 26.6 fixed point number of pixels.
|
||||
func (c *Context) PointToFixed(x float64) fixed.Int26_6 {
|
||||
return fixed.Int26_6(x * float64(c.dpi) * (64.0 / 72.0))
|
||||
}
|
||||
|
||||
// drawContour draws the given closed contour with the given offset.
|
||||
func (c *Context) drawContour(ps []truetype.Point, dx, dy fixed.Int26_6) {
|
||||
if len(ps) == 0 {
|
||||
return
|
||||
}
|
||||
start := fixed.Point26_6{
|
||||
X: dx + ps[0].X,
|
||||
Y: dy - ps[0].Y,
|
||||
}
|
||||
others := []truetype.Point(nil)
|
||||
if ps[0].Flags&0x01 != 0 {
|
||||
others = ps[1:]
|
||||
} else {
|
||||
last := fixed.Point26_6{
|
||||
X: dx + ps[len(ps)-1].X,
|
||||
Y: dy - ps[len(ps)-1].Y,
|
||||
}
|
||||
if ps[len(ps)-1].Flags&0x01 != 0 {
|
||||
start = last
|
||||
others = ps[:len(ps)-1]
|
||||
} else {
|
||||
start = fixed.Point26_6{
|
||||
X: (start.X + last.X) / 2,
|
||||
Y: (start.Y + last.Y) / 2,
|
||||
}
|
||||
others = ps
|
||||
}
|
||||
}
|
||||
c.r.Start(start)
|
||||
q0, on0 := start, true
|
||||
for _, p := range others {
|
||||
q := fixed.Point26_6{
|
||||
X: dx + p.X,
|
||||
Y: dy - p.Y,
|
||||
}
|
||||
on := p.Flags&0x01 != 0
|
||||
if on {
|
||||
if on0 {
|
||||
c.r.Add1(q)
|
||||
} else {
|
||||
c.r.Add2(q0, q)
|
||||
}
|
||||
} else {
|
||||
if on0 {
|
||||
// No-op.
|
||||
} else {
|
||||
mid := fixed.Point26_6{
|
||||
X: (q0.X + q.X) / 2,
|
||||
Y: (q0.Y + q.Y) / 2,
|
||||
}
|
||||
c.r.Add2(q0, mid)
|
||||
}
|
||||
}
|
||||
q0, on0 = q, on
|
||||
}
|
||||
if on0 {
|
||||
c.r.Add1(start)
|
||||
} else {
|
||||
c.r.Add2(q0, start)
|
||||
}
|
||||
}
|
||||
|
||||
// rasterize returns the advance width, glyph mask and integer-pixel offset to render the given glyph at the given sub-pixel offsets.
|
||||
// The 26.6 fixed point arguments fx and fy must be in the range [0, 1).
|
||||
func (c *Context) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) (
|
||||
fixed.Int26_6, *image.Alpha, image.Point, error) {
|
||||
|
||||
if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil {
|
||||
return 0, nil, image.Point{}, err
|
||||
}
|
||||
xmin := int(fx+c.glyphBuf.Bounds.Min.X) >> 6
|
||||
ymin := int(fy-c.glyphBuf.Bounds.Max.Y) >> 6
|
||||
xmax := int(fx+c.glyphBuf.Bounds.Max.X+0x3f) >> 6
|
||||
ymax := int(fy-c.glyphBuf.Bounds.Min.Y+0x3f) >> 6
|
||||
if xmin > xmax || ymin > ymax {
|
||||
return 0, nil, image.Point{}, errors.New("freetype: negative sized glyph")
|
||||
}
|
||||
fx -= fixed.Int26_6(xmin << 6)
|
||||
fy -= fixed.Int26_6(ymin << 6)
|
||||
c.r.Clear()
|
||||
e0 := 0
|
||||
for _, e1 := range c.glyphBuf.Ends {
|
||||
c.drawContour(c.glyphBuf.Points[e0:e1], fx, fy)
|
||||
e0 = e1
|
||||
}
|
||||
a := image.NewAlpha(image.Rect(0, 0, xmax-xmin, ymax-ymin))
|
||||
c.r.Rasterize(raster.NewAlphaSrcPainter(a))
|
||||
return c.glyphBuf.AdvanceWidth, a, image.Point{xmin, ymin}, nil
|
||||
}
|
||||
|
||||
// glyph returns the advance width, glyph mask and integer-pixel offset to render the given glyph at the given sub-pixel point.
|
||||
func (c *Context) glyph(glyph truetype.Index, p fixed.Point26_6) (fixed.Int26_6, *image.Alpha, image.Point, error) {
|
||||
ix, fx := int(p.X>>6), p.X&0x3f
|
||||
iy, fy := int(p.Y>>6), p.Y&0x3f
|
||||
tg := int(glyph) % nGlyphs
|
||||
tx := int(fx) / (64 / nXFractions)
|
||||
ty := int(fy) / (64 / nYFractions)
|
||||
t := ((tg*nXFractions)+tx)*nYFractions + ty
|
||||
if e := c.cache[t]; e.valid && e.glyph == glyph {
|
||||
return e.advanceWidth, e.mask, e.offset.Add(image.Point{ix, iy}), nil
|
||||
}
|
||||
advanceWidth, mask, offset, err := c.rasterize(glyph, fx, fy)
|
||||
if err != nil {
|
||||
return 0, nil, image.Point{}, err
|
||||
}
|
||||
c.cache[t] = cacheEntry{true, glyph, advanceWidth, mask, offset}
|
||||
return advanceWidth, mask, offset.Add(image.Point{ix, iy}), nil
|
||||
}
|
||||
|
||||
// DrawString draws s at p and returns p advanced by the text extent.
|
||||
func (c *Context) DrawString(s string, p fixed.Point26_6) (fixed.Point26_6, error) {
|
||||
if c.f == nil {
|
||||
return fixed.Point26_6{}, errors.New("freetype: DrawText called with a nil font")
|
||||
}
|
||||
prev, hasPrev := truetype.Index(0), false
|
||||
for _, rune := range s {
|
||||
index := c.f.Index(rune)
|
||||
if hasPrev {
|
||||
kern := c.f.Kern(c.scale, prev, index)
|
||||
if c.hinting != font.HintingNone {
|
||||
kern = (kern + 32) &^ 63
|
||||
}
|
||||
p.X += kern
|
||||
}
|
||||
advanceWidth, mask, offset, err := c.glyph(index, p)
|
||||
if err != nil {
|
||||
return fixed.Point26_6{}, err
|
||||
}
|
||||
p.X += advanceWidth
|
||||
glyphRect := mask.Bounds().Add(offset)
|
||||
dr := c.clip.Intersect(glyphRect)
|
||||
if !dr.Empty() {
|
||||
mp := image.Point{0, dr.Min.Y - glyphRect.Min.Y}
|
||||
draw.DrawMask(c.dst, dr, c.src, image.ZP, mask, mp, draw.Over)
|
||||
}
|
||||
prev, hasPrev = index, true
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// recalc recalculates scale and bounds values from the font size, screen resolution and font metrics, and invalidates the glyph cache.
|
||||
func (c *Context) recalc() {
|
||||
c.scale = fixed.Int26_6(c.fontSize * c.dpi * (64.0 / 72.0))
|
||||
if c.f == nil {
|
||||
c.r.SetBounds(0, 0)
|
||||
} else {
|
||||
b := c.f.Bounds(c.scale)
|
||||
xmin := +int(b.Min.X) >> 6
|
||||
ymin := -int(b.Max.Y) >> 6
|
||||
xmax := +int(b.Max.X+63) >> 6
|
||||
ymax := -int(b.Min.Y-63) >> 6
|
||||
c.r.SetBounds(xmax-xmin, ymax-ymin)
|
||||
}
|
||||
for i := range c.cache {
|
||||
c.cache[i] = cacheEntry{}
|
||||
}
|
||||
}
|
||||
|
||||
// SetDPI sets the screen resolution in dots per inch.
|
||||
func (c *Context) SetDPI(dpi float64) {
|
||||
if c.dpi == dpi {
|
||||
return
|
||||
}
|
||||
c.dpi = dpi
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetFont sets the font used to draw text.
|
||||
func (c *Context) SetFont(f *truetype.Font) {
|
||||
if c.f == f {
|
||||
return
|
||||
}
|
||||
c.f = f
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetFontSize sets the font size in points (as in "a 12 point font").
|
||||
func (c *Context) SetFontSize(fontSize float64) {
|
||||
if c.fontSize == fontSize {
|
||||
return
|
||||
}
|
||||
c.fontSize = fontSize
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetHinting sets the hinting policy.
|
||||
func (c *Context) SetHinting(hinting font.Hinting) {
|
||||
c.hinting = hinting
|
||||
for i := range c.cache {
|
||||
c.cache[i] = cacheEntry{}
|
||||
}
|
||||
}
|
||||
|
||||
// SetDst sets the destination image for draw operations.
|
||||
func (c *Context) SetDst(dst draw.Image) {
|
||||
c.dst = dst
|
||||
}
|
||||
|
||||
// SetSrc sets the source image for draw operations. This is typically an image.Uniform.
|
||||
func (c *Context) SetSrc(src image.Image) {
|
||||
c.src = src
|
||||
}
|
||||
|
||||
// SetClip sets the clip rectangle for drawing.
|
||||
func (c *Context) SetClip(clip image.Rectangle) {
|
||||
c.clip = clip
|
||||
}
|
||||
|
||||
// NewContext creates a new Context.
|
||||
func NewContext() *Context {
|
||||
return &Context{
|
||||
r: raster.NewRasterizer(0, 0),
|
||||
fontSize: 12,
|
||||
dpi: 72,
|
||||
scale: 12 << 6,
|
||||
}
|
||||
}
|
||||
|
||||
// FontBox get textbox.
|
||||
func (c *Context) FontBox(s string, p fixed.Point26_6) (fixed.Point26_6, error) {
|
||||
if c.f == nil {
|
||||
return fixed.Point26_6{}, errors.New("freetype: DrawText called with a nil font")
|
||||
}
|
||||
prev, hasPrev := truetype.Index(0), false
|
||||
for _, rune := range s {
|
||||
index := c.f.Index(rune)
|
||||
if hasPrev {
|
||||
kern := c.f.Kern(c.scale, prev, index)
|
||||
if c.hinting != font.HintingNone {
|
||||
kern = (kern + 32) &^ 63
|
||||
}
|
||||
p.X += kern
|
||||
}
|
||||
advanceWidth, _, _, err := c.glyph(index, p)
|
||||
if err != nil {
|
||||
return fixed.Point26_6{}, err
|
||||
}
|
||||
p.X += advanceWidth
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Int26_6ToString convert Int26_6 to string.
|
||||
func Int26_6ToString(x fixed.Int26_6) string {
|
||||
const shift, mask = 6, 1<<6 - 1
|
||||
if x >= 0 {
|
||||
return fmt.Sprintf("%d:%02d", int32(x>>shift), int32(x&mask))
|
||||
}
|
||||
x = -x
|
||||
if x >= 0 {
|
||||
return fmt.Sprintf("-%d:%02d", int32(x>>shift), int32(x&mask))
|
||||
}
|
||||
return "-33554432:00" // The minimum value is -(1<<25).
|
||||
}
|
228
app/interface/main/creative/dao/drawimg/freetype_test.go
Normal file
228
app/interface/main/creative/dao/drawimg/freetype_test.go
Normal file
@ -0,0 +1,228 @@
|
||||
package drawimg
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/smartystreets/goconvey/convey"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
func TestDrawimgParseFont(t *testing.T) {
|
||||
convey.Convey("ParseFont", t, func(ctx convey.C) {
|
||||
var (
|
||||
b = []byte("")
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1, err := ParseFont(b)
|
||||
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(err, convey.ShouldBeNil)
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgPt(t *testing.T) {
|
||||
convey.Convey("Pt", t, func(ctx convey.C) {
|
||||
var (
|
||||
x = int(0)
|
||||
y = int(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := Pt(x, y)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgPointToFixed(t *testing.T) {
|
||||
convey.Convey("PointToFixed", t, func(ctx convey.C) {
|
||||
var (
|
||||
x = float64(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := di.c.PointToFixed(x)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgdrawContour(t *testing.T) {
|
||||
convey.Convey("drawContour", t, func(ctx convey.C) {
|
||||
var (
|
||||
ps = []truetype.Point{
|
||||
{
|
||||
X: 2,
|
||||
Y: 2,
|
||||
Flags: 1,
|
||||
},
|
||||
{
|
||||
X: 2,
|
||||
Y: 2,
|
||||
Flags: 1,
|
||||
},
|
||||
}
|
||||
dx fixed.Int26_6
|
||||
dy fixed.Int26_6
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c.drawContour(ps, dx, dy)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgrecalc(t *testing.T) {
|
||||
convey.Convey("recalc", t, func(ctx convey.C) {
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c.recalc()
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgSetDPI(t *testing.T) {
|
||||
convey.Convey("SetDPI", t, func(ctx convey.C) {
|
||||
var (
|
||||
dpi = float64(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c.SetDPI(dpi)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgSetFont(t *testing.T) {
|
||||
convey.Convey("SetFont", t, func(ctx convey.C) {
|
||||
var (
|
||||
f = &truetype.Font{}
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c.SetFont(f)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgSetFontSize(t *testing.T) {
|
||||
convey.Convey("SetFontSize", t, func(ctx convey.C) {
|
||||
var (
|
||||
fontSize = float64(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c.SetFontSize(fontSize)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgSetHinting(t *testing.T) {
|
||||
convey.Convey("SetHinting", t, func(ctx convey.C) {
|
||||
var (
|
||||
hinting font.Hinting
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c.SetHinting(hinting)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgSetDst(t *testing.T) {
|
||||
convey.Convey("SetDst", t, func(ctx convey.C) {
|
||||
var (
|
||||
dst draw.Image
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c.SetDst(dst)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgSetSrc(t *testing.T) {
|
||||
convey.Convey("SetSrc", t, func(ctx convey.C) {
|
||||
var (
|
||||
src image.Image
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c.SetSrc(src)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgSetClip(t *testing.T) {
|
||||
convey.Convey("SetClip", t, func(ctx convey.C) {
|
||||
var (
|
||||
clip image.Rectangle
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c.SetClip(clip)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgNewContext(t *testing.T) {
|
||||
convey.Convey("NewContext", t, func(ctx convey.C) {
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := NewContext()
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgInt26_6ToString(t *testing.T) {
|
||||
convey.Convey("Int26_6ToString", t, func(ctx convey.C) {
|
||||
var (
|
||||
x fixed.Int26_6
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := Int26_6ToString(x)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgDrawString(t *testing.T) {
|
||||
convey.Convey("DrawString", t, func(ctx convey.C) {
|
||||
var (
|
||||
s = "mock"
|
||||
p fixed.Point26_6
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
di.c = &c
|
||||
monkeyLoad()
|
||||
monkeyClear()
|
||||
monkeyRasterizer()
|
||||
p1, err := di.c.DrawString(s, p)
|
||||
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(err, convey.ShouldBeNil)
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
158
app/interface/main/creative/dao/drawimg/gaussian.go
Normal file
158
app/interface/main/creative/dao/drawimg/gaussian.go
Normal file
@ -0,0 +1,158 @@
|
||||
package drawimg
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
func gaussianBlurKernel(x, sigma float64) float64 {
|
||||
return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi))
|
||||
}
|
||||
|
||||
func blurHorizontal(src *image.NRGBA, kernel []float64) *image.NRGBA {
|
||||
radius := len(kernel) - 1
|
||||
width := src.Bounds().Max.X
|
||||
height := src.Bounds().Max.Y
|
||||
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
parallel(width, func(partStart, partEnd int) {
|
||||
for x := partStart; x < partEnd; x++ {
|
||||
start := x - radius
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
end := x + radius
|
||||
if end > width-1 {
|
||||
end = width - 1
|
||||
}
|
||||
weightSum := 0.0
|
||||
for ix := start; ix <= end; ix++ {
|
||||
weightSum += kernel[absint(x-ix)]
|
||||
}
|
||||
for y := 0; y < height; y++ {
|
||||
var r, g, b, a float64
|
||||
for ix := start; ix <= end; ix++ {
|
||||
weight := kernel[absint(x-ix)]
|
||||
i := y*src.Stride + ix*4
|
||||
wa := float64(src.Pix[i+3]) * weight
|
||||
r += float64(src.Pix[i+0]) * wa
|
||||
g += float64(src.Pix[i+1]) * wa
|
||||
b += float64(src.Pix[i+2]) * wa
|
||||
a += wa
|
||||
}
|
||||
j := y*dst.Stride + x*4
|
||||
dst.Pix[j+0] = clamp(r / a)
|
||||
dst.Pix[j+1] = clamp(g / a)
|
||||
dst.Pix[j+2] = clamp(b / a)
|
||||
dst.Pix[j+3] = clamp(a / weightSum)
|
||||
}
|
||||
}
|
||||
})
|
||||
return dst
|
||||
}
|
||||
|
||||
func blurVertical(src *image.NRGBA, kernel []float64) *image.NRGBA {
|
||||
radius := len(kernel) - 1
|
||||
width := src.Bounds().Max.X
|
||||
height := src.Bounds().Max.Y
|
||||
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
parallel(height, func(partStart, partEnd int) {
|
||||
for y := partStart; y < partEnd; y++ {
|
||||
start := y - radius
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
end := y + radius
|
||||
if end > height-1 {
|
||||
end = height - 1
|
||||
}
|
||||
weightSum := 0.0
|
||||
for iy := start; iy <= end; iy++ {
|
||||
weightSum += kernel[absint(y-iy)]
|
||||
}
|
||||
for x := 0; x < width; x++ {
|
||||
var r, g, b, a float64
|
||||
for iy := start; iy <= end; iy++ {
|
||||
weight := kernel[absint(y-iy)]
|
||||
i := iy*src.Stride + x*4
|
||||
wa := float64(src.Pix[i+3]) * weight
|
||||
r += float64(src.Pix[i+0]) * wa
|
||||
g += float64(src.Pix[i+1]) * wa
|
||||
b += float64(src.Pix[i+2]) * wa
|
||||
a += wa
|
||||
}
|
||||
j := y*dst.Stride + x*4
|
||||
dst.Pix[j+0] = clamp(r / a)
|
||||
dst.Pix[j+1] = clamp(g / a)
|
||||
dst.Pix[j+2] = clamp(b / a)
|
||||
dst.Pix[j+3] = clamp(a / weightSum)
|
||||
}
|
||||
}
|
||||
})
|
||||
return dst
|
||||
}
|
||||
|
||||
// Sharpen produces a sharpened version of the image.
|
||||
func Sharpen(img image.Image, radius int, sigma float64) *image.NRGBA {
|
||||
if sigma <= 0 {
|
||||
// sigma parameter must be positive!
|
||||
return Clone(img)
|
||||
}
|
||||
src := toNRGBA(img)
|
||||
blurred := Blur(img, radius, sigma)
|
||||
width := src.Bounds().Max.X
|
||||
height := src.Bounds().Max.Y
|
||||
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
parallel(height, func(partStart, partEnd int) {
|
||||
for y := partStart; y < partEnd; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
i := y*src.Stride + x*4
|
||||
for j := 0; j < 4; j++ {
|
||||
k := i + j
|
||||
val := int(src.Pix[k])<<1 - int(blurred.Pix[k])
|
||||
if val < 0 {
|
||||
val = 0
|
||||
} else if val > 255 {
|
||||
val = 255
|
||||
}
|
||||
dst.Pix[k] = uint8(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return dst
|
||||
}
|
||||
|
||||
// Blur produces a blurred version of the image using a Gaussian function.
|
||||
func Blur(img image.Image, radius int, sigma float64) *image.NRGBA {
|
||||
var dst *image.NRGBA
|
||||
if sigma <= 0 {
|
||||
// sigma parameter must be positive!
|
||||
return Clone(img)
|
||||
}
|
||||
src := toNRGBA(img)
|
||||
kernel := make([]float64, radius+1)
|
||||
for i := 0; i <= radius; i++ {
|
||||
kernel[i] = gaussianBlurKernel(float64(i), sigma)
|
||||
}
|
||||
dst = blurHorizontal(src, kernel)
|
||||
dst = blurVertical(dst, kernel)
|
||||
return dst
|
||||
}
|
||||
|
||||
// IsTooBright assume that average brightness higher than 100 is too bright.
|
||||
func IsTooBright(img image.Image) bool {
|
||||
var (
|
||||
pixCount, totalBrightness float64
|
||||
)
|
||||
pixCount = 0
|
||||
totalBrightness = 0
|
||||
AdjustFunc(img, func(c color.NRGBA) color.NRGBA {
|
||||
brightness := 0.2126*float64(c.R) + 0.7152*float64(c.G) + 0.0722*float64(c.B)
|
||||
totalBrightness += brightness
|
||||
pixCount++
|
||||
return c
|
||||
})
|
||||
averBrightness := totalBrightness / pixCount
|
||||
return averBrightness > 100
|
||||
}
|
558
app/interface/main/creative/dao/drawimg/imgutil.go
Normal file
558
app/interface/main/creative/dao/drawimg/imgutil.go
Normal file
@ -0,0 +1,558 @@
|
||||
package drawimg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Format is an image file format.
|
||||
type Format int
|
||||
|
||||
// Image file formats.
|
||||
const (
|
||||
JPEG Format = iota
|
||||
PNG
|
||||
GIF
|
||||
)
|
||||
|
||||
func (f Format) String() string {
|
||||
switch f {
|
||||
case JPEG:
|
||||
return "JPEG"
|
||||
case PNG:
|
||||
return "PNG"
|
||||
case GIF:
|
||||
return "GIF"
|
||||
default:
|
||||
return "Unsupported"
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrUnsupportedFormat means the given image format (or file extension) is unsupported.
|
||||
ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
|
||||
)
|
||||
|
||||
// Decode reads an image from r.
|
||||
func Decode(r io.Reader) (image.Image, error) {
|
||||
img, _, err := image.Decode(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toNRGBA(img), nil
|
||||
}
|
||||
|
||||
// Open loads an image from file
|
||||
func Open(filename string) (image.Image, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
img, err := Decode(file)
|
||||
return img, err
|
||||
}
|
||||
|
||||
// Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
|
||||
func Encode(w io.Writer, img image.Image, format Format) error {
|
||||
var err error
|
||||
switch format {
|
||||
case JPEG:
|
||||
var rgba *image.RGBA
|
||||
if nrgba, ok := img.(*image.NRGBA); ok {
|
||||
if nrgba.Opaque() {
|
||||
rgba = &image.RGBA{
|
||||
Pix: nrgba.Pix,
|
||||
Stride: nrgba.Stride,
|
||||
Rect: nrgba.Rect,
|
||||
}
|
||||
}
|
||||
}
|
||||
if rgba != nil {
|
||||
err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: 95})
|
||||
} else {
|
||||
err = jpeg.Encode(w, img, &jpeg.Options{Quality: 95})
|
||||
}
|
||||
case PNG:
|
||||
err = png.Encode(w, img)
|
||||
case GIF:
|
||||
err = gif.Encode(w, img, &gif.Options{NumColors: 256})
|
||||
default:
|
||||
err = ErrUnsupportedFormat
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Save saves the image to file with the specified filename.
|
||||
// The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif" are supported.
|
||||
func Save(img image.Image, filename string) (err error) {
|
||||
formats := map[string]Format{
|
||||
".jpg": JPEG,
|
||||
".jpeg": JPEG,
|
||||
".png": PNG,
|
||||
".gif": GIF,
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
f, ok := formats[ext]
|
||||
if !ok {
|
||||
return ErrUnsupportedFormat
|
||||
}
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return Encode(file, img, f)
|
||||
}
|
||||
|
||||
// NewNRGBA creates a new image with the specified width and height, and fills it with the specified color.
|
||||
func NewNRGBA(width, height int, fillColor color.Color) *image.NRGBA {
|
||||
if width <= 0 || height <= 0 {
|
||||
return &image.NRGBA{}
|
||||
}
|
||||
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
|
||||
if c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 {
|
||||
return dst
|
||||
}
|
||||
cs := []uint8{c.R, c.G, c.B, c.A}
|
||||
// fill the first row
|
||||
for x := 0; x < width; x++ {
|
||||
copy(dst.Pix[x*4:(x+1)*4], cs)
|
||||
}
|
||||
// copy the first row to other rows
|
||||
for y := 1; y < height; y++ {
|
||||
copy(dst.Pix[y*dst.Stride:y*dst.Stride+width*4], dst.Pix[0:width*4])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Clone returns a copy of the given image.
|
||||
func Clone(img image.Image) *image.NRGBA {
|
||||
srcBounds := img.Bounds()
|
||||
srcMinX := srcBounds.Min.X
|
||||
srcMinY := srcBounds.Min.Y
|
||||
dstBounds := srcBounds.Sub(srcBounds.Min)
|
||||
dstW := dstBounds.Dx()
|
||||
dstH := dstBounds.Dy()
|
||||
dst := image.NewNRGBA(dstBounds)
|
||||
switch src := img.(type) {
|
||||
case *image.NRGBA:
|
||||
rowSize := srcBounds.Dx() * 4
|
||||
parallel(dstH, func(partStart, partEnd int) {
|
||||
for dstY := partStart; dstY < partEnd; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
si := src.PixOffset(srcMinX, srcMinY+dstY)
|
||||
copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
|
||||
}
|
||||
})
|
||||
case *image.NRGBA64:
|
||||
parallel(dstH, func(partStart, partEnd int) {
|
||||
for dstY := partStart; dstY < partEnd; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
si := src.PixOffset(srcMinX, srcMinY+dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
dst.Pix[di+0] = src.Pix[si+0]
|
||||
dst.Pix[di+1] = src.Pix[si+2]
|
||||
dst.Pix[di+2] = src.Pix[si+4]
|
||||
dst.Pix[di+3] = src.Pix[si+6]
|
||||
di += 4
|
||||
si += 8
|
||||
}
|
||||
}
|
||||
})
|
||||
case *image.RGBA:
|
||||
parallel(dstH, func(partStart, partEnd int) {
|
||||
for dstY := partStart; dstY < partEnd; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
si := src.PixOffset(srcMinX, srcMinY+dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
a := src.Pix[si+3]
|
||||
dst.Pix[di+3] = a
|
||||
switch a {
|
||||
case 0:
|
||||
dst.Pix[di+0] = 0
|
||||
dst.Pix[di+1] = 0
|
||||
dst.Pix[di+2] = 0
|
||||
case 0xff:
|
||||
dst.Pix[di+0] = src.Pix[si+0]
|
||||
dst.Pix[di+1] = src.Pix[si+1]
|
||||
dst.Pix[di+2] = src.Pix[si+2]
|
||||
default:
|
||||
var tmp uint16
|
||||
tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a)
|
||||
dst.Pix[di+0] = uint8(tmp)
|
||||
tmp = uint16(src.Pix[si+1]) * 0xff / uint16(a)
|
||||
dst.Pix[di+1] = uint8(tmp)
|
||||
tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a)
|
||||
dst.Pix[di+2] = uint8(tmp)
|
||||
}
|
||||
di += 4
|
||||
si += 4
|
||||
}
|
||||
}
|
||||
})
|
||||
case *image.RGBA64:
|
||||
parallel(dstH, func(partStart, partEnd int) {
|
||||
for dstY := partStart; dstY < partEnd; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
si := src.PixOffset(srcMinX, srcMinY+dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
a := src.Pix[si+6]
|
||||
dst.Pix[di+3] = a
|
||||
switch a {
|
||||
case 0:
|
||||
dst.Pix[di+0] = 0
|
||||
dst.Pix[di+1] = 0
|
||||
dst.Pix[di+2] = 0
|
||||
case 0xff:
|
||||
dst.Pix[di+0] = src.Pix[si+0]
|
||||
dst.Pix[di+1] = src.Pix[si+2]
|
||||
dst.Pix[di+2] = src.Pix[si+4]
|
||||
default:
|
||||
var tmp uint16
|
||||
tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a)
|
||||
dst.Pix[di+0] = uint8(tmp)
|
||||
tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a)
|
||||
dst.Pix[di+1] = uint8(tmp)
|
||||
tmp = uint16(src.Pix[si+4]) * 0xff / uint16(a)
|
||||
dst.Pix[di+2] = uint8(tmp)
|
||||
}
|
||||
di += 4
|
||||
si += 8
|
||||
}
|
||||
}
|
||||
})
|
||||
case *image.Gray:
|
||||
parallel(dstH, func(partStart, partEnd int) {
|
||||
for dstY := partStart; dstY < partEnd; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
si := src.PixOffset(srcMinX, srcMinY+dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
c := src.Pix[si]
|
||||
dst.Pix[di+0] = c
|
||||
dst.Pix[di+1] = c
|
||||
dst.Pix[di+2] = c
|
||||
dst.Pix[di+3] = 0xff
|
||||
di += 4
|
||||
si++
|
||||
}
|
||||
}
|
||||
})
|
||||
case *image.Gray16:
|
||||
parallel(dstH, func(partStart, partEnd int) {
|
||||
for dstY := partStart; dstY < partEnd; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
si := src.PixOffset(srcMinX, srcMinY+dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
c := src.Pix[si]
|
||||
dst.Pix[di+0] = c
|
||||
dst.Pix[di+1] = c
|
||||
dst.Pix[di+2] = c
|
||||
dst.Pix[di+3] = 0xff
|
||||
di += 4
|
||||
si += 2
|
||||
}
|
||||
}
|
||||
})
|
||||
case *image.YCbCr:
|
||||
parallel(dstH, func(partStart, partEnd int) {
|
||||
for dstY := partStart; dstY < partEnd; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
srcX := srcMinX + dstX
|
||||
srcY := srcMinY + dstY
|
||||
siy := src.YOffset(srcX, srcY)
|
||||
sic := src.COffset(srcX, srcY)
|
||||
r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
|
||||
dst.Pix[di+0] = r
|
||||
dst.Pix[di+1] = g
|
||||
dst.Pix[di+2] = b
|
||||
dst.Pix[di+3] = 0xff
|
||||
di += 4
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
case *image.Paletted:
|
||||
plen := len(src.Palette)
|
||||
pnew := make([]color.NRGBA, plen)
|
||||
for i := 0; i < plen; i++ {
|
||||
pnew[i] = color.NRGBAModel.Convert(src.Palette[i]).(color.NRGBA)
|
||||
}
|
||||
parallel(dstH, func(partStart, partEnd int) {
|
||||
for dstY := partStart; dstY < partEnd; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
si := src.PixOffset(srcMinX, srcMinY+dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
c := pnew[src.Pix[si]]
|
||||
dst.Pix[di+0] = c.R
|
||||
dst.Pix[di+1] = c.G
|
||||
dst.Pix[di+2] = c.B
|
||||
dst.Pix[di+3] = c.A
|
||||
di += 4
|
||||
si++
|
||||
}
|
||||
}
|
||||
})
|
||||
default:
|
||||
parallel(dstH, func(partStart, partEnd int) {
|
||||
for dstY := partStart; dstY < partEnd; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
|
||||
dst.Pix[di+0] = c.R
|
||||
dst.Pix[di+1] = c.G
|
||||
dst.Pix[di+2] = c.B
|
||||
dst.Pix[di+3] = c.A
|
||||
di += 4
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// toNRGBA converts any image type to *image.NRGBA with min-point at (0, 0).
|
||||
func toNRGBA(img image.Image) *image.NRGBA {
|
||||
srcBounds := img.Bounds()
|
||||
if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
|
||||
if src0, ok := img.(*image.NRGBA); ok {
|
||||
return src0
|
||||
}
|
||||
}
|
||||
return Clone(img)
|
||||
}
|
||||
|
||||
// AdjustFunc performs a gamma correction on the image and returns the adjusted image.
|
||||
func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
|
||||
src := toNRGBA(img)
|
||||
width := src.Bounds().Max.X
|
||||
height := src.Bounds().Max.Y
|
||||
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
parallel(height, func(partStart, partEnd int) {
|
||||
for y := partStart; y < partEnd; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
i := y*src.Stride + x*4
|
||||
j := y*dst.Stride + x*4
|
||||
r := src.Pix[i+0]
|
||||
g := src.Pix[i+1]
|
||||
b := src.Pix[i+2]
|
||||
a := src.Pix[i+3]
|
||||
c := fn(color.NRGBA{r, g, b, a})
|
||||
dst.Pix[j+0] = c.R
|
||||
dst.Pix[j+1] = c.G
|
||||
dst.Pix[j+2] = c.B
|
||||
dst.Pix[j+3] = c.A
|
||||
}
|
||||
}
|
||||
})
|
||||
return dst
|
||||
}
|
||||
|
||||
// AdjustGamma performs a gamma correction on the image and returns the adjusted image.
|
||||
// Gamma parameter must be positive. Gamma = 1.0 gives the original image.
|
||||
// Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// dstImage = imaging.AdjustGamma(srcImage, 0.7)
|
||||
//
|
||||
func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
|
||||
e := 1.0 / math.Max(gamma, 0.0001)
|
||||
lut := make([]uint8, 256)
|
||||
for i := 0; i < 256; i++ {
|
||||
lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
|
||||
}
|
||||
fn := func(c color.NRGBA) color.NRGBA {
|
||||
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
|
||||
}
|
||||
return AdjustFunc(img, fn)
|
||||
}
|
||||
|
||||
func sigmoid(a, b, x float64) float64 {
|
||||
return 1 / (1 + math.Exp(b*(a-x)))
|
||||
}
|
||||
|
||||
// AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
|
||||
// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
|
||||
// The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
|
||||
// The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
|
||||
// If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // increase the contrast
|
||||
// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // decrease the contrast
|
||||
//
|
||||
func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
|
||||
if factor == 0 {
|
||||
return Clone(img)
|
||||
}
|
||||
lut := make([]uint8, 256)
|
||||
a := math.Min(math.Max(midpoint, 0.0), 1.0)
|
||||
b := math.Abs(factor)
|
||||
sig0 := sigmoid(a, b, 0)
|
||||
sig1 := sigmoid(a, b, 1)
|
||||
e := 1.0e-6
|
||||
if factor > 0 {
|
||||
for i := 0; i < 256; i++ {
|
||||
x := float64(i) / 255.0
|
||||
sigX := sigmoid(a, b, x)
|
||||
f := (sigX - sig0) / (sig1 - sig0)
|
||||
lut[i] = clamp(f * 255.0)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < 256; i++ {
|
||||
x := float64(i) / 255.0
|
||||
arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
|
||||
f := a - math.Log(1.0/arg-1.0)/b
|
||||
lut[i] = clamp(f * 255.0)
|
||||
}
|
||||
}
|
||||
fn := func(c color.NRGBA) color.NRGBA {
|
||||
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
|
||||
}
|
||||
return AdjustFunc(img, fn)
|
||||
}
|
||||
|
||||
// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
|
||||
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
|
||||
// The percentage = -100 gives solid grey image.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// dstImage = imaging.AdjustContrast(srcImage, -10) // decrease image contrast by 10%
|
||||
// dstImage = imaging.AdjustContrast(srcImage, 20) // increase image contrast by 20%
|
||||
//
|
||||
func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
|
||||
percentage = math.Min(math.Max(percentage, -100.0), 100.0)
|
||||
lut := make([]uint8, 256)
|
||||
v := (100.0 + percentage) / 100.0
|
||||
for i := 0; i < 256; i++ {
|
||||
if 0 <= v && v <= 1 {
|
||||
lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
|
||||
} else if 1 < v && v < 2 {
|
||||
lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
|
||||
} else {
|
||||
lut[i] = uint8(float64(i)/255.0+0.5) * 255
|
||||
}
|
||||
}
|
||||
fn := func(c color.NRGBA) color.NRGBA {
|
||||
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
|
||||
}
|
||||
return AdjustFunc(img, fn)
|
||||
}
|
||||
|
||||
// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
|
||||
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
|
||||
// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// dstImage = imaging.AdjustBrightness(srcImage, -15) // decrease image brightness by 15%
|
||||
// dstImage = imaging.AdjustBrightness(srcImage, 10) // increase image brightness by 10%
|
||||
//
|
||||
func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
|
||||
percentage = math.Min(math.Max(percentage, -100.0), 100.0)
|
||||
lut := make([]uint8, 256)
|
||||
shift := 255.0 * percentage / 100.0
|
||||
for i := 0; i < 256; i++ {
|
||||
lut[i] = clamp(float64(i) + shift)
|
||||
}
|
||||
fn := func(c color.NRGBA) color.NRGBA {
|
||||
return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
|
||||
}
|
||||
return AdjustFunc(img, fn)
|
||||
}
|
||||
|
||||
// Grayscale produces grayscale version of the image.
|
||||
func Grayscale(img image.Image) *image.NRGBA {
|
||||
fn := func(c color.NRGBA) color.NRGBA {
|
||||
f := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
|
||||
y := uint8(f + 0.5)
|
||||
return color.NRGBA{y, y, y, c.A}
|
||||
}
|
||||
return AdjustFunc(img, fn)
|
||||
}
|
||||
|
||||
// Invert produces inverted (negated) version of the image.
|
||||
func Invert(img image.Image) *image.NRGBA {
|
||||
fn := func(c color.NRGBA) color.NRGBA {
|
||||
return color.NRGBA{255 - c.R, 255 - c.G, 255 - c.B, c.A}
|
||||
}
|
||||
return AdjustFunc(img, fn)
|
||||
}
|
||||
|
||||
// parallel starts parallel image processing based on the current GOMAXPROCS value.
|
||||
// If GOMAXPROCS = 1 it uses no parallelization.
|
||||
// If GOMAXPROCS > 1 it spawns N=GOMAXPROCS workers in separate goroutines.
|
||||
func parallel(dataSize int, fn func(partStart, partEnd int)) {
|
||||
numGoroutines := 1
|
||||
partSize := dataSize
|
||||
numProcs := runtime.GOMAXPROCS(0)
|
||||
if numProcs > 1 {
|
||||
numGoroutines = numProcs
|
||||
partSize = dataSize / (numGoroutines * 10)
|
||||
if partSize < 1 {
|
||||
partSize = 1
|
||||
}
|
||||
}
|
||||
if numGoroutines == 1 {
|
||||
fn(0, dataSize)
|
||||
} else {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numGoroutines)
|
||||
idx := uint64(0)
|
||||
for p := 0; p < numGoroutines; p++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
partStart := int(atomic.AddUint64(&idx, uint64(partSize))) - partSize
|
||||
if partStart >= dataSize {
|
||||
break
|
||||
}
|
||||
partEnd := partStart + partSize
|
||||
if partEnd > dataSize {
|
||||
partEnd = dataSize
|
||||
}
|
||||
fn(partStart, partEnd)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// absint returns the absolute value of i.
|
||||
func absint(i int) int {
|
||||
if i < 0 {
|
||||
return -i
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// clamp rounds and clamps float64 value to fit into uint8.
|
||||
func clamp(x float64) uint8 {
|
||||
v := int64(x + 0.5)
|
||||
if v > 255 {
|
||||
return 255
|
||||
}
|
||||
if v > 0 {
|
||||
return uint8(v)
|
||||
}
|
||||
return 0
|
||||
}
|
290
app/interface/main/creative/dao/drawimg/imgutil_test.go
Normal file
290
app/interface/main/creative/dao/drawimg/imgutil_test.go
Normal file
@ -0,0 +1,290 @@
|
||||
package drawimg
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/bouk/monkey"
|
||||
"github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestDrawimgString(t *testing.T) {
|
||||
convey.Convey("String", t, func(ctx convey.C) {
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := JPEG.String()
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgOpen(t *testing.T) {
|
||||
convey.Convey("Open", t, func(ctx convey.C) {
|
||||
var (
|
||||
filename = ""
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1, err := Open(filename)
|
||||
ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldBeNil)
|
||||
ctx.So(err, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgEncode(t *testing.T) {
|
||||
convey.Convey("Encode", t, func(ctx convey.C) {
|
||||
var (
|
||||
w io.Writer
|
||||
img = image.NewRGBA(imgRectangle)
|
||||
format = JPEG
|
||||
)
|
||||
monkeyJpegEncode()
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
err := Encode(w, img, format)
|
||||
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
|
||||
ctx.So(err, convey.ShouldBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgSave(t *testing.T) {
|
||||
convey.Convey("Save", t, func(ctx convey.C) {
|
||||
var (
|
||||
img image.Image
|
||||
filename = ""
|
||||
)
|
||||
ctx.Convey("When everything goes not positive", func(ctx convey.C) {
|
||||
err := Save(img, filename)
|
||||
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
|
||||
ctx.So(err, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgNewNRGBA(t *testing.T) {
|
||||
convey.Convey("NewNRGBA", t, func(ctx convey.C) {
|
||||
var (
|
||||
width = int(0)
|
||||
height = int(0)
|
||||
fillColor color.Color
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := NewNRGBA(width, height, fillColor)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgClone(t *testing.T) {
|
||||
convey.Convey("Clone", t, func(ctx convey.C) {
|
||||
var (
|
||||
rgba = image.NewRGBA(imgRectangle)
|
||||
rgba64 = image.NewNRGBA64(imgRectangle)
|
||||
)
|
||||
ctx.Convey("RGBA", func(ctx convey.C) {
|
||||
p1 := Clone(rgba)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
ctx.Convey("RGBA64", func(ctx convey.C) {
|
||||
p1 := Clone(rgba64)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgtoNRGBA(t *testing.T) {
|
||||
convey.Convey("toNRGBA", t, func(ctx convey.C) {
|
||||
var (
|
||||
img = image.NewRGBA64(imgRectangle)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := toNRGBA(img)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgAdjustFunc(t *testing.T) {
|
||||
convey.Convey("AdjustFunc", t, func(ctx convey.C) {
|
||||
var (
|
||||
img = image.NewRGBA64(imgRectangle)
|
||||
fn = func(c color.NRGBA) color.NRGBA { return c }
|
||||
)
|
||||
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := AdjustFunc(img, fn)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgAdjustGamma(t *testing.T) {
|
||||
convey.Convey("AdjustGamma", t, func(ctx convey.C) {
|
||||
var (
|
||||
img = image.NewRGBA64(imgRectangle)
|
||||
gamma = float64(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := AdjustGamma(img, gamma)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgsigmoid(t *testing.T) {
|
||||
convey.Convey("sigmoid", t, func(ctx convey.C) {
|
||||
var (
|
||||
a = float64(0)
|
||||
b = float64(0)
|
||||
x = float64(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := sigmoid(a, b, x)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgAdjustSigmoid(t *testing.T) {
|
||||
convey.Convey("AdjustSigmoid", t, func(ctx convey.C) {
|
||||
var (
|
||||
img = image.NewRGBA64(imgRectangle)
|
||||
midpoint = float64(0)
|
||||
factor = float64(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := AdjustSigmoid(img, midpoint, factor)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgAdjustContrast(t *testing.T) {
|
||||
convey.Convey("AdjustContrast", t, func(ctx convey.C) {
|
||||
var (
|
||||
img = image.NewRGBA64(imgRectangle)
|
||||
percentage = float64(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := AdjustContrast(img, percentage)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgAdjustBrightness(t *testing.T) {
|
||||
convey.Convey("AdjustBrightness", t, func(ctx convey.C) {
|
||||
var (
|
||||
img = image.NewRGBA64(imgRectangle)
|
||||
percentage = float64(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := AdjustBrightness(img, percentage)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgGrayscale(t *testing.T) {
|
||||
convey.Convey("Grayscale", t, func(ctx convey.C) {
|
||||
var (
|
||||
img = image.NewRGBA64(imgRectangle)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := Grayscale(img)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgInvert(t *testing.T) {
|
||||
convey.Convey("Invert", t, func(ctx convey.C) {
|
||||
var (
|
||||
img = image.NewRGBA64(imgRectangle)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := Invert(img)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgparallel(t *testing.T) {
|
||||
convey.Convey("parallel", t, func(ctx convey.C) {
|
||||
var (
|
||||
dataSize = int(0)
|
||||
fn func(partStart int, partEnd int)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
parallel(dataSize, fn)
|
||||
ctx.Convey("No return values", func(ctx convey.C) {
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgabsint(t *testing.T) {
|
||||
convey.Convey("absint", t, func(ctx convey.C) {
|
||||
var (
|
||||
i = int(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := absint(i)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDrawimgclamp(t *testing.T) {
|
||||
convey.Convey("clamp", t, func(ctx convey.C) {
|
||||
var (
|
||||
x = float64(0)
|
||||
)
|
||||
ctx.Convey("When everything gose positive", func(ctx convey.C) {
|
||||
p1 := clamp(x)
|
||||
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
|
||||
ctx.So(p1, convey.ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func monkeyJpegEncode() {
|
||||
monkey.Patch(jpeg.Encode, func(_ io.Writer, _ image.Image, _ *jpeg.Options) error {
|
||||
return nil
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user