feat(归并文件): 归并根包文件

This commit is contained in:
xiaoqidun committed 2026-10-19 10:17:59 +08:00
1 parent ec24b6a17d
commit 1a8de5a3f1
23 files changed
+1724 -1981

No files matched your search

+132
View File
@@ -16,12 +16,16 @@ package ofdgo
import (
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"image"
"image/color"
"math"
basegeom "github.com/xiaoqidun/ofdgo/internal/base/geom"
baseimage "github.com/xiaoqidun/ofdgo/internal/base/image"
"golang.org/x/image/draw"
)
// 光栅后端接收的路径指令
@@ -382,3 +386,131 @@ func rasterCommandGeometry(geometry GeometryBackend, command RasterCommand) (Geo
}
return path, nil
}
// rasterClipKey 区分页面尺寸、分辨率和裁剪路径
// 入参: page 页面, path 页面坐标路径
// 返回: [32]byte 缓存键
func rasterClipKey(page *RasterPage, path []RasterSegment) [32]byte {
hash := sha256.New()
var data [8]byte
number := func(v float64) {
binary.LittleEndian.PutUint64(data[:], math.Float64bits(v))
hash.Write(data[:])
}
number(page.Width)
number(page.Height)
number(page.DPI)
for _, s := range path {
hash.Write([]byte{byte(s.Verb)})
for _, p := range []RasterPoint{s.End, s.Control1, s.Control2} {
number(p.X)
number(p.Y)
}
}
var key [32]byte
copy(key[:], hash.Sum(nil))
return key
}
// rasterClipMask 使用所选后端生成区域化透明度蒙版,同页相同裁剪可复用
// 入参: backend 光栅后端, page 页面, path 页面坐标裁剪, cache 蒙版缓存
// 返回: *image.Alpha 页面像素坐标蒙版, error 绘制错误
func rasterClipMask(backend RasterBackend, page *RasterPage, path []RasterSegment, cache *renderCache[[32]byte, *image.Alpha]) (*image.Alpha, error) {
key := rasterClipKey(page, path)
if mask, ok := cache.get(key); ok {
return mask, nil
}
w, h, err := page.PixelSize()
if err != nil {
return nil, err
}
matrix := page.PixelTransform(RasterMatrix{1, 0, 0, 1, 0, 0}, h)
minX, minY, maxX, maxY := math.Inf(1), math.Inf(1), math.Inf(-1), math.Inf(-1)
include := func(p RasterPoint) {
p = matrix.Apply(p)
minX = math.Min(minX, p.X)
minY = math.Min(minY, p.Y)
maxX = math.Max(maxX, p.X)
maxY = math.Max(maxY, p.Y)
}
for _, s := range path {
switch s.Verb {
case RasterMove, RasterLine:
include(s.End)
case RasterQuad:
include(s.End)
include(s.Control1)
case RasterCubic:
include(s.End)
include(s.Control1)
include(s.Control2)
case RasterClose:
default:
return nil, fmt.Errorf("invalid clip path command %d", s.Verb)
}
}
if len(path) == 0 || minX > maxX || minY > maxY {
return image.NewAlpha(image.Rectangle{}), nil
}
if !finite(minX) || !finite(minY) || !finite(maxX) || !finite(maxY) {
return nil, fmt.Errorf("invalid clip coordinates")
}
bounds := image.Rect(int(math.Max(0, math.Min(float64(w), math.Floor(minX)-1))), int(math.Max(0, math.Min(float64(h), math.Floor(minY)-1))), int(math.Max(0, math.Min(float64(w), math.Ceil(maxX)+1))), int(math.Max(0, math.Min(float64(h), math.Ceil(maxY)+1))))
mask := image.NewAlpha(bounds)
if !bounds.Empty() {
scale := page.DPI / 25.4
local := &RasterPage{Width: float64(bounds.Dx()) / scale, Height: float64(bounds.Dy()) / scale, DPI: page.DPI}
local.Commands = []RasterCommand{{Path: path, Paint: RasterPaint{Color: color.RGBA{255, 255, 255, 255}}, Transform: RasterMatrix{1, 0, 0, 1, -float64(bounds.Min.X) / scale, (matrix[5] - float64(bounds.Min.Y)) / scale}}}
pixels, err := backend.Render(local)
if err != nil {
return nil, err
}
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
start := mask.PixOffset(bounds.Min.X, y)
row := mask.Pix[start : start+bounds.Dx()]
for x := range row {
row[x] = uint8(baseimage.AlphaAt(pixels, x, y-bounds.Min.Y) >> 8)
}
}
}
cache.put(key, mask, len(mask.Pix)+256)
return mask, nil
}
// PixelTransform 将页面毫米变换转换为像素变换,保留尺寸取整后的原点位置
// 入参: m 局部到页面变换, height 目标像素高度
// 返回: RasterMatrix 局部到像素变换
func (p *RasterPage) PixelTransform(m RasterMatrix, height int) RasterMatrix {
scale := p.DPI / 25.4
for i := range m {
m[i] *= scale
}
m[5] += float64(height) - p.Height*scale
return m
}
// drawRasterImageCommand 使用当前后端生成裁剪蒙版,共用高质量图像采样
// 入参: backend 当前后端, page 页面, dst 目标图像, src 原始图像, command 图像指令, cache 蒙版缓存
// 返回: error 裁剪绘制错误
func drawRasterImageCommand(backend RasterBackend, page *RasterPage, dst draw.Image, src image.Image, command RasterCommand, cache *renderCache[[32]byte, *image.Alpha]) error {
m := page.PixelTransform(command.Transform, dst.Bounds().Dy())
if command.Clip == nil {
baseimage.DrawPixels(dst, src, m, draw.Over)
return nil
}
mask, err := rasterClipMask(backend, page, command.Clip, cache)
if err != nil {
return err
}
if mask.Rect.Empty() {
return nil
}
bounds := mask.Rect.Intersect(dst.Bounds()).Intersect(baseimage.TransformBounds(src.Bounds(), m, dst.Bounds()))
if bounds.Empty() {
return nil
}
layer := image.NewRGBA64(bounds)
baseimage.DrawPixels(layer, baseimage.PixelSource(src), m, draw.Src)
draw.DrawMask(dst, bounds, layer, bounds.Min, mask, bounds.Min, draw.Over)
return nil
}