mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
This commit is contained in:
1 parent
f44d0d8a60
commit
580e7043c4
31 files changed
+1488
-204
No files matched your search
+120
-12
@@ -18,34 +18,39 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"sync"
|
||||
|
||||
"github.com/gogpu/gg"
|
||||
)
|
||||
|
||||
// GGBackend 提供字体、页面编译和CPU绘制,不注册全局GPU设备或隐式切换后端
|
||||
type GGBackend struct{}
|
||||
// StrokeGeometry用于GG暂不能准确处理的尖角连接、闭合或曲线虚线及亚像素渐变描边,未配置时返回能力错误
|
||||
type GGBackend struct{ StrokeGeometry GeometryBackend }
|
||||
|
||||
// Name 返回后端标识
|
||||
// 返回: string 后端标识
|
||||
func (GGBackend) Name() string { return "gg" }
|
||||
|
||||
// Render 绘制已编译页面,使用自有双圆渐变语义保持原有定位
|
||||
// Prepare 将路径、样式和裁剪准备为固定DPI的GG场景
|
||||
// 入参: page 只读页面
|
||||
// 返回: image.Image 图像, error 绘制错误
|
||||
func (GGBackend) Render(page *RasterPage) (image.Image, error) {
|
||||
// 返回: RasterScene 可重复绘制场景, error 准备错误
|
||||
func (b GGBackend) Prepare(page *RasterPage) (RasterScene, error) {
|
||||
w, h, err := page.PixelSize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pixmap := gg.NewPixmap(w, h)
|
||||
img := pixmap.ImageView()
|
||||
raster := gg.NewSoftwareRenderer(w, h)
|
||||
page = cloneRasterPage(page)
|
||||
scene := &ggRasterScene{page: page, width: w, height: h, masks: renderCache[[32]byte, *image.Alpha]{limit: 16 << 20}}
|
||||
for i, cmd := range page.Commands {
|
||||
if cmd.Stroke != nil && !ggNativeStroke(cmd, page.DPI/25.4) {
|
||||
cmd, err = b.expandStroke(cmd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gg command %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
m := page.PixelTransform(cmd.Transform, h)
|
||||
if cmd.Image != nil {
|
||||
if err := drawRasterImageCommand(GGBackend{}, page, img, imagePixelSource(cmd.Image), cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scene.commands = append(scene.commands, ggRasterCommand{command: cmd})
|
||||
continue
|
||||
}
|
||||
path := gg.NewPath()
|
||||
@@ -81,16 +86,119 @@ func (GGBackend) Render(page *RasterPage) (image.Image, error) {
|
||||
return ggRGBA(g.At(p.X, p.Y))
|
||||
}))
|
||||
}
|
||||
if cmd.EvenOdd {
|
||||
if cmd.EvenOdd && cmd.Stroke == nil {
|
||||
paint.FillRule = gg.FillRuleEvenOdd
|
||||
}
|
||||
if err := raster.Fill(pixmap, path, paint); err != nil {
|
||||
if cmd.Clip != nil {
|
||||
mask, err := rasterClipMask(GGBackend{}, page, cmd.Clip, &scene.masks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mask.Rect.Empty() {
|
||||
continue
|
||||
}
|
||||
paint.ClipMask, paint.ClipMaskW, paint.ClipMaskH = mask.Pix, mask.Stride, mask.Rect.Dy()
|
||||
paint.ClipMaskX, paint.ClipMaskY = mask.Rect.Min.X, mask.Rect.Min.Y
|
||||
}
|
||||
if cmd.Stroke != nil {
|
||||
style, err := ggStroke(*cmd.Stroke, page.DPI/25.4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paint.SetStroke(style)
|
||||
paint.SetStrokeBrush(paint.FillBrush())
|
||||
}
|
||||
scene.commands = append(scene.commands, ggRasterCommand{command: cmd, path: path, paint: paint})
|
||||
}
|
||||
return scene, nil
|
||||
}
|
||||
|
||||
// ggRasterCommand 保存已转换的像素路径和样式
|
||||
type ggRasterCommand struct {
|
||||
command RasterCommand
|
||||
path *gg.Path
|
||||
paint *gg.Paint
|
||||
}
|
||||
|
||||
// ggRasterScene 复用路径和局部裁剪,串行保护掩码缓存
|
||||
type ggRasterScene struct {
|
||||
mu sync.Mutex
|
||||
page *RasterPage
|
||||
width, height int
|
||||
commands []ggRasterCommand
|
||||
masks renderCache[[32]byte, *image.Alpha]
|
||||
}
|
||||
|
||||
// Render 绘制独立像素缓冲,不累积上次内容
|
||||
// 返回: image.Image 图像, error 绘制错误
|
||||
func (s *ggRasterScene) Render() (image.Image, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
pixmap := gg.NewPixmap(s.width, s.height)
|
||||
img := pixmap.ImageView()
|
||||
raster := gg.NewSoftwareRenderer(s.width, s.height)
|
||||
for i, cmd := range s.commands {
|
||||
var err error
|
||||
switch {
|
||||
case cmd.command.Image != nil:
|
||||
err = drawRasterImageCommand(GGBackend{}, s.page, img, imagePixelSource(cmd.command.Image), cmd.command, &s.masks)
|
||||
case cmd.command.Stroke != nil:
|
||||
err = raster.Stroke(pixmap, cmd.path, cmd.paint)
|
||||
default:
|
||||
err = raster.Fill(pixmap, cmd.path, cmd.paint)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gg command %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// Render 准备并绘制页面,重复绘制可使用Prepare复用场景
|
||||
// 入参: page 只读页面
|
||||
// 返回: image.Image 图像, error 绘制错误
|
||||
func (b GGBackend) Render(page *RasterPage) (image.Image, error) {
|
||||
scene, err := b.Prepare(page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return scene.Render()
|
||||
}
|
||||
|
||||
// ggStroke 将页面毫米样式转换为GG像素描边,虚线与线宽同步换算
|
||||
// 入参: options 描边样式, scale 每毫米像素数
|
||||
// 返回: gg.Stroke 像素样式, error 样式错误
|
||||
func ggStroke(options StrokeOptions, scale float64) (gg.Stroke, error) {
|
||||
if err := validateStroke(options); err != nil {
|
||||
return gg.Stroke{}, err
|
||||
}
|
||||
style := gg.DefaultStroke().WithWidth(options.Width * scale)
|
||||
style.MiterLimit = options.MiterLimit
|
||||
if style.MiterLimit == 0 {
|
||||
style.MiterLimit = defaultMiterLimit
|
||||
}
|
||||
switch options.Cap {
|
||||
case "Round":
|
||||
style.Cap = gg.LineCapRound
|
||||
case "Square":
|
||||
style.Cap = gg.LineCapSquare
|
||||
}
|
||||
switch options.Join {
|
||||
case "Round":
|
||||
style.Join = gg.LineJoinRound
|
||||
case "Bevel":
|
||||
style.Join = gg.LineJoinBevel
|
||||
}
|
||||
if len(options.Dashes) > 0 {
|
||||
dashes := make([]float64, len(options.Dashes))
|
||||
for i, value := range options.Dashes {
|
||||
dashes[i] = value * scale
|
||||
}
|
||||
style = style.WithDashPattern(dashes...).WithDashOffset(options.DashOffset * scale)
|
||||
}
|
||||
return style, nil
|
||||
}
|
||||
|
||||
// ggRGBA 将标准库预乘颜色转换为gg的非预乘颜色
|
||||
// 入参: c 预乘颜色
|
||||
// 返回: gg.RGBA 非预乘颜色
|
||||
|
||||
Reference in new issue
Block a user