美文网首页
go+imagemagick使用svg+png绘制复杂图片

go+imagemagick使用svg+png绘制复杂图片

作者: fencex | 来源:发表于2018-06-01 10:49 被阅读426次

开发过程会有使用go绘制较为复杂图形的需求,比如这次,需要做一个类似拖拽图案验证码。
具体需求:

  1. 4条边,凹凸平3个形状,随机组合,
  2. 随机位置上截图如上形状的图片,并且在原图有如上形状的阴影遮罩

go自带image/draw包不能绘制复杂的图形(或者说太复杂),经过尝试,可以使用go来绘制svg,再使用imagemagick来进行特效处理,最后再次使用go把图层融合在一起。

github.com/ajstarks/svgo #基本图形svg绘制
github.com/gographics/imagick #特效处理(需安装imagemagick)

先上最终最终效果

origin.png tile-final.png

go 绘制基本图形

使用 svgo 来绘制 path,生成 svg,代码贴在最后

imagemagick convert把 svg 转化为 png, 加阴影

注:只展示 imagemagick convert,没有融合到 go 程序里

tile.png
convert \
  \( -background none MSVG:tile-cut.svg -background black -shadow 50x2+0+10 \
    -gravity North -background none -extent 200x200 \
    -compose over  \
    \( -background none MSVG:tile-cut.svg -background white -shadow 120x6+0+0 -resize 95% \
      -gravity center -background none -extent 200x200 \
    \) \
    -background none -gravity NorthEast -geometry +0+0 -composite \
  \) \
  -compose over tile-line.svg -gravity center -geometry +0+0 -composite \
tile.png

代码注释:

  1. MSVG 支持svg 作为输入源,需要 imagemagick 支持 rsvg, 比如mac:
    brew install imagemagick --with-librsvg
  2. -background black -shadow 50x2+0+10 前两个数字自己调着看,影响shadow的大小和透明度,后两个是x,y轴偏移,可+可-
  3. -background none 背景透明
  4. compose 两个图层组合方式, over 表示 后者在前者之上,还有其他选项,详见官网手册
  5. -gravity North -background none -extent 200x200 -extent 扩展图片尺寸,-gravity North 表示扩展的时候,原图处于什么位置,North 表示北,还有NorthEast 等,详见官网手册

处理阴影svg

tile-shadow.png
convert -density 2400 -background transparent  MSVG:tile-shadow.svg \
-fill "rgba(0,0,0,0.3)" -opaque black -resize 200x200 tile-shadow.png

代码注释:

  1. -opaque 删除图片中颜色,这里删除黑色,填充rgba(0,0,0,0.3), 否则不能转化透明度,这里用了一个trick
  2. -density 2400 这里通过修改了分辨率来提高 svg 转化到 png 的细致程度,再强制 -resize 200x200

完成这个小需求绕挺多弯路现总结如下:

  1. go 可以完成一些简单图形的绘制,比如shape/tile 中At方法其实是对每个点进行上色,这是 draw 包的实现
  2. go 支持图层叠加,支持图层融合
  3. 绘制几何图形使用 svg path,很方便,go可以使用 svgo 包,但是不支持转化成png图片,也不能作为draw包的输入源
  4. 图片处理 使用 imagemagick, 我这里没有使用扩展把逻辑写到代码里,因为我可以把这些 tile png先生成,go直接叠加图层,降低运算,比如这次需求,4条边,3种形状(凹凸平),2个类型(外边阴影,阴影填充) ,3x3x3x3x2 = 162

附go代码:

main.go

package main

import (
    "image"
    "log"
    _ "image/jpeg"
    _ "image/png"
    "os"
    "math/rand"
    "time"
    "math"
    "image/png"
    "image/draw"
    "captcha/shape"
)

func main() {

    //保存svgs,
    saveSvgs()

    cutWidth := 200
    top, right, bottom, left := 1, 1, 0, -1
   
    reader, err := os.Open("images/14d4c647b216975cb298481f4e550ebc.jpg")
    if err != nil {
        log.Fatal(err)
    }
    defer reader.Close()
    m, _, err := image.Decode(reader)
    rgbImg := m.(*image.YCbCr)

    reader2, err := os.Open("tile.png")
    if err != nil {
        log.Fatal(err)
    }
    defer reader2.Close()
    m2, _, err := image.Decode(reader2)
    rgbImg2 := m2.(*image.NRGBA64)

    reader3, err := os.Open("tile-shadow.png")
    if err != nil {
        log.Fatal(err)
    }
    defer reader3.Close()
    m3, _, err := image.Decode(reader3)
    rgbImg3 := m3.(*image.NRGBA64)

    randRect := getRandomRectangle(m.Bounds(), cutWidth, cutWidth)
    tile := shape.Tile{image.Pt(45, 0), cutWidth, top, right, bottom, left, false}

    // go draw 包处理代码,
    // 新建图片
    originImage := image.NewRGBA(rgbImg.Bounds())
    // 使用draw原图
    draw.Draw(originImage, rgbImg.Bounds(), rgbImg, rgbImg.Bounds().Min, draw.Src)
    // 把imagemagick处理好的阴影png 绘制到图片的随机位置 randRect 之上
    draw.Draw(originImage, randRect.Bounds(), rgbImg3, rgbImg3.Bounds().Min, draw.Over)

    f2, _ := os.Create("origin.png")
    defer f2.Close()
    png.Encode(f2, originImage)

    cutImage4 := image.NewRGBA(image.Rect(0, 0, cutWidth, cutWidth))
    // 在随机位置randRect 截取 tile 形状的截图
    draw.DrawMask(cutImage4, cutImage4.Bounds(), rgbImg, randRect.Bounds().Min, &tile, tile.Bounds().Min, draw.Src)
    // 在图片之上添加 imagemagick 处理好的 tile.png
    draw.Draw(cutImage4, cutImage4.Bounds(), rgbImg2, rgbImg2.Bounds().Min, draw.Over)
    f5, _ := os.Create("tile-final.png")
    defer f5.Close()
    png.Encode(f5, cutImage4)
}

// 绘制svg
func saveSvgs(){
    cutWidth := 200
    top, right, bottom, left := 1, 1, 0, -1

    //保存 svg 文件
    tileSvg := shape.TileSvg{cutWidth, top, right, bottom, left}
    tileSvg.SaveSvg("tile-cut.svg", "stroke-width:10;stroke:White;fill:none;")
    tileSvg.SaveSvg("tile-line.svg", "stroke-width:2;stroke:White;fill:none;")
    tileSvg.SaveSvg("tile-shadow.svg", "stroke-width:2;stroke:White;fill:black;fill-opacity:0.5")
}

// 随机获取截图位置
func getRandomRectangle(rectangle image.Rectangle, subWidth int, subHeight int) (*image.Rectangle) {
    bounds := rectangle.Bounds()
    width := bounds.Max.X - bounds.Min.X
    height := bounds.Max.Y - bounds.Min.Y

    rand.Seed(time.Now().Unix())
    maxPosX := math.Ceil(float64(width)*0.7) - math.Ceil(float64(subWidth)*1.3)
    maxPosY := math.Ceil(float64(height)*0.7) - math.Ceil(float64(subHeight)*1.3)
    if maxPosX < 0 || maxPosY < 0 {
        //todo error
        tmp := image.Rect(0, 0, 0, 0)
        return &tmp
    }
    posX := rand.Intn(int(maxPosX)) + int(math.Ceil(float64(width)*0.3))
    posY := rand.Intn(int(maxPosY)) + int(math.Ceil(float64(height)*0.3))

    log.Println(maxPosX, maxPosY)
    log.Println(posX, posY, posX+int(subWidth), posY+int(subHeight))

    retRectangle := image.Rect(posX, posY, posX+int(subWidth), posY+int(subHeight), )

    return &retRectangle
}

shape/tilesvg.go 绘制svg图形

package shape

import (
    "os"
    "fmt"
    "github.com/ajstarks/svgo"
)

type TileSvg struct {
    Width int
    T     int
    R     int
    B     int
    L     int
}

/**
shape svg path
 */
func (t *TileSvg) SaveSvg(filename string, stroke string) {
    width := t.Width
    height := t.Width
    f5, _ := os.Create(filename)
    canvas := svg.New(f5)
    canvas.Start(width, height)

    if len(stroke) <= 0 {
        stroke = "stroke-width:10;stroke:green;fill:red;"
    }
    path := fmt.Sprintf("M%d,%d h%d ", width/5, width/5, width/5)

    if t.T > 0 {
        path += fmt.Sprintf("A%d,%d 0 0,1 %d,%d ", width/10, width/10, width*3/5, width/5)
    } else if t.T < 0 {
        path += fmt.Sprintf("A%d,%d 0 1,0 %d,%d ", width/10, width/10, width*3/5, width/5)
    } else {
        path += fmt.Sprintf("h+%d ", width/5)
    }
    path += fmt.Sprintf("h%d v%d ", width/5, width/5)
    if t.R > 0 {
        path += fmt.Sprintf("A%d,%d 0 0,1 %d,%d ", width/10, width/10, width*4/5, width*3/5)
    } else if t.R < 0 {
        path += fmt.Sprintf("A%d,%d 0 1,0 %d,%d ", width/10, width/10, width*4/5, width*3/5)
    } else {
        path += fmt.Sprintf("v+%d ", width/5)
    }
    path += fmt.Sprintf("v%d h-%d ", width/5, width/5)

    if t.B > 0 {
        path += fmt.Sprintf("A%d,%d 0 0,1 %d,%d ", width/10, width/10, width*2/5, width*4/5)
    } else if t.B < 0 {
        path += fmt.Sprintf("A%d,%d 0 1,0 %d,%d ", width/10, width/10, width*2/5, width*4/5)
    } else {
        path += fmt.Sprintf("h-%d ", width/5)
    }
    path += fmt.Sprintf("h-%d v-%d ", width/5, width/5)

    if t.L > 0 {
        path += fmt.Sprintf(" A%d,%d 0 0,1 %d,%d ", width/10, width/10, width/5, width*2/5)
    } else if t.L < 0 {
        path += fmt.Sprintf(" A%d,%d 0 1,0 %d,%d ", width/10, width/10, width/5, width*2/5)
    } else {
        path += fmt.Sprintf("v-%d ", width/5)
    }
    path += fmt.Sprintf("v-%d ", width/5)
    canvas.Path(path, stroke)

    canvas.End()
}

shape/tile.go

package shape

import (
    "image"
    "image/color"
)

type Tile struct {
    Min    image.Point
    Width  int
    T      int
    R      int
    B      int
    L      int
    Revert bool
}

func (c *Tile) ColorModel() color.Model {
    return color.AlphaModel
}

func (c *Tile) Bounds() image.Rectangle {
    return image.Rect(c.Min.X, c.Min.Y, c.Min.X+c.Width, c.Min.Y+c.Width)
}

func (c *Tile) At(x, y int) color.Color {

    colorAt := color.Alpha{220}
    colorWithin := color.Alpha{220}
    colorWithout := color.Alpha{0}
    if c.Revert {
        colorWithin = color.Alpha{0}
        colorWithout = color.Alpha{220}
    }

    margin := c.Width / 5
    if x > (c.Min.X+c.Width/5) && x < (c.Bounds().Max.X-c.Width/5) && y > (c.Min.Y+c.Width/5) && y < (c.Bounds().Max.Y-c.Width/5) {
        colorAt = colorWithin
    } else {
        colorAt = colorWithout
    }

    r := c.Width / 10
    //todo 优化算法

    if c.T != 0 {
        roundSpot := image.Pt((c.Bounds().Max.X-c.Min.X)/2+c.Min.X, c.Min.Y+margin)
        xx, yy, rr := float64(x-roundSpot.X)+0.5, float64(y-roundSpot.Y)+0.5, float64(r)+0.5
        if xx*xx+yy*yy < rr*rr {
            if c.T > 0 {
                colorAt = colorWithin
            } else {
                colorAt = colorWithout
            }
        }
    }

    if c.R != 0 {
        roundSpot := image.Pt(c.Bounds().Max.X-margin, (c.Bounds().Max.Y-c.Min.Y)/2+c.Min.Y)
        xx, yy, rr := float64(x-roundSpot.X)+0.5, float64(y-roundSpot.Y)+0.5, float64(r)+0.5
        if xx*xx+yy*yy < rr*rr {
            if c.R > 0 {
                colorAt = colorWithin
            } else {
                colorAt = colorWithout
            }
        }
    }

    if c.B != 0 {
        roundSpot := image.Pt((c.Bounds().Max.X-c.Min.X)/2+c.Min.X, (c.Bounds().Max.Y - margin))
        xx, yy, rr := float64(x-roundSpot.X)+0.5, float64(y-roundSpot.Y)+0.5, float64(r)+0.5
        if xx*xx+yy*yy < rr*rr {
            if c.B > 0 {
                colorAt = colorWithin
            } else {
                colorAt = colorWithout
            }
        }
    }

    if c.L != 0 {
        roundSpot := image.Pt(c.Min.X+margin, (c.Bounds().Max.Y-c.Min.Y)/2+c.Min.Y)
        xx, yy, rr := float64(x-roundSpot.X)+0.5, float64(y-roundSpot.Y)+0.5, float64(r)+0.5
        if xx*xx+yy*yy < rr*rr {
            if c.L > 0 {
                colorAt = colorWithin
            } else {
                colorAt = colorWithout
            }
        }
    }

    return colorAt
}

相关文章

  • go+imagemagick使用svg+png绘制复杂图片

    开发过程会有使用go绘制较为复杂图形的需求,比如这次,需要做一个类似拖拽图案验证码。具体需求: 4条边,凹凸平3个...

  • 2017-04-25 canvas 学习笔记

    canvas 绘制线条 嵌入式不建议使用,会导致内部图片拉伸 绘制表格背景 绘制表格 绘制矩形 绘制画板 绘制弧线...

  • HTML5 CANVAS

    简单初步: 创建画布 绘制矩形 绘制图形 绘制图片 HTML5使用路径 制作简单动画 1.使用setInterva...

  • OpenGLES 使用 —— 图片绘制

    目标 使用OpenGLES将一张图片加载到屏幕中 创建一个继承于GLKViewController的ViewCon...

  • Apk瘦身

    优化方式 图片资源优化 首先选择VD 矢量图片只需放置一份 图片太大绘制时间长,制作复杂成都高...

  • canvas绘图

    1.创建路径绘制线条(直线和折线、多边形) 2.绘制图片 3.切割图片 使用clip()切割图片 4.画布缩放旋转...

  • OpenGL ES关于纹理翻转策略解析

    在用OpenGL ES绘制图片的时候, 我们发现所绘制的图片颠倒了, 以下我们来使用几种解析策略来解决这个问题, ...

  • 计算机视觉 OpenCV (3)

    今天我们主要介绍如何使用 OpenCV 来在图片上绘制几何图形、线、矩形、圆以及添加文字。 在图片上绘制直线 li...

  • CAShapeLayer 和drawRect 绘制图形的区别

    一般都是使用CAShapeLayer 绘制图形, 今天突然看到使用 drawRect 绘制图片, 在想两个异同点....

  • imageWithContentsOfFile 读取nil

    1 . [UIImage imageNamed:name]; 说明 : 有缓存的, 小图片和经常绘制的图片可以使用...

网友评论

      本文标题:go+imagemagick使用svg+png绘制复杂图片

      本文链接:https://www.haomeiwen.com/subject/bzphsftx.html