mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
1 parent
c8a163a077
commit
0351dd54f5
31 files changed
+993
-311
No files matched your search
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
||||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1
|
github.com/xiaoqidun/jbig2 v1.1.1
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261007170436-881165d037e5
|
github.com/xiaoqidun/pdfgo v0.0.0-20261008021024-e8f1cc0a7ad9
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
golang.org/x/image v0.46.0
|
golang.org/x/image v0.46.0
|
||||||
golang.org/x/text v0.42.0
|
golang.org/x/text v0.42.0
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
|||||||
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261007170436-881165d037e5 h1:FZ24gdR14GDzZ0+pV0ZQUjxA71eSiEMAiHH2B5O8fPo=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261008021024-e8f1cc0a7ad9 h1:aHEEZYzIL7cCLW9IXMlBC+LyCxkYrge3Zw/GfioDT2I=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261007170436-881165d037e5/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261008021024-e8f1cc0a7ad9/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"image"
|
"image"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -34,6 +35,12 @@ type outputImageJob struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// outputImageSource 保存已读取资源的摘要和格式,不保留原始编码
|
||||||
|
type outputImageSource struct {
|
||||||
|
key outputImageKey
|
||||||
|
jpeg bool
|
||||||
|
}
|
||||||
|
|
||||||
// outputImageOptions 统一资源压缩及像素需求,受保护图片只进行无损编码
|
// outputImageOptions 统一资源压缩及像素需求,受保护图片只进行无损编码
|
||||||
// 入参: key 资源路径
|
// 入参: key 资源路径
|
||||||
// 返回: CompressionOptions 实际策略, image.Point 像素需求
|
// 返回: CompressionOptions 实际策略, image.Point 像素需求
|
||||||
@@ -51,9 +58,9 @@ func (e *Editor) outputImageOptions(key string) (CompressionOptions, image.Point
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepareOutputImages 有界并行优化独立图片,读取、缓存及进度仍在调用线程处理
|
// prepareOutputImages 有界并行优化独立图片,读取、缓存及进度仍在调用线程处理
|
||||||
// 入参: keys 资源路径, load 串行读取方法,空值表示跳过, results 本次写出的候选缓存
|
// 入参: keys 资源路径, load 串行读取方法,空值表示跳过, results 候选缓存, sources 原始资源摘要,可为空
|
||||||
// 返回: error 读取、进度或取消错误
|
// 返回: error 读取、进度或取消错误
|
||||||
func (e *Editor) prepareOutputImages(keys []string, load func(string) ([]byte, error), results map[outputImageKey][]byte) error {
|
func (e *Editor) prepareOutputImages(keys []string, load func(string) ([]byte, error), results map[outputImageKey][]byte, sources map[string]outputImageSource) error {
|
||||||
workers := min(2, runtime.GOMAXPROCS(0))
|
workers := min(2, runtime.GOMAXPROCS(0))
|
||||||
if workers < 2 || len(keys) < 2 {
|
if workers < 2 || len(keys) < 2 {
|
||||||
return nil
|
return nil
|
||||||
@@ -84,6 +91,9 @@ func (e *Editor) prepareOutputImages(keys []string, load func(string) ([]byte, e
|
|||||||
}
|
}
|
||||||
options, demand := e.outputImageOptions(key)
|
options, demand := e.outputImageOptions(key)
|
||||||
cacheKey := outputImageKey{hash: sha256.Sum256(data), options: options, size: demand}
|
cacheKey := outputImageKey{hash: sha256.Sum256(data), options: options, size: demand}
|
||||||
|
if sources != nil {
|
||||||
|
sources[key] = outputImageSource{key: cacheKey, jpeg: bytes.HasPrefix(data, []byte{255, 216})}
|
||||||
|
}
|
||||||
if _, cached := results[cacheKey]; cached {
|
if _, cached := results[cacheKey]; cached {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea
|
|||||||
formats := make(map[string]string)
|
formats := make(map[string]string)
|
||||||
seen := make(map[string]map[[32]byte]string)
|
seen := make(map[string]map[[32]byte]string)
|
||||||
imageResults := make(map[outputImageKey][]byte)
|
imageResults := make(map[outputImageKey][]byte)
|
||||||
|
imageSources := make(map[string]outputImageSource)
|
||||||
ordered := slices.Sorted(maps.Keys(actual))
|
ordered := slices.Sorted(maps.Keys(actual))
|
||||||
var imageKeys []string
|
var imageKeys []string
|
||||||
for _, key := range ordered {
|
for _, key := range ordered {
|
||||||
@@ -191,7 +192,7 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea
|
|||||||
}
|
}
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
if err := e.prepareOutputImages(imageKeys, load, imageResults); err != nil {
|
if err := e.prepareOutputImages(imageKeys, load, imageResults, imageSources); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, key := range ordered {
|
for _, key := range ordered {
|
||||||
@@ -205,24 +206,22 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea
|
|||||||
if err := editorProgress(e.OnWriteProgress).report("compress", e.output.completed, 0); err != nil {
|
if err := editorProgress(e.OnWriteProgress).report("compress", e.output.completed, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if data, ok := parts[actual[key]]; ok && len(data) > outputOptimizationBufferLimit {
|
source, prefetched := imageSources[key]
|
||||||
continue
|
var data []byte
|
||||||
}
|
if !prefetched {
|
||||||
if reader != nil && parts[actual[key]] == nil {
|
var err error
|
||||||
if file, ok := reader.packageFile(actual[key]); ok && file.UncompressedSize64 > outputOptimizationBufferLimit {
|
data, err = load(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if data == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
source.key.hash = sha256.Sum256(data)
|
||||||
data, err := read(actual[key])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(data) > outputOptimizationBufferLimit {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if role == "MediaFile" {
|
if role == "MediaFile" {
|
||||||
options, demand := e.outputImageOptions(key)
|
options, demand := e.outputImageOptions(key)
|
||||||
cacheKey := outputImageKey{hash: sha256.Sum256(data), options: options, size: demand}
|
cacheKey := outputImageKey{hash: source.key.hash, options: options, size: demand}
|
||||||
candidate, cached := imageResults[cacheKey]
|
candidate, cached := imageResults[cacheKey]
|
||||||
var err error
|
var err error
|
||||||
if !cached {
|
if !cached {
|
||||||
@@ -231,6 +230,8 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea
|
|||||||
imageResults[cacheKey] = nil
|
imageResults[cacheKey] = nil
|
||||||
if len(candidate) < len(data) {
|
if len(candidate) < len(data) {
|
||||||
imageResults[cacheKey] = candidate
|
imageResults[cacheKey] = candidate
|
||||||
|
} else {
|
||||||
|
candidate = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,14 +239,24 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea
|
|||||||
if e.output.ctx.Err() != nil {
|
if e.output.ctx.Err() != nil {
|
||||||
return e.output.ctx.Err()
|
return e.output.ctx.Err()
|
||||||
}
|
}
|
||||||
} else if candidate != nil && len(candidate) < len(data) {
|
} else if candidate != nil {
|
||||||
data = candidate
|
data = candidate
|
||||||
parts[actual[key]] = data
|
parts[actual[key]] = data
|
||||||
|
source.key.hash = sha256.Sum256(data)
|
||||||
}
|
}
|
||||||
if bytes.HasPrefix(data, []byte{255, 216}) {
|
if data != nil {
|
||||||
|
source.jpeg = bytes.HasPrefix(data, []byte{255, 216})
|
||||||
|
}
|
||||||
|
if source.jpeg {
|
||||||
formats[key] = "JPEG"
|
formats[key] = "JPEG"
|
||||||
ext := path.Ext(actual[key])
|
ext := path.Ext(actual[key])
|
||||||
if !strings.EqualFold(ext, ".jpg") && !strings.EqualFold(ext, ".jpeg") {
|
if !strings.EqualFold(ext, ".jpg") && !strings.EqualFold(ext, ".jpeg") {
|
||||||
|
if data == nil {
|
||||||
|
data, err = read(actual[key])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
stem := strings.TrimSuffix(actual[key], ext)
|
stem := strings.TrimSuffix(actual[key], ext)
|
||||||
name := stem + ".jpg"
|
name := stem + ".jpg"
|
||||||
for n := 1; reserved[strings.ToLower(name)]; n++ {
|
for n := 1; reserved[strings.ToLower(name)]; n++ {
|
||||||
@@ -262,7 +273,7 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea
|
|||||||
if seen[role] == nil {
|
if seen[role] == nil {
|
||||||
seen[role] = make(map[[32]byte]string)
|
seen[role] = make(map[[32]byte]string)
|
||||||
}
|
}
|
||||||
hash := sha256.Sum256(data)
|
hash := source.key.hash
|
||||||
if first, ok := seen[role][hash]; ok {
|
if first, ok := seen[role][hash]; ok {
|
||||||
aliases[key] = first
|
aliases[key] = first
|
||||||
removed[key] = true
|
removed[key] = true
|
||||||
|
|||||||
@@ -78,11 +78,10 @@ func (p GeometryPath) Stroke(options StrokeOptions) (GeometryPath, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
outline, err := part.stroke(options)
|
result, err = part.stroke(result, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result = append(result, outline...)
|
|
||||||
if len(result) > 1<<20 {
|
if len(result) > 1<<20 {
|
||||||
return nil, fmt.Errorf("stroke outline limit exceeded")
|
return nil, fmt.Errorf("stroke outline limit exceeded")
|
||||||
}
|
}
|
||||||
@@ -282,11 +281,10 @@ func (line geometryPolyline) dash(pattern []float64, total, offset float64) ([]g
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// stroke 用同向矩形、连接楔形及圆盘组成非零填充区域
|
// stroke 追加同向矩形、连接楔形及圆盘组成的非零填充区域
|
||||||
// 入参: options 描边样式
|
// 入参: result 已有轮廓, options 描边样式
|
||||||
// 返回: GeometryPath 填充轮廓, error 不支持的退化方形线帽
|
// 返回: GeometryPath 填充轮廓, error 不支持的退化方形线帽
|
||||||
func (line geometryPolyline) stroke(options StrokeOptions) (GeometryPath, error) {
|
func (line geometryPolyline) stroke(result GeometryPath, options StrokeOptions) (GeometryPath, error) {
|
||||||
var result GeometryPath
|
|
||||||
half := options.Width / 2
|
half := options.Width / 2
|
||||||
polygon := func(points ...Point) {
|
polygon := func(points ...Point) {
|
||||||
scale := 0.0
|
scale := 0.0
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ type pdfImporter struct {
|
|||||||
halftoneWarnings map[*pdfgo.Halftone]bool
|
halftoneWarnings map[*pdfgo.Halftone]bool
|
||||||
halftones map[string]*pdfgo.Halftone
|
halftones map[string]*pdfgo.Halftone
|
||||||
transferBackdrop bool
|
transferBackdrop bool
|
||||||
|
transferModel pdfgo.Name
|
||||||
compression CompressionOptions
|
compression CompressionOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -402,7 +402,7 @@ func (r *u3dRender) depthRange() (float64, float64, bool, error) {
|
|||||||
}
|
}
|
||||||
mesh := &r.model.Meshes[index]
|
mesh := &r.model.Meshes[index]
|
||||||
matrix := r.toCamera.multiply(instance.world)
|
matrix := r.toCamera.multiply(instance.world)
|
||||||
for j := range len(mesh.Faces) + len(mesh.Lines) {
|
for j := range len(mesh.Faces) + len(mesh.Lines) + len(mesh.Points) {
|
||||||
_, corners := mesh.primitive(j)
|
_, corners := mesh.primitive(j)
|
||||||
if j%256 == 0 {
|
if j%256 == 0 {
|
||||||
if err := r.ctx.Err(); err != nil {
|
if err := r.ctx.Err(); err != nil {
|
||||||
|
|||||||
+61
-28
@@ -56,8 +56,11 @@ type pdfCompositor struct {
|
|||||||
meshes map[pdfMeshKey][]pdfShadingPixel
|
meshes map[pdfMeshKey][]pdfShadingPixel
|
||||||
gradients map[pdfGradientKey][]pdfShadingPixel
|
gradients map[pdfGradientKey][]pdfShadingPixel
|
||||||
transfers []*pdfgo.TransferFunction
|
transfers []*pdfgo.TransferFunction
|
||||||
|
channels []uint8
|
||||||
|
transferModel pdfgo.Name
|
||||||
transferBlocked bool
|
transferBlocked bool
|
||||||
shadingSource bool
|
shadingSource bool
|
||||||
|
softMask bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfCompositeKey 区分图元的填充与描边几何
|
// pdfCompositeKey 区分图元的填充与描边几何
|
||||||
@@ -89,10 +92,18 @@ type pdfCompositeColorSampler struct {
|
|||||||
values map[float64][4]float64
|
values map[float64][4]float64
|
||||||
position pdfgo.GradientPosition
|
position pdfgo.GradientPosition
|
||||||
converter pdfgo.ColorConverter
|
converter pdfgo.ColorConverter
|
||||||
|
colorants *pdfShadingColorants
|
||||||
prepared bool
|
prepared bool
|
||||||
converted bool
|
converted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pdfImageProcessKey 隔离图像的组空间及软蒙版采样,不复用错误的原生映射
|
||||||
|
type pdfImageProcessKey struct {
|
||||||
|
image *pdfgo.Image
|
||||||
|
space *pdfgo.ColorSpace
|
||||||
|
softMask bool
|
||||||
|
}
|
||||||
|
|
||||||
// pdfCompositeCache 在单页内复用几何、图像分量及蒙版内容
|
// pdfCompositeCache 在单页内复用几何、图像分量及蒙版内容
|
||||||
type pdfCompositeCache struct {
|
type pdfCompositeCache struct {
|
||||||
geometry *renderCache[pdfCompositeSceneKey, pdfCompositeGeometry]
|
geometry *renderCache[pdfCompositeSceneKey, pdfCompositeGeometry]
|
||||||
@@ -100,6 +111,7 @@ type pdfCompositeCache struct {
|
|||||||
bounds map[pdfCompositeKey]Box
|
bounds map[pdfCompositeKey]Box
|
||||||
images map[*pdfgo.Image]*pdfgo.ImageComponents
|
images map[*pdfgo.Image]*pdfgo.ImageComponents
|
||||||
imageMatrix map[*pdfgo.ImageMark]pdfgo.Matrix
|
imageMatrix map[*pdfgo.ImageMark]pdfgo.Matrix
|
||||||
|
imageProcesses map[pdfImageProcessKey]*pdfgo.ImageProcess
|
||||||
masks map[*pdfgo.SoftMask][]pdfCompositeNode
|
masks map[*pdfgo.SoftMask][]pdfCompositeNode
|
||||||
meshes map[*pdfgo.MeshGradient][]pdfMeshTriangle
|
meshes map[*pdfgo.MeshGradient][]pdfMeshTriangle
|
||||||
patterns map[pdfgo.Paint]*pdfCompositePattern
|
patterns map[pdfgo.Paint]*pdfCompositePattern
|
||||||
@@ -346,9 +358,6 @@ func (p *pdfImporter) collectCompositeNodes(walk func(pdfgo.Visitor) error) ([]p
|
|||||||
if err := p.halftone(mark.Style); err != nil {
|
if err := p.halftone(mark.Style); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := p.resolveTransfer(&mark.Style); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nodes = append(nodes, pdfCompositeNode{path: &mark})
|
nodes = append(nodes, pdfCompositeNode{path: &mark})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -386,9 +395,6 @@ func (p *pdfImporter) collectCompositeNodes(walk func(pdfgo.Visitor) error) ([]p
|
|||||||
if err := p.halftone(mark.Style); err != nil {
|
if err := p.halftone(mark.Style); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := p.resolveTransfer(&mark.Style); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(mark.Glyphs) > 1 && (mark.Object != nil && !mark.Object.Knockout || mark.Style.AlphaIsShape || mark.Style.Fill.Alpha != 1 || mark.Style.Stroke.Alpha != 1 || mark.Style.SoftMask != nil || !pdfNormalBlend(mark.Style.BlendMode) || mark.Mode%4 == 1 || mark.Mode%4 == 2) {
|
if len(mark.Glyphs) > 1 && (mark.Object != nil && !mark.Object.Knockout || mark.Style.AlphaIsShape || mark.Style.Fill.Alpha != 1 || mark.Style.Stroke.Alpha != 1 || mark.Style.SoftMask != nil || !pdfNormalBlend(mark.Style.BlendMode) || mark.Mode%4 == 1 || mark.Mode%4 == 2) {
|
||||||
for i := range mark.Glyphs {
|
for i := range mark.Glyphs {
|
||||||
glyph := mark
|
glyph := mark
|
||||||
@@ -426,9 +432,6 @@ func (p *pdfImporter) collectCompositeNodes(walk func(pdfgo.Visitor) error) ([]p
|
|||||||
if err := p.halftone(mark.Style); err != nil {
|
if err := p.halftone(mark.Style); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := p.resolveTransfer(&mark.Style); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nodes = append(nodes, pdfCompositeNode{image: &mark})
|
nodes = append(nodes, pdfCompositeNode{image: &mark})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -499,15 +502,19 @@ func (p *pdfImporter) compositePage(space *pdfgo.ColorSpace, walk func(pdfgo.Vis
|
|||||||
p.compositeNodes = nil
|
p.compositeNodes = nil
|
||||||
p.transferBackdrop = false
|
p.transferBackdrop = false
|
||||||
p.compositeSpace = space
|
p.compositeSpace = space
|
||||||
if pdfCompositeHasTransfer(nodes) {
|
device, err := p.transferDevice(space, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.transferModel = device
|
||||||
|
hasTransfer, err := p.compositeHasTransfer(nodes, device)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hasTransfer {
|
||||||
if p.warning == nil {
|
if p.warning == nil {
|
||||||
return &pdfgo.UnsupportedError{Feature: "device transfer function rasterization"}
|
return &pdfgo.UnsupportedError{Feature: "device transfer function rasterization"}
|
||||||
}
|
}
|
||||||
if overprint, err := p.processOverprint(nodes); err != nil {
|
|
||||||
return err
|
|
||||||
} else if overprint {
|
|
||||||
return &pdfgo.UnsupportedError{Feature: "per-colorant transfer function overprinting"}
|
|
||||||
}
|
|
||||||
if err := p.compositeRegion(nil, pdfCompositeNode{group: &pdfgo.GroupMark{Alpha: 1, ColorSpace: space}, children: nodes}, space, true); err != nil {
|
if err := p.compositeRegion(nil, pdfCompositeNode{group: &pdfgo.GroupMark{Alpha: 1, ColorSpace: space}, children: nodes}, space, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -540,16 +547,16 @@ func (p *pdfImporter) compositeObjects(nodes []pdfCompositeNode) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("inspect graphic %d: %w", i+1, err)
|
return fmt.Errorf("inspect graphic %d: %w", i+1, err)
|
||||||
}
|
}
|
||||||
transfer := pdfCompositeHasTransfer([]pdfCompositeNode{node})
|
device := p.transferModel
|
||||||
|
if device == "" {
|
||||||
|
device = "DeviceRGB"
|
||||||
|
}
|
||||||
|
transfer, err := p.compositeHasTransfer([]pdfCompositeNode{node}, device)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if p.transferBackdrop || transfer {
|
if p.transferBackdrop || transfer {
|
||||||
direct = false
|
direct = false
|
||||||
for _, source := range [][]pdfCompositeNode{p.compositeNodes, {node}} {
|
|
||||||
if overprint, err := p.processOverprint(source); err != nil {
|
|
||||||
return err
|
|
||||||
} else if overprint {
|
|
||||||
return &pdfgo.UnsupportedError{Feature: "per-colorant transfer function overprinting"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if direct {
|
if direct {
|
||||||
if err := node.emit(p.visitor()); err != nil {
|
if err := node.emit(p.visitor()); err != nil {
|
||||||
@@ -785,7 +792,19 @@ func (p *pdfImporter) compositeRegion(backdrop []pdfCompositeNode, node pdfCompo
|
|||||||
scratch := &pdfCompositeScratch{}
|
scratch := &pdfCompositeScratch{}
|
||||||
var transfers []*pdfgo.TransferFunction
|
var transfers []*pdfgo.TransferFunction
|
||||||
var previousTransfers []*pdfgo.TransferFunction
|
var previousTransfers []*pdfgo.TransferFunction
|
||||||
if pdfCompositeHasTransfer(backdrop) || pdfCompositeHasTransfer([]pdfCompositeNode{node}) {
|
device := p.transferModel
|
||||||
|
if device == "" {
|
||||||
|
device = "DeviceRGB"
|
||||||
|
}
|
||||||
|
hasTransfer := false
|
||||||
|
for _, nodes := range [][]pdfCompositeNode{backdrop, {node}} {
|
||||||
|
found, err := p.compositeHasTransfer(nodes, device)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hasTransfer = hasTransfer || found
|
||||||
|
}
|
||||||
|
if hasTransfer {
|
||||||
transfers = make([]*pdfgo.TransferFunction, len(buffer))
|
transfers = make([]*pdfgo.TransferFunction, len(buffer))
|
||||||
previousTransfers = make([]*pdfgo.TransferFunction, len(buffer))
|
previousTransfers = make([]*pdfgo.TransferFunction, len(buffer))
|
||||||
}
|
}
|
||||||
@@ -799,6 +818,7 @@ func (p *pdfImporter) compositeRegion(backdrop []pdfCompositeNode, node pdfCompo
|
|||||||
c := pdfCompositor{importer: p, box: Box{X: box.X + float64(x)*step, Y: box.Y + float64(y)*step, W: float64(width) * step, H: float64(height) * step}, width: width, height: height, inverse: inverse, cache: cache, scratch: scratch, masks: map[*pdfgo.SoftMask][]float64{}}
|
c := pdfCompositor{importer: p, box: Box{X: box.X + float64(x)*step, Y: box.Y + float64(y)*step, W: float64(width) * step, H: float64(height) * step}, width: width, height: height, inverse: inverse, cache: cache, scratch: scratch, masks: map[*pdfgo.SoftMask][]float64{}}
|
||||||
if transfers != nil {
|
if transfers != nil {
|
||||||
c.transfers = transfers[:width*height]
|
c.transfers = transfers[:width*height]
|
||||||
|
c.transferModel = device
|
||||||
clear(c.transfers)
|
clear(c.transfers)
|
||||||
}
|
}
|
||||||
pixels := buffer[:width*height]
|
pixels := buffer[:width*height]
|
||||||
@@ -824,7 +844,18 @@ func (p *pdfImporter) compositeRegion(backdrop []pdfCompositeNode, node pdfCompo
|
|||||||
}
|
}
|
||||||
var rgb [3]float64
|
var rgb [3]float64
|
||||||
var err error
|
var err error
|
||||||
if c.transfers != nil {
|
if c.transfers != nil && device == "DeviceCMYK" {
|
||||||
|
values := pixel.values
|
||||||
|
if flatten {
|
||||||
|
for j := range values {
|
||||||
|
values[j] *= pixel.alpha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values, err = c.transfers[i].Apply(values[:], device, "")
|
||||||
|
if err == nil {
|
||||||
|
rgb, err = pdfCompositeOutputColor(space, values)
|
||||||
|
}
|
||||||
|
} else if c.transfers != nil {
|
||||||
rgb, err = space.RGB(pixel.values[:space.Components()], "RelativeColorimetric")
|
rgb, err = space.RGB(pixel.values[:space.Components()], "RelativeColorimetric")
|
||||||
} else {
|
} else {
|
||||||
rgb, err = pdfCompositeOutputColor(space, pixel.values)
|
rgb, err = pdfCompositeOutputColor(space, pixel.values)
|
||||||
@@ -834,12 +865,14 @@ func (p *pdfImporter) compositeRegion(backdrop []pdfCompositeNode, node pdfCompo
|
|||||||
}
|
}
|
||||||
alpha := pixel.alpha
|
alpha := pixel.alpha
|
||||||
if flatten {
|
if flatten {
|
||||||
for j := range rgb {
|
if c.transfers == nil || device != "DeviceCMYK" {
|
||||||
rgb[j] = pixel.alpha*rgb[j] + 1 - pixel.alpha
|
for j := range rgb {
|
||||||
|
rgb[j] = pixel.alpha*rgb[j] + 1 - pixel.alpha
|
||||||
|
}
|
||||||
}
|
}
|
||||||
alpha = 1
|
alpha = 1
|
||||||
}
|
}
|
||||||
if c.transfers != nil && c.transfers[i] != nil {
|
if c.transfers != nil && device != "DeviceCMYK" && c.transfers[i] != nil {
|
||||||
values, err := c.transfers[i].Apply(rgb[:], "DeviceRGB", "")
|
values, err := c.transfers[i].Apply(rgb[:], "DeviceRGB", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+219
-101
@@ -65,12 +65,18 @@ func (c *pdfCompositor) drawMark(node pdfCompositeNode, pixels []pdfCompositePix
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var initial []pdfCompositePixel
|
var initial []pdfCompositePixel
|
||||||
|
var initialChannels []uint8
|
||||||
result := pixels
|
result := pixels
|
||||||
grouped := fill && stroke && (style.FillOverprint || style.StrokeOverprint) && style.Fill.Alpha == style.Stroke.Alpha
|
grouped := fill && stroke && (style.FillOverprint || style.StrokeOverprint) && style.Fill.Alpha == style.Stroke.Alpha
|
||||||
if fill && stroke {
|
if fill && stroke {
|
||||||
initial = c.acquirePixels(len(pixels))
|
initial = c.acquirePixels(len(pixels))
|
||||||
defer c.releasePixels(initial)
|
defer c.releasePixels(initial)
|
||||||
copy(initial, pixels)
|
copy(initial, pixels)
|
||||||
|
if c.channels != nil && !grouped {
|
||||||
|
initialChannels = c.acquireChannels(len(c.channels))
|
||||||
|
defer c.releaseChannels(initialChannels)
|
||||||
|
copy(initialChannels, c.channels)
|
||||||
|
}
|
||||||
if grouped {
|
if grouped {
|
||||||
result = c.acquirePixels(len(pixels))
|
result = c.acquirePixels(len(pixels))
|
||||||
defer c.releasePixels(result)
|
defer c.releasePixels(result)
|
||||||
@@ -96,7 +102,7 @@ func (c *pdfCompositor) drawMark(node pdfCompositeNode, pixels []pdfCompositePix
|
|||||||
} else if outline {
|
} else if outline {
|
||||||
knockout = initial
|
knockout = initial
|
||||||
}
|
}
|
||||||
if err := c.drawPaint(node, outline, paint, mode, overprint, paintMask, knockout, result, space); err != nil {
|
if err := c.drawPaint(node, outline, paint, mode, overprint, paintMask, knockout, initialChannels, result, space); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,9 +113,9 @@ func (c *pdfCompositor) drawMark(node pdfCompositeNode, pixels []pdfCompositePix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// drawPaint 结合几何覆盖与源颜色绘制一个填充或描边
|
// drawPaint 结合几何覆盖与源颜色绘制一个填充或描边
|
||||||
// 入参: node 图元, outline 描边标志, paint 画刷, mode 混合模式, overprint 叠印标志, mask 蒙版, initial 挖空初始背景, pixels 输出, space 混合空间
|
// 入参: node 图元, outline 描边标志, paint 画刷, mode 混合模式, overprint 叠印标志, mask 蒙版, initial 挖空初始背景, initialChannels 初始通道, pixels 输出, space 混合空间
|
||||||
// 返回: error 几何或颜色错误
|
// 返回: error 几何或颜色错误
|
||||||
func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdfgo.Paint, mode pdfgo.Name, overprint bool, mask []float64, initial, pixels []pdfCompositePixel, space *pdfgo.ColorSpace) error {
|
func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdfgo.Paint, mode pdfgo.Name, overprint bool, mask []float64, initial []pdfCompositePixel, initialChannels []uint8, pixels []pdfCompositePixel, space *pdfgo.ColorSpace) error {
|
||||||
coverage, err := c.coverage(node, outline)
|
coverage, err := c.coverage(node, outline)
|
||||||
if err != nil || coverage == nil {
|
if err != nil || coverage == nil {
|
||||||
return err
|
return err
|
||||||
@@ -123,40 +129,78 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if node.image != nil && (node.image.Image.ImageMask || node.image.Image.Mask != nil) {
|
}
|
||||||
soft, err := node.image.Image.HasSoftMask()
|
if node.image != nil && (node.image.Image.ImageMask || node.image.Image.Mask != nil) {
|
||||||
if err != nil {
|
soft, err := node.image.Image.HasSoftMask()
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
imageShape = !soft
|
|
||||||
}
|
}
|
||||||
|
imageShape = !soft
|
||||||
}
|
}
|
||||||
var processMask [4]bool
|
var processMask [4]bool
|
||||||
processOverprint := false
|
processOverprint := false
|
||||||
if overprint && space.Model == "DeviceCMYK" && !space.Calibrated() {
|
var nativePaint *pdfgo.Paint
|
||||||
if node.image != nil {
|
if !c.softMask && space.Model == "DeviceCMYK" && !space.Calibrated() {
|
||||||
processMask, processOverprint, err = pdfImageProcessColorants(node.image.Image)
|
if node.image != nil && !node.image.Image.ImageMask {
|
||||||
if err != nil {
|
if overprint {
|
||||||
return err
|
processMask, processOverprint, err = pdfImageProcessColorants(node.image.Image)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
processMask, processOverprint = paint.Process.CMYKMask()
|
if overprint {
|
||||||
|
processMask, processOverprint = paint.Process.CMYKMask()
|
||||||
|
}
|
||||||
|
if paint.Colorant != nil {
|
||||||
|
nativePaint, processMask, err = pdfCompositeNativePaint(paint, space, style.RenderingIntent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
processOverprint = overprint && nativePaint != nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var colorants *pdfShadingColorants
|
||||||
|
if node.image == nil || node.image.Image.ImageMask {
|
||||||
|
colorants = pdfPrepareShadingColorants(paint, space, c.softMask)
|
||||||
|
if colorants != nil && overprint {
|
||||||
|
processMask, processOverprint = colorants.process.Mask(), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
step := 25.4 / c.importer.rasterDPI
|
step := 25.4 / c.importer.rasterDPI
|
||||||
uniform := node.image == nil && paint.Axial == nil && paint.Radial == nil && paint.Function == nil && paint.Tiling == nil && paint.Mesh == nil && paint.Shading == nil
|
solid := paint.Axial == nil && paint.Radial == nil && paint.Function == nil && paint.Tiling == nil && paint.Mesh == nil && paint.Shading == nil
|
||||||
compatible := overprint && !processOverprint && style.OverprintMode == 1 && uniform && paint.SourceSpace == "DeviceCMYK" && space.Model == "DeviceCMYK" && !space.Calibrated()
|
uniform := node.image == nil && solid
|
||||||
|
compatible := overprint && !processOverprint && style.OverprintMode == 1 && solid && (node.image == nil || node.image.Image.ImageMask) && paint.SourceSpace == "DeviceCMYK" && space.Model == "DeviceCMYK" && !space.Calibrated()
|
||||||
|
sourceSpace := paint.SourceSpace
|
||||||
|
if node.image != nil && !node.image.Image.ImageMask && transfer != nil && c.transferModel == "DeviceCMYK" {
|
||||||
|
sourceSpace, err = node.image.Image.ColorSpaceFamily()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
var marked *[4]bool
|
var marked *[4]bool
|
||||||
if processOverprint {
|
if processOverprint {
|
||||||
marked = &processMask
|
marked = &processMask
|
||||||
}
|
}
|
||||||
|
if transfer != nil && c.transferModel == "DeviceCMYK" && !processOverprint && sourceSpace == "DeviceGray" {
|
||||||
|
transfer = transfer.SelectChannels([4]bool{false, false, false, true})
|
||||||
|
}
|
||||||
|
var transferVariants [16]*pdfgo.TransferFunction
|
||||||
|
var transferSelected [16]bool
|
||||||
var pattern []pdfCompositePixel
|
var pattern []pdfCompositePixel
|
||||||
|
var patternChannels []uint8
|
||||||
|
if (paint.Shading != nil || paint.Tiling != nil) && (node.image == nil || node.image.Image.ImageMask) && (c.channels != nil || c.transfers != nil) {
|
||||||
|
patternChannels = c.acquireChannels(len(pixels))
|
||||||
|
defer c.releaseChannels(patternChannels)
|
||||||
|
clear(patternChannels)
|
||||||
|
}
|
||||||
if paint.Shading != nil && (node.image == nil || node.image.Image.ImageMask) {
|
if paint.Shading != nil && (node.image == nil || node.image.Image.ImageMask) {
|
||||||
backdrop := pixels
|
backdrop := pixels
|
||||||
if initial != nil {
|
if initial != nil {
|
||||||
backdrop = initial
|
backdrop = initial
|
||||||
}
|
}
|
||||||
pattern, err = c.shadingPattern(paint.Shading, node, outline, backdrop, space)
|
pattern, err = c.shadingPattern(paint.Shading, node, outline, backdrop, space, patternChannels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -167,7 +211,7 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
if initial != nil {
|
if initial != nil {
|
||||||
backdrop = initial
|
backdrop = initial
|
||||||
}
|
}
|
||||||
pattern, err = c.tiling(paint, backdrop, space)
|
pattern, err = c.tiling(paint, backdrop, space, patternChannels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -188,11 +232,11 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
var uniformValues [4]float64
|
var uniformValues [4]float64
|
||||||
uniformReady := false
|
uniformReady := false
|
||||||
var sampler *pdfCompositeColorSampler
|
var sampler *pdfCompositeColorSampler
|
||||||
if g := paint.Axial; node.image == nil && shading == nil && g != nil && g.Bounds == nil && g.Background == nil {
|
if g := paint.Axial; (node.image == nil || node.image.Image.ImageMask) && shading == nil && g != nil && g.Bounds == nil && g.Background == nil {
|
||||||
sampler = &pdfCompositeColorSampler{paint: paint, space: space, intent: style.RenderingIntent, conversion: style.ColorConversion}
|
sampler = &pdfCompositeColorSampler{paint: paint, space: space, intent: style.RenderingIntent, conversion: style.ColorConversion, colorants: colorants}
|
||||||
}
|
}
|
||||||
if g := paint.Radial; node.image == nil && shading == nil && g != nil && g.Bounds == nil && g.Background == nil {
|
if g := paint.Radial; (node.image == nil || node.image.Image.ImageMask) && shading == nil && g != nil && g.Bounds == nil && g.Background == nil {
|
||||||
sampler = &pdfCompositeColorSampler{paint: paint, space: space, intent: style.RenderingIntent, conversion: style.ColorConversion}
|
sampler = &pdfCompositeColorSampler{paint: paint, space: space, intent: style.RenderingIntent, conversion: style.ColorConversion, colorants: colorants}
|
||||||
}
|
}
|
||||||
bounds, err := c.geometryBounds(node, outline)
|
bounds, err := c.geometryBounds(node, outline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -217,6 +261,9 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
var values [4]float64
|
var values [4]float64
|
||||||
if pattern != nil {
|
if pattern != nil {
|
||||||
sample := pattern[index]
|
sample := pattern[index]
|
||||||
|
if patternChannels != nil && patternChannels[index]&pdfTransferShape == 0 {
|
||||||
|
selectionShape = 0
|
||||||
|
}
|
||||||
values = sample.values
|
values = sample.values
|
||||||
if paint.Shading != nil {
|
if paint.Shading != nil {
|
||||||
shape = sample.shape
|
shape = sample.shape
|
||||||
@@ -231,9 +278,11 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
var alpha float64
|
var alpha float64
|
||||||
_, alpha, err = c.imageColor(node.image, point, space)
|
_, alpha, err = c.imageColor(node.image, point, space)
|
||||||
if imageShape {
|
if imageShape {
|
||||||
selectionShape = alpha
|
selectionShape *= alpha
|
||||||
|
shape *= alpha
|
||||||
|
} else {
|
||||||
|
opacity *= alpha
|
||||||
}
|
}
|
||||||
opacity *= alpha
|
|
||||||
}
|
}
|
||||||
} else if shading != nil {
|
} else if shading != nil {
|
||||||
values = shading[index].values
|
values = shading[index].values
|
||||||
@@ -247,9 +296,9 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
var alpha float64
|
var alpha float64
|
||||||
_, alpha, err = c.imageColor(node.image, point, space)
|
_, alpha, err = c.imageColor(node.image, point, space)
|
||||||
if imageShape {
|
if imageShape {
|
||||||
selectionShape = alpha
|
selectionShape *= alpha
|
||||||
}
|
}
|
||||||
if c.shadingSource {
|
if c.shadingSource || imageShape {
|
||||||
shape *= alpha
|
shape *= alpha
|
||||||
} else {
|
} else {
|
||||||
opacity *= alpha
|
opacity *= alpha
|
||||||
@@ -257,7 +306,11 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
}
|
}
|
||||||
} else if uniform {
|
} else if uniform {
|
||||||
if !uniformReady {
|
if !uniformReady {
|
||||||
uniformValues, _, err = pdfCompositeColor(paint, pdfgo.Point{}, space, style.RenderingIntent, style.ColorConversion)
|
if nativePaint != nil {
|
||||||
|
uniformValues = nativePaint.Values
|
||||||
|
} else {
|
||||||
|
uniformValues, _, err = pdfCompositeColor(paint, pdfgo.Point{}, space, style.RenderingIntent, style.ColorConversion)
|
||||||
|
}
|
||||||
uniformReady = true
|
uniformReady = true
|
||||||
}
|
}
|
||||||
values = uniformValues
|
values = uniformValues
|
||||||
@@ -266,10 +319,20 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
if node.image != nil {
|
if node.image != nil {
|
||||||
var alpha float64
|
var alpha float64
|
||||||
values, alpha, err = c.imageColor(node.image, point, space)
|
values, alpha, err = c.imageColor(node.image, point, space)
|
||||||
if imageShape {
|
if nativePaint != nil {
|
||||||
selectionShape = alpha
|
values = nativePaint.Values
|
||||||
}
|
}
|
||||||
if c.shadingSource {
|
if err == nil && sampler != nil {
|
||||||
|
var visible bool
|
||||||
|
values, visible, err = sampler.color(point)
|
||||||
|
if !visible {
|
||||||
|
alpha = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if imageShape {
|
||||||
|
selectionShape *= alpha
|
||||||
|
}
|
||||||
|
if c.shadingSource || imageShape {
|
||||||
shape *= alpha
|
shape *= alpha
|
||||||
} else {
|
} else {
|
||||||
opacity *= alpha
|
opacity *= alpha
|
||||||
@@ -306,14 +369,58 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pixels[index].shape += shape * (1 - pixels[index].shape)
|
pixels[index].shape += shape * (1 - pixels[index].shape)
|
||||||
|
bits := pdfTransferChannels
|
||||||
|
if (c.channels != nil || transfer != nil && c.transferModel == "DeviceCMYK") && (compatible || marked != nil || patternChannels != nil) {
|
||||||
|
bits = 0
|
||||||
|
for j, selected := range pdfPaintChannels(values, compatible, marked) {
|
||||||
|
if selected {
|
||||||
|
bits |= 1 << j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if patternChannels != nil {
|
||||||
|
bits &= patternChannels[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.channels != nil && shape != 0 && selectionShape != 0 {
|
||||||
|
if initialChannels != nil && initial != nil {
|
||||||
|
c.channels[index] = initialChannels[index]
|
||||||
|
}
|
||||||
|
c.channels[index] |= bits | pdfTransferShape
|
||||||
|
}
|
||||||
if c.transfers != nil && shape != 0 && selectionShape != 0 {
|
if c.transfers != nil && shape != 0 && selectionShape != 0 {
|
||||||
c.transfers[index] = transfer
|
selected := transfer
|
||||||
|
if transfer != nil && c.transferModel == "DeviceCMYK" && bits != pdfTransferChannels {
|
||||||
|
if !transferSelected[bits] {
|
||||||
|
var channels [4]bool
|
||||||
|
for j := range channels {
|
||||||
|
channels[j] = bits&(1<<j) != 0
|
||||||
|
}
|
||||||
|
transferVariants[bits] = transfer.SelectChannels(channels)
|
||||||
|
transferSelected[bits] = true
|
||||||
|
}
|
||||||
|
selected = transferVariants[bits]
|
||||||
|
}
|
||||||
|
c.transfers[index] = selected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pdfCompositeNativePaint 使用库层色料定义求值设备四色,不以备用色代替原生浓度
|
||||||
|
// 入参: paint 源画刷, space 设备四色空间, intent 渲染意图
|
||||||
|
// 返回: *pdfgo.Paint 原生画刷或nil, [4]bool 显式通道, error 色料定义错误
|
||||||
|
func pdfCompositeNativePaint(paint pdfgo.Paint, space *pdfgo.ColorSpace, intent pdfgo.Name) (*pdfgo.Paint, [4]bool, error) {
|
||||||
|
if paint.Colorant == nil {
|
||||||
|
return nil, [4]bool{}, nil
|
||||||
|
}
|
||||||
|
result, err := paint.Colorant.Space.ResolveInGroup(paint.Colorant.Tints, &pdfgo.ColorantDevice{Space: space}, space, intent, false)
|
||||||
|
if err != nil || len(result.Alternates) != 0 {
|
||||||
|
return nil, [4]bool{}, err
|
||||||
|
}
|
||||||
|
return result.Process, result.ProcessMask, nil
|
||||||
|
}
|
||||||
|
|
||||||
// pdfCompositeInterpolate 按形状覆盖混合预乘颜色及累计透明度
|
// pdfCompositeInterpolate 按形状覆盖混合预乘颜色及累计透明度
|
||||||
// 入参: target 原像素与输出, source 替换像素, shape 覆盖率
|
// 入参: target 原像素与输出, source 替换像素, shape 覆盖率
|
||||||
// 返回: error 像素或覆盖率错误
|
// 返回: error 像素或覆盖率错误
|
||||||
@@ -389,15 +496,26 @@ func (c *pdfCompositor) drawGroup(node pdfCompositeNode, pixels []pdfCompositePi
|
|||||||
result := c.acquirePixels(len(initial))
|
result := c.acquirePixels(len(initial))
|
||||||
defer c.releasePixels(result)
|
defer c.releasePixels(result)
|
||||||
copy(result, initial)
|
copy(result, initial)
|
||||||
|
local := *c
|
||||||
|
if c.channels != nil {
|
||||||
|
local.channels = c.acquireChannels(len(c.channels))
|
||||||
|
defer c.releaseChannels(local.channels)
|
||||||
|
if g.Isolated {
|
||||||
|
clear(local.channels)
|
||||||
|
} else {
|
||||||
|
copy(local.channels, c.channels)
|
||||||
|
}
|
||||||
|
}
|
||||||
if g.Knockout {
|
if g.Knockout {
|
||||||
if err := c.drawKnockout(node.children, initial, result, space); err != nil {
|
if err := local.drawKnockout(node.children, initial, result, space); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := c.draw(node.children, result, space); err != nil {
|
if err := local.draw(node.children, result, space); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.gradients, c.meshes = local.gradients, local.meshes
|
||||||
mask, err := c.mask(g.SoftMask, parent)
|
mask, err := c.mask(g.SoftMask, parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -415,7 +533,26 @@ func (c *pdfCompositor) drawGroup(node pdfCompositeNode, pixels []pdfCompositePi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pdfCompositeGroupRegion(c.importer.ctx, pixels, initial, result, c.width, region, space, parent, g.Alpha, mask, g.BlendMode, g.RenderingIntent, g.ColorConversion)
|
if err := pdfCompositeGroupRegion(c.importer.ctx, pixels, initial, result, c.width, region, space, parent, g.Alpha, mask, g.BlendMode, g.RenderingIntent, g.ColorConversion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.channels != nil {
|
||||||
|
independent := g.Isolated || !space.Equal(parent)
|
||||||
|
for y := region.Min.Y; y < region.Max.Y; y++ {
|
||||||
|
if err := c.importer.ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i, end := y*c.width+region.Min.X, y*c.width+region.Max.X; i < end; i++ {
|
||||||
|
if result[i].shape != 0 && local.channels[i]&pdfTransferShape != 0 {
|
||||||
|
c.channels[i] = local.channels[i]
|
||||||
|
if independent {
|
||||||
|
c.channels[i] |= pdfTransferChannels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawKnockout 将每个对象与组初始背景合成,再按对象形状替换先前对象
|
// drawKnockout 将每个对象与组初始背景合成,再按对象形状替换先前对象
|
||||||
@@ -428,6 +565,16 @@ func (c *pdfCompositor) drawKnockout(nodes []pdfCompositeNode, initial, pixels [
|
|||||||
candidate := c.acquirePixels(len(initial))
|
candidate := c.acquirePixels(len(initial))
|
||||||
defer c.releasePixels(candidate)
|
defer c.releasePixels(candidate)
|
||||||
clear(candidate)
|
clear(candidate)
|
||||||
|
local := *c
|
||||||
|
var initialChannels []uint8
|
||||||
|
if c.channels != nil {
|
||||||
|
initialChannels = c.acquireChannels(len(c.channels))
|
||||||
|
defer c.releaseChannels(initialChannels)
|
||||||
|
copy(initialChannels, c.channels)
|
||||||
|
local.channels = c.acquireChannels(len(c.channels))
|
||||||
|
defer c.releaseChannels(local.channels)
|
||||||
|
clear(local.channels)
|
||||||
|
}
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
var box Box
|
var box Box
|
||||||
var err error
|
var err error
|
||||||
@@ -449,8 +596,11 @@ func (c *pdfCompositor) drawKnockout(nodes []pdfCompositeNode, initial, pixels [
|
|||||||
}
|
}
|
||||||
start, end := y*c.width+region.Min.X, y*c.width+region.Max.X
|
start, end := y*c.width+region.Min.X, y*c.width+region.Max.X
|
||||||
copy(candidate[start:end], initial[start:end])
|
copy(candidate[start:end], initial[start:end])
|
||||||
|
if initialChannels != nil {
|
||||||
|
copy(local.channels[start:end], initialChannels[start:end])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := c.draw([]pdfCompositeNode{node}, candidate, space); err != nil {
|
if err := local.draw([]pdfCompositeNode{node}, candidate, space); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for y := region.Min.Y; y < region.Max.Y; y++ {
|
for y := region.Min.Y; y < region.Max.Y; y++ {
|
||||||
@@ -459,9 +609,13 @@ func (c *pdfCompositor) drawKnockout(nodes []pdfCompositeNode, initial, pixels [
|
|||||||
if err := pdfCompositeKnockout(&pixels[i], initial[i], candidate[i]); err != nil {
|
if err := pdfCompositeKnockout(&pixels[i], initial[i], candidate[i]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if c.channels != nil && candidate[i].shape != 0 {
|
||||||
|
c.channels[i] = local.channels[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.gradients, c.meshes = local.gradients, local.meshes
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,6 +734,8 @@ func (c *pdfCompositor) mask(mask *pdfgo.SoftMask, inherited *pdfgo.ColorSpace)
|
|||||||
}
|
}
|
||||||
local := *c
|
local := *c
|
||||||
local.transfers = nil
|
local.transfers = nil
|
||||||
|
local.channels = nil
|
||||||
|
local.softMask = true
|
||||||
if err := local.draw(nodes, pixels, space); err != nil {
|
if err := local.draw(nodes, pixels, space); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -643,34 +799,25 @@ func (c *pdfCompositor) imageColor(mark *pdfgo.ImageMark, point pdfgo.Point, spa
|
|||||||
c.cache.images[mark.Image] = source
|
c.cache.images[mark.Image] = source
|
||||||
}
|
}
|
||||||
x, y := point.X*float64(source.Rect.Dx())-.5, (1-point.Y)*float64(source.Rect.Dy())-.5
|
x, y := point.X*float64(source.Rect.Dx())-.5, (1-point.Y)*float64(source.Rect.Dy())-.5
|
||||||
_, nativeProcess := source.Process.CMYKMask()
|
key := pdfImageProcessKey{image: mark.Image, space: space, softMask: c.softMask}
|
||||||
process := mark.Style.FillOverprint && space.Model == "DeviceCMYK" && !space.Calibrated() && len(source.Colorants) > 0
|
process, prepared := c.cache.imageProcesses[key]
|
||||||
var indices [4]int
|
if !prepared {
|
||||||
if process && !nativeProcess {
|
var err error
|
||||||
for i, name := range source.Colorants {
|
process, err = source.PrepareProcess(&pdfgo.ColorantDevice{Space: space}, space, c.softMask)
|
||||||
index := -1
|
if err != nil {
|
||||||
for j, known := range []pdfgo.Name{"Cyan", "Magenta", "Yellow", "Black"} {
|
return [4]float64{}, 0, err
|
||||||
if name == known {
|
|
||||||
index = j
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if index < 0 {
|
|
||||||
process = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indices[i] = index
|
|
||||||
}
|
}
|
||||||
|
if c.cache.imageProcesses == nil {
|
||||||
|
c.cache.imageProcesses = make(map[pdfImageProcessKey]*pdfgo.ImageProcess)
|
||||||
|
}
|
||||||
|
c.cache.imageProcesses[key] = process
|
||||||
}
|
}
|
||||||
sample := func(x, y int) ([4]float64, float64) {
|
sample := func(x, y int) ([4]float64, float64) {
|
||||||
x, y = max(0, min(source.Rect.Dx()-1, x)), max(0, min(source.Rect.Dy()-1, y))
|
x, y = source.Rect.Min.X+max(0, min(source.Rect.Dx()-1, x)), source.Rect.Min.Y+max(0, min(source.Rect.Dy()-1, y))
|
||||||
values, alpha := source.ValuesAt(x, y)
|
if process != nil {
|
||||||
if process && !nativeProcess {
|
return process.ValuesAt(x, y)
|
||||||
values = [4]float64{}
|
|
||||||
for j, tint := range source.TintsAt(x, y) {
|
|
||||||
values[indices[j]] = float64(tint) / 65535
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return values, alpha
|
return source.ValuesAt(x, y)
|
||||||
}
|
}
|
||||||
values, alpha := sample(int(math.Floor(x+.5)), int(math.Floor(y+.5)))
|
values, alpha := sample(int(math.Floor(x+.5)), int(math.Floor(y+.5)))
|
||||||
if mark.Image.Interpolate {
|
if mark.Image.Interpolate {
|
||||||
@@ -712,7 +859,7 @@ func (c *pdfCompositor) imageColor(mark *pdfgo.ImageMark, point pdfgo.Point, spa
|
|||||||
}
|
}
|
||||||
return color, alpha * (1 - values[0]), err
|
return color, alpha * (1 - values[0]), err
|
||||||
}
|
}
|
||||||
if process || space.Equal(source.Space) {
|
if process != nil || space.Equal(source.Space) {
|
||||||
return values, alpha, nil
|
return values, alpha, nil
|
||||||
}
|
}
|
||||||
values, err := space.ConvertWith(values[:source.Space.Components()], source.Space, mark.Style.RenderingIntent, mark.Style.ColorConversion)
|
values, err := space.ConvertWith(values[:source.Space.Components()], source.Space, mark.Style.RenderingIntent, mark.Style.ColorConversion)
|
||||||
@@ -723,35 +870,14 @@ func (c *pdfCompositor) imageColor(mark *pdfgo.ImageMark, point pdfgo.Point, spa
|
|||||||
// 入参: image PDF图像
|
// 入参: image PDF图像
|
||||||
// 返回: [4]bool 参与绘制的四色通道, bool 是否全部属于印刷原色, error 色空间错误
|
// 返回: [4]bool 参与绘制的四色通道, bool 是否全部属于印刷原色, error 色空间错误
|
||||||
func pdfImageProcessColorants(image *pdfgo.Image) ([4]bool, bool, error) {
|
func pdfImageProcessColorants(image *pdfgo.Image) ([4]bool, bool, error) {
|
||||||
var mask [4]bool
|
source, err := image.ColorantSpace()
|
||||||
process, err := image.ProcessColorants()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mask, false, err
|
return [4]bool{}, false, err
|
||||||
}
|
}
|
||||||
if mask, native := process.CMYKMask(); native {
|
space := &pdfgo.ColorSpace{Model: "DeviceCMYK"}
|
||||||
return mask, true, nil
|
process := source.PrepareProcess(&pdfgo.ColorantDevice{Space: space}, space, false)
|
||||||
}
|
mask := process.Mask()
|
||||||
names, err := image.Colorants()
|
return mask, mask != ([4]bool{}), nil
|
||||||
if err != nil {
|
|
||||||
return mask, false, err
|
|
||||||
}
|
|
||||||
if len(names) == 0 {
|
|
||||||
return mask, false, nil
|
|
||||||
}
|
|
||||||
for _, name := range names {
|
|
||||||
found := false
|
|
||||||
for j, known := range []pdfgo.Name{"Cyan", "Magenta", "Yellow", "Black"} {
|
|
||||||
if name == known {
|
|
||||||
mask[j] = true
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return [4]bool{}, false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mask, true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfCompositeColor 将原始画刷变换到混合空间,渐变在源空间求值
|
// pdfCompositeColor 将原始画刷变换到混合空间,渐变在源空间求值
|
||||||
@@ -882,14 +1008,16 @@ func (s *pdfCompositeColorSampler) color(point pdfgo.Point) ([4]float64, bool, e
|
|||||||
var source *pdfgo.ColorSpace
|
var source *pdfgo.ColorSpace
|
||||||
var intent pdfgo.Name
|
var intent pdfgo.Name
|
||||||
var err error
|
var err error
|
||||||
if g != nil {
|
if s.colorants != nil {
|
||||||
|
values, err = s.colorants.at(position)
|
||||||
|
} else if g != nil {
|
||||||
values, err = g.ValuesAt(position)
|
values, err = g.ValuesAt(position)
|
||||||
source, intent = g.Space, g.Intent
|
source, intent = g.Space, g.Intent
|
||||||
} else {
|
} else {
|
||||||
values, err = r.ValuesAt(position)
|
values, err = r.ValuesAt(position)
|
||||||
source, intent = r.Space, r.Intent
|
source, intent = r.Space, r.Intent
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil && s.colorants == nil {
|
||||||
if !s.converted {
|
if !s.converted {
|
||||||
s.converter, err = s.space.PrepareConversion(source, intent, s.conversion)
|
s.converter, err = s.space.PrepareConversion(source, intent, s.conversion)
|
||||||
s.converted = err == nil
|
s.converted = err == nil
|
||||||
@@ -917,17 +1045,7 @@ func pdfCompositePaintOver(backdrop *pdfCompositePixel, source [4]float64, alpha
|
|||||||
if alpha == 0 {
|
if alpha == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
mask := [4]bool{true, true, true, true}
|
mask := pdfPaintChannels(source, compatible, marked)
|
||||||
if marked != nil {
|
|
||||||
mask = *marked
|
|
||||||
}
|
|
||||||
if compatible {
|
|
||||||
for i := range source {
|
|
||||||
if source[i] == 0 {
|
|
||||||
mask[i] = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
values, combined, err := space.CompositeOverprint(backdrop.values[:space.Components()], source[:space.Components()], backdrop.alpha, alpha, mode, mask[:space.Components()])
|
values, combined, err := space.CompositeOverprint(backdrop.values[:space.Components()], source[:space.Components()], backdrop.alpha, alpha, mode, mask[:space.Components()])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ type pdfCompositePattern struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tiling 将图案作为非隔离组绘制,保留背景贡献及单元间隙
|
// tiling 将图案作为非隔离组绘制,保留背景贡献及单元间隙
|
||||||
// 入参: paint 图案画刷, backdrop 初始背景, space 混合空间
|
// 入参: paint 图案画刷, backdrop 初始背景, space 混合空间, channels 可选源通道输出
|
||||||
// 返回: []pdfCompositePixel 去除背景贡献的图案像素, error 解析或绘制错误
|
// 返回: []pdfCompositePixel 去除背景贡献的图案像素, error 解析或绘制错误
|
||||||
func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel, space *pdfgo.ColorSpace) ([]pdfCompositePixel, error) {
|
func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel, space *pdfgo.ColorSpace, channels []uint8) ([]pdfCompositePixel, error) {
|
||||||
source := paint.Tiling
|
source := paint.Tiling
|
||||||
matrix := c.importer.matrix.Mul(source.Matrix)
|
matrix := c.importer.matrix.Mul(source.Matrix)
|
||||||
inverse, ok := matrix.Inverse()
|
inverse, ok := matrix.Inverse()
|
||||||
@@ -74,6 +74,11 @@ func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel,
|
|||||||
}
|
}
|
||||||
candidate := c.acquirePixels(len(backdrop))
|
candidate := c.acquirePixels(len(backdrop))
|
||||||
defer c.releasePixels(candidate)
|
defer c.releasePixels(candidate)
|
||||||
|
var candidateChannels []uint8
|
||||||
|
if channels != nil {
|
||||||
|
candidateChannels = c.acquireChannels(len(backdrop))
|
||||||
|
defer c.releaseChannels(candidateChannels)
|
||||||
|
}
|
||||||
disjoint := source.BBox.XMax-source.BBox.XMin <= xstep && source.BBox.YMax-source.BBox.YMin <= ystep
|
disjoint := source.BBox.XMax-source.BBox.XMin <= xstep && source.BBox.YMax-source.BBox.YMin <= ystep
|
||||||
pixelStep := 25.4 / c.importer.rasterDPI
|
pixelStep := 25.4 / c.importer.rasterDPI
|
||||||
localInverse, _ := pattern.local.matrix.Inverse()
|
localInverse, _ := pattern.local.matrix.Inverse()
|
||||||
@@ -93,6 +98,10 @@ func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pixels := candidate[:width*height]
|
pixels := candidate[:width*height]
|
||||||
|
var cellChannels []uint8
|
||||||
|
if channels != nil {
|
||||||
|
cellChannels = candidateChannels[:width*height]
|
||||||
|
}
|
||||||
for y := range height {
|
for y := range height {
|
||||||
if err := c.importer.ctx.Err(); err != nil {
|
if err := c.importer.ctx.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -104,10 +113,16 @@ func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel,
|
|||||||
initial = backdrop[index]
|
initial = backdrop[index]
|
||||||
}
|
}
|
||||||
pixels[y*width+x] = pdfCompositePixel{values: initial.values, alpha: initial.alpha}
|
pixels[y*width+x] = pdfCompositePixel{values: initial.values, alpha: initial.alpha}
|
||||||
|
if cellChannels != nil {
|
||||||
|
cellChannels[y*width+x] = 0
|
||||||
|
if !disjoint {
|
||||||
|
cellChannels[y*width+x] = channels[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cell := pdfCompositor{importer: &pattern.local, box: Box{X: c.box.X + float64(left)*pixelStep - dx, Y: c.box.Y + float64(top)*pixelStep - dy, W: float64(width) * pixelStep, H: float64(height) * pixelStep},
|
cell := pdfCompositor{importer: &pattern.local, box: Box{X: c.box.X + float64(left)*pixelStep - dx, Y: c.box.Y + float64(top)*pixelStep - dy, W: float64(width) * pixelStep, H: float64(height) * pixelStep},
|
||||||
width: width, height: height, inverse: localInverse, cache: pattern.local.compositingCache(), scratch: c.scratch, masks: map[*pdfgo.SoftMask][]float64{}}
|
width: width, height: height, inverse: localInverse, cache: pattern.local.compositingCache(), scratch: c.scratch, masks: map[*pdfgo.SoftMask][]float64{}, softMask: c.softMask, channels: cellChannels}
|
||||||
err := cell.draw(pattern.nodes, pixels, space)
|
err := cell.draw(pattern.nodes, pixels, space)
|
||||||
cell.releaseMasks()
|
cell.releaseMasks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,6 +135,13 @@ func (c *pdfCompositor) tiling(paint pdfgo.Paint, backdrop []pdfCompositePixel,
|
|||||||
for x := range width {
|
for x := range width {
|
||||||
index := (top+y)*c.width + left + x
|
index := (top+y)*c.width + left + x
|
||||||
pdfCompositePatternCell(&result[index], backdrop[index], pixels[y*width+x], disjoint)
|
pdfCompositePatternCell(&result[index], backdrop[index], pixels[y*width+x], disjoint)
|
||||||
|
if channels != nil && pixels[y*width+x].shape != 0 {
|
||||||
|
if disjoint {
|
||||||
|
channels[index] |= cellChannels[y*width+x]
|
||||||
|
} else {
|
||||||
|
channels[index] = cellChannels[y*width+x]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,9 +230,21 @@ func (p *pdfImporter) compositePattern(paint pdfgo.Paint) (*pdfCompositePattern,
|
|||||||
// 入参: node 图元, space 混合空间
|
// 入参: node 图元, space 混合空间
|
||||||
// 返回: bool 是否可直接转换, error 图案解析错误
|
// 返回: bool 是否可直接转换, error 图案解析错误
|
||||||
func (p *pdfImporter) directCompositeNode(node pdfCompositeNode, space *pdfgo.ColorSpace) (bool, error) {
|
func (p *pdfImporter) directCompositeNode(node pdfCompositeNode, space *pdfgo.ColorSpace) (bool, error) {
|
||||||
if node.group == nil && space.Model == "DeviceCMYK" && !space.Calibrated() {
|
if node.group == nil && space.Device() {
|
||||||
style, fill, stroke := node.style()
|
style, fill, stroke := node.style()
|
||||||
if style.ColorConversion != (pdfgo.ColorConversion{}) {
|
if node.image == nil || node.image.Image.ImageMask {
|
||||||
|
for i, paint := range [2]pdfgo.Paint{style.Fill, style.Stroke} {
|
||||||
|
if i == 0 && !fill || i == 1 && !stroke {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if native := pdfPrepareShadingColorants(paint, space, false); native != nil {
|
||||||
|
if !native.process.AlternateEquivalent() || i == 0 && style.FillOverprint || i == 1 && style.StrokeOverprint {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if space.Model == "DeviceCMYK" && style.ColorConversion != (pdfgo.ColorConversion{}) {
|
||||||
if node.image != nil && !node.image.Image.ImageMask {
|
if node.image != nil && !node.image.Image.ImageMask {
|
||||||
source := p.compositingCache().images[node.image.Image]
|
source := p.compositingCache().images[node.image.Image]
|
||||||
if source == nil {
|
if source == nil {
|
||||||
@@ -314,7 +348,27 @@ func (p *pdfImporter) processOverprint(nodes []pdfCompositeNode) (bool, error) {
|
|||||||
if fill && style.FillOverprint && pdfOverprintNeedsSeparation(style.Fill) || stroke && style.StrokeOverprint && pdfOverprintNeedsSeparation(style.Stroke) {
|
if fill && style.FillOverprint && pdfOverprintNeedsSeparation(style.Fill) || stroke && style.StrokeOverprint && pdfOverprintNeedsSeparation(style.Stroke) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
if style.Transfer != nil || style.Halftone != nil {
|
||||||
|
if fill && style.FillOverprint && style.Fill.CMYK != nil || stroke && style.StrokeOverprint && style.Stroke.CMYK != nil {
|
||||||
|
transfer, err := p.resolveTransfer(style, "DeviceCMYK")
|
||||||
|
if err != nil || transfer != nil {
|
||||||
|
return transfer != nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for i, paint := range []pdfgo.Paint{style.Fill, style.Stroke} {
|
for i, paint := range []pdfgo.Paint{style.Fill, style.Stroke} {
|
||||||
|
if i == 0 && fill && style.FillOverprint || i == 1 && stroke && style.StrokeOverprint {
|
||||||
|
if native := pdfPrepareShadingColorants(paint, &pdfgo.ColorSpace{Model: "DeviceCMYK"}, false); native != nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
native, _, err := pdfCompositeNativePaint(paint, &pdfgo.ColorSpace{Model: "DeviceCMYK"}, style.RenderingIntent)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if native != nil && (!pdfOpaqueBlack(*native) || !pdfOpaqueBlack(paint)) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
if paint.Shading != nil && (i == 0 && fill || i == 1 && stroke) {
|
if paint.Shading != nil && (i == 0 && fill || i == 1 && stroke) {
|
||||||
nodes, err := p.compositeShadingPattern(paint.Shading)
|
nodes, err := p.compositeShadingPattern(paint.Shading)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ type pdfCompositeBuffers[T any] [8][]T
|
|||||||
|
|
||||||
// pdfCompositeScratch 复用像素及蒙版缓冲,不跨页持有活动内容
|
// pdfCompositeScratch 复用像素及蒙版缓冲,不跨页持有活动内容
|
||||||
type pdfCompositeScratch struct {
|
type pdfCompositeScratch struct {
|
||||||
pixels pdfCompositeBuffers[pdfCompositePixel]
|
pixels pdfCompositeBuffers[pdfCompositePixel]
|
||||||
masks pdfCompositeBuffers[float64]
|
masks pdfCompositeBuffers[float64]
|
||||||
rasters [8]*image.RGBA
|
channels pdfCompositeBuffers[uint8]
|
||||||
|
rasters [8]*image.RGBA
|
||||||
}
|
}
|
||||||
|
|
||||||
// acquireCoverage 借用当前块的独占画布,保留活动画布供嵌套合成读取
|
// acquireCoverage 借用当前块的独占画布,保留活动画布供嵌套合成读取
|
||||||
@@ -121,6 +122,24 @@ func (c *pdfCompositor) releasePixels(pixels []pdfCompositePixel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// acquireChannels 借用图案通道标记,仅在设备函数需要时分配
|
||||||
|
// 入参: count 当前块像素数
|
||||||
|
// 返回: []uint8 尚未初始化的独占通道缓冲
|
||||||
|
func (c *pdfCompositor) acquireChannels(count int) []uint8 {
|
||||||
|
if c.scratch == nil {
|
||||||
|
c.scratch = &pdfCompositeScratch{}
|
||||||
|
}
|
||||||
|
return c.scratch.channels.acquire(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// releaseChannels 归还不再参与嵌套合成的通道标记
|
||||||
|
// 入参: channels 当前调用借用的通道缓冲
|
||||||
|
func (c *pdfCompositor) releaseChannels(channels []uint8) {
|
||||||
|
if c.scratch != nil {
|
||||||
|
c.scratch.channels.release(channels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// releaseMasks 在当前块绘制结束后归还蒙版结果,不复用其他块的坐标缓存
|
// releaseMasks 在当前块绘制结束后归还蒙版结果,不复用其他块的坐标缓存
|
||||||
func (c *pdfCompositor) releaseMasks() {
|
func (c *pdfCompositor) releaseMasks() {
|
||||||
if c.scratch != nil {
|
if c.scratch != nil {
|
||||||
|
|||||||
@@ -56,19 +56,27 @@ func (p *pdfImporter) compositeShadingPattern(pattern *pdfgo.ShadingPattern) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
// shadingPattern 统一对象和图案的几何覆盖,按相交面积合成背景及内部着色
|
// shadingPattern 统一对象和图案的几何覆盖,按相交面积合成背景及内部着色
|
||||||
// 入参: pattern 着色图案, node 使用图元, outline 是否描边, backdrop 初始背景, space 父级混合空间
|
// 入参: pattern 着色图案, node 使用图元, outline 是否描边, backdrop 初始背景, space 父级混合空间, channels 可选源通道输出
|
||||||
// 返回: []pdfCompositePixel 源颜色、形状及不透明度, error 状态或合成错误
|
// 返回: []pdfCompositePixel 源颜色、形状及不透明度, error 状态或合成错误
|
||||||
func (c *pdfCompositor) shadingPattern(pattern *pdfgo.ShadingPattern, node pdfCompositeNode, outline bool, backdrop []pdfCompositePixel, space *pdfgo.ColorSpace) ([]pdfCompositePixel, error) {
|
func (c *pdfCompositor) shadingPattern(pattern *pdfgo.ShadingPattern, node pdfCompositeNode, outline bool, backdrop []pdfCompositePixel, space *pdfgo.ColorSpace, channels []uint8) ([]pdfCompositePixel, error) {
|
||||||
nodes, err := c.importer.shadingSources(pattern, node, outline)
|
nodes, err := c.importer.shadingSources(pattern, node, outline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
local := *c
|
local := *c
|
||||||
local.transfers, local.shadingSource = nil, true
|
local.transfers, local.shadingSource = nil, true
|
||||||
|
local.channels = channels
|
||||||
result, err := local.shadingSourcePixels(nodes[len(nodes)-1], backdrop, space)
|
result, err := local.shadingSourcePixels(nodes[len(nodes)-1], backdrop, space)
|
||||||
if err != nil || len(nodes) == 1 {
|
if err != nil || len(nodes) == 1 {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
var backgroundChannels []uint8
|
||||||
|
if channels != nil {
|
||||||
|
backgroundChannels = c.acquireChannels(len(channels))
|
||||||
|
defer c.releaseChannels(backgroundChannels)
|
||||||
|
clear(backgroundChannels)
|
||||||
|
}
|
||||||
|
local.channels = backgroundChannels
|
||||||
background, err := local.shadingSourcePixels(nodes[0], backdrop, space)
|
background, err := local.shadingSourcePixels(nodes[0], backdrop, space)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.releasePixels(result)
|
c.releasePixels(result)
|
||||||
@@ -115,6 +123,9 @@ func (c *pdfCompositor) shadingPattern(pattern *pdfgo.ShadingPattern, node pdfCo
|
|||||||
}
|
}
|
||||||
result[i].alpha, result[i].effect = alpha, alpha
|
result[i].alpha, result[i].effect = alpha, alpha
|
||||||
result[i].shape += background[i].shape * remaining
|
result[i].shape += background[i].shape * remaining
|
||||||
|
if channels != nil && background[i].shape*remaining > 0 {
|
||||||
|
channels[i] |= backgroundChannels[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -74,8 +74,8 @@ func (c *pdfFormCache) reset() {
|
|||||||
// 入参: nodes 普通表单连续图元
|
// 入参: nodes 普通表单连续图元
|
||||||
// 返回: bool 是否已保存为复合对象, error 检查或转换错误
|
// 返回: bool 是否已保存为复合对象, error 检查或转换错误
|
||||||
func (p *pdfImporter) compositeForm(nodes []pdfCompositeNode) (bool, error) {
|
func (p *pdfImporter) compositeForm(nodes []pdfCompositeNode) (bool, error) {
|
||||||
if pdfCompositeHasTransfer(nodes) {
|
if found, err := p.compositeHasTransfer(nodes, p.transferModel); err != nil || found {
|
||||||
return false, nil
|
return false, err
|
||||||
}
|
}
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if err := p.ctx.Err(); err != nil {
|
if err := p.ctx.Err(); err != nil {
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ import (
|
|||||||
"github.com/xiaoqidun/pdfgo"
|
"github.com/xiaoqidun/pdfgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// pdfImageContentKey 区分编码内容、渲染意图、模板填充色及有损采样尺寸
|
// pdfImageContentKey 区分编码内容、渲染意图、原生输出空间、模板填充色及有损采样尺寸
|
||||||
type pdfImageContentKey struct {
|
type pdfImageContentKey struct {
|
||||||
data [32]byte
|
data [32]byte
|
||||||
intent pdfgo.Name
|
intent pdfgo.Name
|
||||||
tint color.NRGBA64
|
process pdfgo.Name
|
||||||
size image.Point
|
tint color.NRGBA64
|
||||||
|
size image.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfImageContent 保存不依赖间接资源的图像描述及已注册编号
|
// pdfImageContent 保存不依赖间接资源的图像描述及已注册编号
|
||||||
@@ -67,7 +68,7 @@ func (p *pdfImporter) imageContentKey(source *pdfgo.Image, key pdfImageKey) (pdf
|
|||||||
hash.Write(data[:n])
|
hash.Write(data[:n])
|
||||||
data = data[n:]
|
data = data[n:]
|
||||||
}
|
}
|
||||||
result := pdfImageContentKey{intent: key.intent, tint: key.tint, size: key.size}
|
result := pdfImageContentKey{intent: key.intent, process: key.process, tint: key.tint, size: key.size}
|
||||||
hash.Sum(result.data[:0])
|
hash.Sum(result.data[:0])
|
||||||
return result, true, p.ctx.Err()
|
return result, true, p.ctx.Err()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ofdgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/xiaoqidun/pdfgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pdfNativeImage 按源像素输出原生过程色,不分配完整显示颜色缓冲
|
||||||
|
type pdfNativeImage struct {
|
||||||
|
source *pdfgo.ImageProcess
|
||||||
|
space *pdfgo.ColorSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageProcessModel 选择与页面原生过程色一致的图像输出,已等价的备用色沿用现有编码路径
|
||||||
|
// 入参: source 源图像
|
||||||
|
// 返回: pdfgo.Name 原生输出空间,无需重映射时为空, error 色料解析错误
|
||||||
|
func (p *pdfImporter) imageProcessModel(source *pdfgo.Image) (pdfgo.Name, error) {
|
||||||
|
space := p.compositeSpace
|
||||||
|
if source.ImageMask || !space.Device() {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
colorants, err := source.ColorantSpace()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
process := colorants.PrepareProcess(&pdfgo.ColorantDevice{Space: space}, space, false)
|
||||||
|
if process == nil || process.Mask() == ([4]bool{}) || process.AlternateEquivalent() {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return space.Model, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageProcess 复用源分量并保留原始分辨率,颜色变换随编码逐像素执行
|
||||||
|
// 入参: source 源图像, model 原生设备空间
|
||||||
|
// 返回: image.Image 只读图像, error 解码或分量映射错误
|
||||||
|
func (p *pdfImporter) imageProcess(source *pdfgo.Image, model pdfgo.Name) (image.Image, error) {
|
||||||
|
cache := p.compositingCache()
|
||||||
|
components := cache.images[source]
|
||||||
|
if components == nil {
|
||||||
|
var err error
|
||||||
|
components, err = source.DecodeComponentsViewContext(p.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cache.images[source] = components
|
||||||
|
}
|
||||||
|
space := &pdfgo.ColorSpace{Model: model}
|
||||||
|
process, err := components.PrepareProcess(&pdfgo.ColorantDevice{Space: space}, space, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if process == nil {
|
||||||
|
return nil, fmt.Errorf("invalid native PDF image process space %s", model)
|
||||||
|
}
|
||||||
|
return &pdfNativeImage{source: process, space: space}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorModel 保留16位原生分量与源透明度
|
||||||
|
// 返回: color.Model 非预乘颜色模型
|
||||||
|
func (s *pdfNativeImage) ColorModel() color.Model { return color.NRGBA64Model }
|
||||||
|
|
||||||
|
// Bounds 返回源图像的完整采样边界
|
||||||
|
// 返回: image.Rectangle 原始像素边界
|
||||||
|
func (s *pdfNativeImage) Bounds() image.Rectangle { return s.source.Bounds() }
|
||||||
|
|
||||||
|
// At 获取按OFD输出映射的原生颜色
|
||||||
|
// 入参: x 横坐标, y 纵坐标
|
||||||
|
// 返回: color.Color 非预乘颜色
|
||||||
|
func (s *pdfNativeImage) At(x, y int) color.Color { return s.NRGBA64At(x, y) }
|
||||||
|
|
||||||
|
// NRGBA64At 按原始精度转换设备过程色,不进行页面采样或备用变换
|
||||||
|
// 入参: x 横坐标, y 纵坐标
|
||||||
|
// 返回: color.NRGBA64 非预乘颜色
|
||||||
|
func (s *pdfNativeImage) NRGBA64At(x, y int) color.NRGBA64 {
|
||||||
|
if !image.Pt(x, y).In(s.Bounds()) {
|
||||||
|
return color.NRGBA64{}
|
||||||
|
}
|
||||||
|
values, alpha := s.source.ValuesAt(x, y)
|
||||||
|
rgb, _ := pdfCompositeOutputColor(s.space, values)
|
||||||
|
return color.NRGBA64{R: uint16(math.Round(rgb[0] * 65535)), G: uint16(math.Round(rgb[1] * 65535)), B: uint16(math.Round(rgb[2] * 65535)), A: uint16(math.Round(alpha * 65535))}
|
||||||
|
}
|
||||||
+13
-4
@@ -40,6 +40,7 @@ type pdfMeshKey struct {
|
|||||||
mesh *pdfgo.MeshGradient
|
mesh *pdfgo.MeshGradient
|
||||||
space *pdfgo.ColorSpace
|
space *pdfgo.ColorSpace
|
||||||
conversion pdfgo.ColorConversion
|
conversion pdfgo.ColorConversion
|
||||||
|
softMask bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfMeshSample 保存采样点参数,patch为一基编号,零表示未覆盖
|
// pdfMeshSample 保存采样点参数,patch为一基编号,零表示未覆盖
|
||||||
@@ -187,7 +188,7 @@ func (c *pdfCompositor) mesh(mesh *pdfgo.MeshGradient, space *pdfgo.ColorSpace,
|
|||||||
if err := c.importer.ctx.Err(); err != nil {
|
if err := c.importer.ctx.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key := pdfMeshKey{mesh, space, conversion}
|
key := pdfMeshKey{mesh, space, conversion, c.softMask}
|
||||||
if pixels, ok := c.meshes[key]; ok {
|
if pixels, ok := c.meshes[key]; ok {
|
||||||
return pixels, nil
|
return pixels, nil
|
||||||
}
|
}
|
||||||
@@ -272,7 +273,11 @@ func (c *pdfCompositor) mesh(mesh *pdfgo.MeshGradient, space *pdfgo.ColorSpace,
|
|||||||
}
|
}
|
||||||
sampleRows := func(start, end int) error {
|
sampleRows := func(start, end int) error {
|
||||||
colors := make(map[[4]float64][4]float64)
|
colors := make(map[[4]float64][4]float64)
|
||||||
sampler := mesh.NewSampler()
|
colorants := pdfPrepareShadingColorants(pdfgo.Paint{Mesh: mesh}, space, c.softMask)
|
||||||
|
var sampler *pdfgo.MeshSampler
|
||||||
|
if colorants == nil {
|
||||||
|
sampler = mesh.NewSampler()
|
||||||
|
}
|
||||||
for y := start * samples; y < end*samples; y++ {
|
for y := start * samples; y < end*samples; y++ {
|
||||||
if err := c.importer.ctx.Err(); err != nil {
|
if err := c.importer.ctx.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -291,13 +296,17 @@ func (c *pdfCompositor) mesh(mesh *pdfgo.MeshGradient, space *pdfgo.ColorSpace,
|
|||||||
values := background
|
values := background
|
||||||
if sample.patch != 0 {
|
if sample.patch != 0 {
|
||||||
var err error
|
var err error
|
||||||
values, err = sampler.ValuesAt(sample.patch-1, sample.u, sample.v)
|
if colorants != nil {
|
||||||
|
values, err = colorants.meshAt(sample.patch-1, sample.u, sample.v)
|
||||||
|
} else {
|
||||||
|
values, err = sampler.ValuesAt(sample.patch-1, sample.u, sample.v)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if cached, ok := colors[values]; ok {
|
if cached, ok := colors[values]; ok {
|
||||||
values = cached
|
values = cached
|
||||||
} else {
|
} else if colorants == nil {
|
||||||
source := values
|
source := values
|
||||||
values, err = converter.Convert(values[:mesh.Space.Components()])
|
values, err = converter.Convert(values[:mesh.Space.Components()])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+34
-15
@@ -28,12 +28,13 @@ import (
|
|||||||
"github.com/xiaoqidun/pdfgo"
|
"github.com/xiaoqidun/pdfgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// pdfImageKey 区分同一图像流的渲染意图及模板填充色
|
// pdfImageKey 区分图像流、渲染意图、原生输出空间、模板填充色及采样尺寸
|
||||||
type pdfImageKey struct {
|
type pdfImageKey struct {
|
||||||
stream *pdfgo.Stream
|
stream *pdfgo.Stream
|
||||||
intent pdfgo.Name
|
intent pdfgo.Name
|
||||||
tint color.NRGBA64
|
process pdfgo.Name
|
||||||
size image.Point
|
tint color.NRGBA64
|
||||||
|
size image.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfImageResource 保存图像的有效颜色空间及对应资源,避免跨资源重映射复用
|
// pdfImageResource 保存图像的有效颜色空间及对应资源,避免跨资源重映射复用
|
||||||
@@ -116,6 +117,10 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error {
|
|||||||
}
|
}
|
||||||
source := mark.Image
|
source := mark.Image
|
||||||
key := pdfImageKey{stream: source.Stream, intent: source.Intent}
|
key := pdfImageKey{stream: source.Stream, intent: source.Intent}
|
||||||
|
key.process, err = p.imageProcessModel(source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if !source.ImageMask && p.compression.Mode == CompressionLossy {
|
if !source.ImageMask && p.compression.Mode == CompressionLossy {
|
||||||
m := p.matrix.Mul(mark.Matrix)
|
m := p.matrix.Mul(mark.Matrix)
|
||||||
key.size = compressionImageSize(math.Hypot(m[0], m[1]), math.Hypot(m[2], m[3]), p.compression.ImageDPI())
|
key.size = compressionImageSize(math.Hypot(m[0], m[1]), math.Hypot(m[2], m[3]), p.compression.ImageDPI())
|
||||||
@@ -164,20 +169,34 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if id == "" {
|
if id == "" {
|
||||||
jbig2Original, err := source.JBIG2FileContext(p.ctx)
|
var jbig2Original, jpegOriginal []byte
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
jpegOriginal, err := source.JPEGFileContext(p.ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var decoded image.Image
|
var decoded image.Image
|
||||||
if len(jbig2Original) == 0 && len(jpegOriginal) == 0 && (source.ImageMask || key.size != (image.Point{})) {
|
if key.process != "" {
|
||||||
decoded, err = source.DecodeImageSizeContext(p.ctx, key.size)
|
decoded, err = p.imageProcess(source, key.process)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if key.size != (image.Point{}) {
|
||||||
|
decoded, err = pdfgo.ResizeImage(p.ctx, decoded, key.size)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jbig2Original, err = source.JBIG2FileContext(p.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jpegOriginal, err = source.JPEGFileContext(p.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(jbig2Original) == 0 && len(jpegOriginal) == 0 && (source.ImageMask || key.size != (image.Point{})) {
|
||||||
|
decoded, err = source.DecodeImageSizeContext(p.ctx, key.size)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if source.ImageMask {
|
if source.ImageMask {
|
||||||
decoded = &pdfStencilImage{source: decoded, fill: key.tint}
|
decoded = &pdfStencilImage{source: decoded, fill: key.tint}
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ import (
|
|||||||
// pdfPatternCellLimit 限制单页图案矢量快照的估算字节数
|
// pdfPatternCellLimit 限制单页图案矢量快照的估算字节数
|
||||||
const pdfPatternCellLimit = 8 << 20
|
const pdfPatternCellLimit = 8 << 20
|
||||||
|
|
||||||
// pdfPatternCellKey 隔离图案来源、基色、坐标、精度与目标文档资源
|
// pdfPatternCellKey 隔离图案来源、基色、坐标、精度、混合空间与目标文档资源
|
||||||
type pdfPatternCellKey struct {
|
type pdfPatternCellKey struct {
|
||||||
source *pdfgo.TilingPattern
|
source *pdfgo.TilingPattern
|
||||||
base pdfgo.Paint
|
base pdfgo.Paint
|
||||||
matrix pdfgo.Matrix
|
matrix pdfgo.Matrix
|
||||||
dpi float64
|
dpi float64
|
||||||
editor *Editor
|
editor *Editor
|
||||||
|
space *pdfgo.ColorSpace
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfPatternInstanceRange 收缩已知路径及图片的实例范围,未知边界保留完整范围
|
// pdfPatternInstanceRange 收缩已知路径及图片的实例范围,未知边界保留完整范围
|
||||||
@@ -63,7 +64,7 @@ func (p *pdfImporter) tilingPattern(source *pdfgo.TilingPattern, base pdfgo.Pain
|
|||||||
if p.patternCells == nil {
|
if p.patternCells == nil {
|
||||||
p.patternCells = &renderCache[pdfPatternCellKey, *Pattern]{limit: pdfPatternCellLimit}
|
p.patternCells = &renderCache[pdfPatternCellKey, *Pattern]{limit: pdfPatternCellLimit}
|
||||||
}
|
}
|
||||||
key := pdfPatternCellKey{source: source, matrix: p.matrix, dpi: p.rasterDPI, editor: p.editor}
|
key := pdfPatternCellKey{source: source, matrix: p.matrix, dpi: p.rasterDPI, editor: p.editor, space: p.compositeSpace}
|
||||||
if source.PaintType == 2 {
|
if source.PaintType == 2 {
|
||||||
base.Tiling, base.Axial, base.Radial, base.Mesh, base.Function, base.Shading = nil, nil, nil, nil, nil, nil
|
base.Tiling, base.Axial, base.Radial, base.Mesh, base.Function, base.Shading = nil, nil, nil, nil, nil, nil
|
||||||
base.Alpha = 1
|
base.Alpha = 1
|
||||||
|
|||||||
+14
-6
@@ -36,6 +36,7 @@ type pdfGradientKey struct {
|
|||||||
space *pdfgo.ColorSpace
|
space *pdfgo.ColorSpace
|
||||||
geometry pdfCompositeKey
|
geometry pdfCompositeKey
|
||||||
conversion pdfgo.ColorConversion
|
conversion pdfgo.ColorConversion
|
||||||
|
softMask bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfICCKey 区分原始ICC空间及输出渲染意图
|
// pdfICCKey 区分原始ICC空间及输出渲染意图
|
||||||
@@ -252,7 +253,7 @@ func (c *pdfCompositor) gradient(paint pdfgo.Paint, space *pdfgo.ColorSpace, nod
|
|||||||
if paint.Axial != nil && paint.Axial.Background == nil && paint.Axial.Bounds == nil || paint.Radial != nil && paint.Radial.Background == nil && paint.Radial.Bounds == nil {
|
if paint.Axial != nil && paint.Axial.Background == nil && paint.Axial.Bounds == nil || paint.Radial != nil && paint.Radial.Background == nil && paint.Radial.Bounds == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
key := pdfGradientKey{axial: paint.Axial, radial: paint.Radial, function: paint.Function, space: space}
|
key := pdfGradientKey{axial: paint.Axial, radial: paint.Radial, function: paint.Function, space: space, softMask: c.softMask}
|
||||||
var conversion pdfgo.ColorConversion
|
var conversion pdfgo.ColorConversion
|
||||||
if node != nil {
|
if node != nil {
|
||||||
key.geometry = pdfCompositeKey{path: node.path, text: node.text, image: node.image, stroke: outline}
|
key.geometry = pdfCompositeKey{path: node.path, text: node.text, image: node.image, stroke: outline}
|
||||||
@@ -360,6 +361,7 @@ func (c *pdfCompositor) gradient(paint pdfgo.Paint, space *pdfgo.ColorSpace, nod
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
colorants := pdfPrepareShadingColorants(paint, space, c.softMask)
|
||||||
step := 25.4 / c.importer.rasterDPI
|
step := 25.4 / c.importer.rasterDPI
|
||||||
pixels := make([]pdfShadingPixel, c.width*c.height)
|
pixels := make([]pdfShadingPixel, c.width*c.height)
|
||||||
for y := range c.height {
|
for y := range c.height {
|
||||||
@@ -399,21 +401,27 @@ func (c *pdfCompositor) gradient(paint pdfgo.Paint, space *pdfgo.ColorSpace, nod
|
|||||||
local = functionInverse.Apply(point)
|
local = functionInverse.Apply(point)
|
||||||
visible = domainClipped || local.X >= g.Domain.XMin && local.X <= g.Domain.XMax && local.Y >= g.Domain.YMin && local.Y <= g.Domain.YMax
|
visible = domainClipped || local.X >= g.Domain.XMin && local.X <= g.Domain.XMax && local.Y >= g.Domain.YMin && local.Y <= g.Domain.YMax
|
||||||
if visible {
|
if visible {
|
||||||
values, err = g.ValuesAt(local)
|
if colorants != nil {
|
||||||
if err == nil {
|
values, err = colorants.pointAt(local)
|
||||||
values, err = converter.Convert(values[:source.Components()])
|
} else {
|
||||||
|
values, err = g.ValuesAt(local)
|
||||||
|
if err == nil {
|
||||||
|
values, err = converter.Convert(values[:source.Components()])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var parameter float64
|
var parameter float64
|
||||||
parameter, visible = position.PositionAt(point)
|
parameter, visible = position.PositionAt(point)
|
||||||
if visible {
|
if visible {
|
||||||
if paint.Axial != nil {
|
if colorants != nil {
|
||||||
|
values, err = colorants.at(parameter)
|
||||||
|
} else if paint.Axial != nil {
|
||||||
values, err = paint.Axial.ValuesAt(parameter)
|
values, err = paint.Axial.ValuesAt(parameter)
|
||||||
} else {
|
} else {
|
||||||
values, err = paint.Radial.ValuesAt(parameter)
|
values, err = paint.Radial.ValuesAt(parameter)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil && colorants == nil {
|
||||||
values, err = converter.Convert(values[:source.Components()])
|
values, err = converter.Convert(values[:source.Components()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ofdgo
|
||||||
|
|
||||||
|
import "github.com/xiaoqidun/pdfgo"
|
||||||
|
|
||||||
|
// pdfShadingColorants 复用原始浓度缓冲及原生通道映射,不跨并发采样共享
|
||||||
|
type pdfShadingColorants struct {
|
||||||
|
paint pdfgo.Paint
|
||||||
|
process *pdfgo.ColorantProcess
|
||||||
|
tints []float64
|
||||||
|
mesh *pdfgo.MeshSampler
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfShadingColorantSpace 返回渐变的源色料定义,不使用备用颜色推断通道
|
||||||
|
// 入参: paint 渐变画刷
|
||||||
|
// 返回: *pdfgo.ColorantSpace 原始色料定义,普通渐变为nil
|
||||||
|
func pdfShadingColorantSpace(paint pdfgo.Paint) *pdfgo.ColorantSpace {
|
||||||
|
switch {
|
||||||
|
case paint.Axial != nil:
|
||||||
|
return paint.Axial.ColorantSpace()
|
||||||
|
case paint.Radial != nil:
|
||||||
|
return paint.Radial.ColorantSpace()
|
||||||
|
case paint.Function != nil:
|
||||||
|
return paint.Function.ColorantSpace()
|
||||||
|
case paint.Mesh != nil:
|
||||||
|
return paint.Mesh.ColorantSpace()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfPrepareShadingColorants 为当前组建立原生过程采样,备用色保留原路径
|
||||||
|
// 入参: paint 渐变画刷, space 组空间, softMask 是否用于软蒙版
|
||||||
|
// 返回: *pdfShadingColorants 独立采样状态,不可原生映射时为nil
|
||||||
|
func pdfPrepareShadingColorants(paint pdfgo.Paint, space *pdfgo.ColorSpace, softMask bool) *pdfShadingColorants {
|
||||||
|
colorants := pdfShadingColorantSpace(paint)
|
||||||
|
process := colorants.PrepareProcess(&pdfgo.ColorantDevice{Space: space}, space, softMask)
|
||||||
|
if process == nil || process.Mask() == ([4]bool{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := &pdfShadingColorants{paint: paint, process: process, tints: make([]float64, len(colorants.Names))}
|
||||||
|
if paint.Mesh != nil {
|
||||||
|
s.mesh = paint.Mesh.NewSampler()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// at 在归一化位置求值轴向或径向原始浓度
|
||||||
|
// 入参: position 归一化渐变参数
|
||||||
|
// 返回: [4]float64 原生过程分量, error 求值错误
|
||||||
|
func (s *pdfShadingColorants) at(position float64) ([4]float64, error) {
|
||||||
|
var err error
|
||||||
|
if s.paint.Axial != nil {
|
||||||
|
err = s.paint.Axial.ColorantValuesAt(position, s.tints)
|
||||||
|
} else {
|
||||||
|
err = s.paint.Radial.ColorantValuesAt(position, s.tints)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return [4]float64{}, err
|
||||||
|
}
|
||||||
|
return s.process.Convert(s.tints)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pointAt 在函数定义域内求值原始浓度
|
||||||
|
// 入参: point 函数坐标
|
||||||
|
// 返回: [4]float64 原生过程分量, error 求值错误
|
||||||
|
func (s *pdfShadingColorants) pointAt(point pdfgo.Point) ([4]float64, error) {
|
||||||
|
if err := s.paint.Function.ColorantValuesAt(point, s.tints); err != nil {
|
||||||
|
return [4]float64{}, err
|
||||||
|
}
|
||||||
|
return s.process.Convert(s.tints)
|
||||||
|
}
|
||||||
|
|
||||||
|
// meshAt 插值网格原始浓度并复用函数结果
|
||||||
|
// 入参: patch 网格序号, u 横向参数, v 纵向参数
|
||||||
|
// 返回: [4]float64 原生过程分量, error 求值错误
|
||||||
|
func (s *pdfShadingColorants) meshAt(patch int, u, v float64) ([4]float64, error) {
|
||||||
|
if err := s.mesh.ColorantValuesAt(patch, u, v, s.tints); err != nil {
|
||||||
|
return [4]float64{}, err
|
||||||
|
}
|
||||||
|
return s.process.Convert(s.tints)
|
||||||
|
}
|
||||||
+64
-23
@@ -16,54 +16,78 @@ package ofdgo
|
|||||||
|
|
||||||
import "github.com/xiaoqidun/pdfgo"
|
import "github.com/xiaoqidun/pdfgo"
|
||||||
|
|
||||||
// pdfTransferKey 区分图形状态与网屏覆盖函数
|
// 图案标记保留设备四色通道及非零对象形状
|
||||||
|
const (
|
||||||
|
pdfTransferChannels uint8 = 15
|
||||||
|
pdfTransferShape uint8 = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// pdfTransferKey 区分图形状态、网屏覆盖函数及输出设备
|
||||||
type pdfTransferKey struct {
|
type pdfTransferKey struct {
|
||||||
transfer *pdfgo.TransferFunction
|
transfer *pdfgo.TransferFunction
|
||||||
halftone *pdfgo.Halftone
|
halftone *pdfgo.Halftone
|
||||||
|
model pdfgo.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveTransfer 在页面内复用连续RGB设备的有效传递函数
|
// resolveTransfer 在页面内复用输出设备的有效函数,不修改原始图形状态
|
||||||
// 入参: style 原始图形状态与有效输出状态
|
// 入参: style 原始图形状态, model 原生设备颜色空间
|
||||||
// 返回: error 网屏传递函数错误
|
// 返回: *pdfgo.TransferFunction 有效函数, error 网屏传递函数错误
|
||||||
func (p *pdfImporter) resolveTransfer(style *pdfgo.Style) error {
|
func (p *pdfImporter) resolveTransfer(style pdfgo.Style, model pdfgo.Name) (*pdfgo.TransferFunction, error) {
|
||||||
if style.Halftone == nil && style.Transfer == nil {
|
if style.Halftone == nil && style.Transfer == nil {
|
||||||
return nil
|
return nil, nil
|
||||||
|
}
|
||||||
|
if model == "" {
|
||||||
|
model = "DeviceRGB"
|
||||||
}
|
}
|
||||||
cache := p.compositingCache()
|
cache := p.compositingCache()
|
||||||
if cache.transfers == nil {
|
if cache.transfers == nil {
|
||||||
cache.transfers = make(map[pdfTransferKey]*pdfgo.TransferFunction)
|
cache.transfers = make(map[pdfTransferKey]*pdfgo.TransferFunction)
|
||||||
}
|
}
|
||||||
key := pdfTransferKey{style.Transfer, style.Halftone}
|
key := pdfTransferKey{style.Transfer, style.Halftone, model}
|
||||||
transfer, found := cache.transfers[key]
|
transfer, found := cache.transfers[key]
|
||||||
if !found {
|
if !found {
|
||||||
var err error
|
var err error
|
||||||
transfer, err = p.reader.DeviceTransfer(style.Transfer, style.Halftone, "DeviceRGB")
|
transfer, err = p.reader.DeviceTransfer(style.Transfer, style.Halftone, model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
cache.transfers[key] = transfer
|
cache.transfers[key] = transfer
|
||||||
}
|
}
|
||||||
style.Transfer = transfer
|
return transfer, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfCompositeHasTransfer 检查页面是否需要保留最终设备传递函数
|
// compositeHasTransfer 检查最终设备使用的通道,不将备用通道误判为有效函数
|
||||||
// 入参: nodes 原始图元及透明组
|
// 入参: nodes 原始图元及透明组, model 原生设备颜色空间
|
||||||
// 返回: bool 是否存在非恒等函数
|
// 返回: bool 是否存在非恒等函数, error 网屏函数错误
|
||||||
func pdfCompositeHasTransfer(nodes []pdfCompositeNode) bool {
|
func (p *pdfImporter) compositeHasTransfer(nodes []pdfCompositeNode, model pdfgo.Name) (bool, error) {
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
if node.group != nil {
|
if node.group != nil {
|
||||||
if pdfCompositeHasTransfer(node.children) {
|
if found, err := p.compositeHasTransfer(node.children, model); err != nil || found {
|
||||||
return true
|
return found, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
style, _, _ := node.style()
|
style, _, _ := node.style()
|
||||||
if style.Transfer != nil {
|
transfer, err := p.resolveTransfer(style, model)
|
||||||
return true
|
if err != nil || transfer != nil {
|
||||||
|
return transfer != nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// transferDevice 为四色叠印保留原生设备通道,其余页面使用连续RGB设备
|
||||||
|
// 入参: space 页面混合空间, nodes 参与合成的图元
|
||||||
|
// 返回: pdfgo.Name 原生设备空间, error 图案或图像解析错误
|
||||||
|
func (p *pdfImporter) transferDevice(space *pdfgo.ColorSpace, nodes []pdfCompositeNode) (pdfgo.Name, error) {
|
||||||
|
if space.Model == "DeviceCMYK" && !space.Calibrated() {
|
||||||
|
if found, err := p.processOverprint(nodes); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if found {
|
||||||
|
return "DeviceCMYK", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "DeviceRGB", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectTransfer 按最上层基本图元及祖先的不透明状态选择最终设备函数
|
// selectTransfer 按最上层基本图元及祖先的不透明状态选择最终设备函数
|
||||||
@@ -78,8 +102,9 @@ func (c *pdfCompositor) selectTransfer(node pdfCompositeNode, outline bool) (*pd
|
|||||||
if c.transferBlocked || paint.Alpha != 1 || style.SoftMask != nil || !pdfNormalBlend(style.BlendMode) {
|
if c.transferBlocked || paint.Alpha != 1 || style.SoftMask != nil || !pdfNormalBlend(style.BlendMode) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if style.Transfer == nil {
|
transfer, err := c.importer.resolveTransfer(style, c.transferModel)
|
||||||
return nil, nil
|
if err != nil || transfer == nil {
|
||||||
|
return transfer, err
|
||||||
}
|
}
|
||||||
if pattern := paint.Shading; pattern != nil && (node.image == nil || node.image.Image.ImageMask) {
|
if pattern := paint.Shading; pattern != nil && (node.image == nil || node.image.Image.ImageMask) {
|
||||||
inner := pattern.Style
|
inner := pattern.Style
|
||||||
@@ -103,7 +128,23 @@ func (c *pdfCompositor) selectTransfer(node pdfCompositeNode, outline bool) (*pd
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return style.Transfer, nil
|
return transfer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdfPaintChannels 选择兼容叠印实际使用源颜色的通道
|
||||||
|
// 入参: values 源四色分量, compatible 是否保留零分量背景, marked 指定通道,nil表示全部
|
||||||
|
// 返回: [4]bool 各通道是否取源颜色
|
||||||
|
func pdfPaintChannels(values [4]float64, compatible bool, marked *[4]bool) [4]bool {
|
||||||
|
channels := [4]bool{true, true, true, true}
|
||||||
|
if marked != nil {
|
||||||
|
channels = *marked
|
||||||
|
}
|
||||||
|
if compatible {
|
||||||
|
for i := range channels {
|
||||||
|
channels[i] = channels[i] && values[i] != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return channels
|
||||||
}
|
}
|
||||||
|
|
||||||
// opaqueTransfer 复用图案不透明性检查,图案内部函数不提前作用于源颜色
|
// opaqueTransfer 复用图案不透明性检查,图案内部函数不提前作用于源颜色
|
||||||
|
|||||||
@@ -69,18 +69,18 @@ func (c *renderCache[K, V]) put(key K, value V, cost int) {
|
|||||||
c.used += cost
|
c.used += cost
|
||||||
}
|
}
|
||||||
|
|
||||||
// rasterCommandCost 估算指令快照及后端路径的缓存成本,不含借用图片
|
// rasterCommandCost 按保留容量估算中立指令成本,不含临时后端路径及借用图片
|
||||||
// 入参: command 绘制指令
|
// 入参: command 绘制指令
|
||||||
// 返回: int 字节成本
|
// 返回: int 字节成本
|
||||||
func rasterCommandCost(command RasterCommand) int {
|
func rasterCommandCost(command RasterCommand) int {
|
||||||
cost := 512 + (len(command.Path)+len(command.Clip))*208
|
cost := 512 + (cap(command.Path)+cap(command.Clip))*56
|
||||||
if command.Stroke != nil {
|
if command.Stroke != nil {
|
||||||
cost += 128 + len(command.Stroke.Dashes)*16
|
cost += 128 + cap(command.Stroke.Dashes)*8
|
||||||
}
|
}
|
||||||
if command.Paint.Gradient != nil {
|
if command.Paint.Gradient != nil {
|
||||||
cost += 256 + len(command.Paint.Gradient.Stops)*64
|
cost += 256 + cap(command.Paint.Gradient.Stops)*64
|
||||||
if mesh := command.Paint.Gradient.Mesh; mesh != nil {
|
if mesh := command.Paint.Gradient.Mesh; mesh != nil {
|
||||||
cost += 32 + len(mesh.Triangles)*240
|
cost += 32 + cap(mesh.Triangles)*240
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cost
|
return cost
|
||||||
|
|||||||
+15
-10
@@ -59,6 +59,7 @@ type U3DNode struct {
|
|||||||
Visibility uint32
|
Visibility uint32
|
||||||
Shaders [][]string
|
Shaders [][]string
|
||||||
LineShaders [][]string
|
LineShaders [][]string
|
||||||
|
PointShaders [][]string
|
||||||
View *U3DView
|
View *U3DView
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +112,9 @@ type u3dDecoder struct {
|
|||||||
palettes map[uint32]map[string]bool
|
palettes map[uint32]map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeU3D 按ECMA-363解码静态基础网格、线集、组、材质、着色器和光源
|
// DecodeU3D 按ECMA-363解码静态基础网格、线集、点集、组、材质、着色器和光源
|
||||||
// 暂不支持渐进网格、纹理和动画,遇到未支持内容返回错误而非部分场景
|
// 暂不支持渐进网格、纹理和动画,遇到未支持内容返回错误而非部分场景
|
||||||
|
// 点集球面预测暂限不超过两个非对跖方向,不推测多方向合并次序
|
||||||
// 入参: ctx 取消上下文, data U3D数据, options 解码限制
|
// 入参: ctx 取消上下文, data U3D数据, options 解码限制
|
||||||
// 返回: *U3DModel 独立场景数据, error 格式、能力、预算或取消错误
|
// 返回: *U3DModel 独立场景数据, error 格式、能力、预算或取消错误
|
||||||
func DecodeU3D(ctx context.Context, data []byte, options U3DOptions) (*U3DModel, error) {
|
func DecodeU3D(ctx context.Context, data []byte, options U3DOptions) (*U3DModel, error) {
|
||||||
@@ -175,7 +177,7 @@ func DecodeU3D(ctx context.Context, data []byte, options U3DOptions) (*U3DModel,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("U3D block at %d: %w", offset, err)
|
return nil, fmt.Errorf("U3D block at %d: %w", offset, err)
|
||||||
}
|
}
|
||||||
continuation := block.kind == 0xffffff15 || block.kind == 0xffffff3b || block.kind == 0xffffff3c || block.kind == 0xffffff3f
|
continuation := block.kind == 0xffffff15 || block.kind == 0xffffff3b || block.kind == 0xffffff3c || block.kind == 0xffffff3e || block.kind == 0xffffff3f
|
||||||
if uint64(offset) < declarationEnd && (uint64(end) > declarationEnd || continuation) || uint64(offset) >= declarationEnd && !continuation {
|
if uint64(offset) < declarationEnd && (uint64(end) > declarationEnd || continuation) || uint64(offset) >= declarationEnd && !continuation {
|
||||||
return nil, fmt.Errorf("invalid U3D declaration boundary at %d", offset)
|
return nil, fmt.Errorf("invalid U3D declaration boundary at %d", offset)
|
||||||
}
|
}
|
||||||
@@ -382,16 +384,16 @@ func (d *u3dDecoder) block(block u3dBlock, owner string, chain int, index uint32
|
|||||||
return name, fmt.Errorf("invalid U3D mesh continuation location")
|
return name, fmt.Errorf("invalid U3D mesh continuation location")
|
||||||
}
|
}
|
||||||
err = d.baseMesh(&r, name)
|
err = d.baseMesh(&r, name)
|
||||||
case 0xffffff37:
|
case 0xffffff36, 0xffffff37:
|
||||||
if chain >= 0 && (chain != 1 || index != 0) {
|
if chain >= 0 && (chain != 1 || index != 0) {
|
||||||
return name, fmt.Errorf("invalid U3D line set chain")
|
return name, fmt.Errorf("invalid U3D primitive chain")
|
||||||
}
|
}
|
||||||
err = d.lineDeclaration(&r, name)
|
err = d.primitiveDeclaration(&r, name, block.kind == 0xffffff36)
|
||||||
case 0xffffff3f:
|
case 0xffffff3e, 0xffffff3f:
|
||||||
if chain >= 0 {
|
if chain >= 0 {
|
||||||
return name, fmt.Errorf("invalid U3D line continuation location")
|
return name, fmt.Errorf("invalid U3D primitive continuation location")
|
||||||
}
|
}
|
||||||
err = d.lineContinuation(&r, name)
|
err = d.primitiveContinuation(&r, name, block.kind == 0xffffff3e)
|
||||||
case 0xffffff45:
|
case 0xffffff45:
|
||||||
if chain != 0 || index == 0 || owner != name || r.u32() != index {
|
if chain != 0 || index == 0 || owner != name || r.u32() != index {
|
||||||
return name, fmt.Errorf("invalid U3D shading modifier chain")
|
return name, fmt.Errorf("invalid U3D shading modifier chain")
|
||||||
@@ -461,7 +463,7 @@ func (d *u3dDecoder) node(r *u3dValues, kind uint32, name string) error {
|
|||||||
return r.err
|
return r.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// shading 分别读取网格和线集着色列表,保留列表内各着色器的先后次序
|
// shading 分别读取网格、线集和点集着色列表,保留列表内各着色器的先后次序
|
||||||
// 入参: r 字段读取器, name 目标节点名
|
// 入参: r 字段读取器, name 目标节点名
|
||||||
// 返回: error 结构、能力或预算错误
|
// 返回: error 结构、能力或预算错误
|
||||||
func (d *u3dDecoder) shading(r *u3dValues, name string) error {
|
func (d *u3dDecoder) shading(r *u3dValues, name string) error {
|
||||||
@@ -470,7 +472,7 @@ func (d *u3dDecoder) shading(r *u3dValues, name string) error {
|
|||||||
return fmt.Errorf("missing U3D shading target")
|
return fmt.Errorf("missing U3D shading target")
|
||||||
}
|
}
|
||||||
flags := r.u32()
|
flags := r.u32()
|
||||||
if flags & ^uint32(3) != 0 {
|
if flags & ^uint32(7) != 0 {
|
||||||
return fmt.Errorf("unsupported U3D shading modifier target")
|
return fmt.Errorf("unsupported U3D shading modifier target")
|
||||||
}
|
}
|
||||||
count := r.u32()
|
count := r.u32()
|
||||||
@@ -505,6 +507,9 @@ func (d *u3dDecoder) shading(r *u3dValues, name string) error {
|
|||||||
if flags&2 != 0 {
|
if flags&2 != 0 {
|
||||||
d.model.Nodes[index].LineShaders = lists
|
d.model.Nodes[index].LineShaders = lists
|
||||||
}
|
}
|
||||||
|
if flags&4 != 0 {
|
||||||
|
d.model.Nodes[index].PointShaders = lists
|
||||||
|
}
|
||||||
return r.err
|
return r.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-13
@@ -19,44 +19,50 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
// drawLines 绘制作者线集,端点独立着色且不参与面的背向剔除
|
// drawPrimitives 绘制作者点集及线集,独立着色且不参与面的背向剔除
|
||||||
// 入参: instance 场景实例, node 模型节点, mesh 属性数组, positions 世界位置, normals 世界法线, pass 绘制轮次, passIndex 轮次索引
|
// 入参: instance 场景实例, node 模型节点, mesh 属性数组, positions 世界位置, normals 世界法线, pass 绘制轮次, passIndex 轮次索引
|
||||||
// 返回: error 几何、资源或取消错误
|
// 返回: error 几何、资源或取消错误
|
||||||
func (r *u3dRender) drawLines(instance u3dInstance, node *U3DNode, mesh *U3DMesh, positions, normals []u3dVector, pass U3DViewPass, passIndex int) error {
|
func (r *u3dRender) drawPrimitives(instance u3dInstance, node *U3DNode, mesh *U3DMesh, positions, normals []u3dVector, pass U3DViewPass, passIndex int) error {
|
||||||
if r.depthOnly {
|
if r.depthOnly {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
shaded := r.mode.shaded()
|
shaded := r.mode.shaded()
|
||||||
for index, line := range mesh.Lines {
|
for index := range len(mesh.Lines) + len(mesh.Points) {
|
||||||
|
shadingIndex, corners := mesh.primitive(len(mesh.Faces) + index)
|
||||||
|
point := index >= len(mesh.Lines)
|
||||||
if index%128 == 0 {
|
if index%128 == 0 {
|
||||||
if err := r.ctx.Err(); err != nil {
|
if err := r.ctx.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if uint64(line.Shading) >= uint64(len(mesh.Shadings)) {
|
if uint64(shadingIndex) >= uint64(len(mesh.Shadings)) {
|
||||||
return fmt.Errorf("invalid U3D render line shading index")
|
return fmt.Errorf("invalid U3D render primitive shading index")
|
||||||
}
|
}
|
||||||
shading := mesh.Shadings[line.Shading]
|
shading := mesh.Shadings[shadingIndex]
|
||||||
if shading.Attributes > 3 || len(shading.TextureDimensions) > 8 {
|
if shading.Attributes > 3 || len(shading.TextureDimensions) > 8 {
|
||||||
return fmt.Errorf("invalid U3D render line shading")
|
return fmt.Errorf("invalid U3D render primitive shading")
|
||||||
}
|
}
|
||||||
var world [2]u3dVector
|
var world [2]u3dVector
|
||||||
var vertices [2]u3dVertex
|
var vertices [2]u3dVertex
|
||||||
for i, corner := range line.Corners {
|
for i, corner := range corners {
|
||||||
if uint64(corner.Position) >= uint64(len(positions)) || shaded && (!mesh.ExcludeNormals && uint64(corner.Normal) >= uint64(len(normals)) || shading.Attributes&1 != 0 && uint64(corner.Diffuse) >= uint64(len(mesh.Diffuse)) || shading.Attributes&2 != 0 && uint64(corner.Specular) >= uint64(len(mesh.Specular))) {
|
if uint64(corner.Position) >= uint64(len(positions)) || shaded && (!mesh.ExcludeNormals && uint64(corner.Normal) >= uint64(len(normals)) || shading.Attributes&1 != 0 && uint64(corner.Diffuse) >= uint64(len(mesh.Diffuse)) || shading.Attributes&2 != 0 && uint64(corner.Specular) >= uint64(len(mesh.Specular))) {
|
||||||
return fmt.Errorf("invalid U3D render line corner index")
|
return fmt.Errorf("invalid U3D render primitive corner index")
|
||||||
}
|
}
|
||||||
world[i] = positions[corner.Position]
|
world[i] = positions[corner.Position]
|
||||||
vertices[i].point = r.toCamera.point(world[i])
|
vertices[i].point = r.toCamera.point(world[i])
|
||||||
for _, value := range vertices[i].point {
|
for _, value := range vertices[i].point {
|
||||||
if !finite(value) {
|
if !finite(value) {
|
||||||
return fmt.Errorf("invalid U3D line camera position")
|
return fmt.Errorf("invalid U3D primitive camera position")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shaderNames := []string{""}
|
shaderNames := []string{""}
|
||||||
if uint64(line.Shading) < uint64(len(node.LineShaders)) {
|
shaders := node.LineShaders
|
||||||
shaderNames = node.LineShaders[line.Shading]
|
if point {
|
||||||
|
shaders = node.PointShaders
|
||||||
|
}
|
||||||
|
if uint64(shadingIndex) < uint64(len(shaders)) {
|
||||||
|
shaderNames = shaders[shadingIndex]
|
||||||
}
|
}
|
||||||
for _, name := range shaderNames {
|
for _, name := range shaderNames {
|
||||||
if err := r.ctx.Err(); err != nil {
|
if err := r.ctx.Err(); err != nil {
|
||||||
@@ -78,7 +84,7 @@ func (r *u3dRender) drawLines(instance u3dInstance, node *U3DNode, mesh *U3DMesh
|
|||||||
if instance.opacity >= 0 || r.opacity >= 0 {
|
if instance.opacity >= 0 || r.opacity >= 0 {
|
||||||
style.shader.Blend = 0x606
|
style.shader.Blend = 0x606
|
||||||
}
|
}
|
||||||
for i, corner := range line.Corners {
|
for i, corner := range corners {
|
||||||
vertices[i].color = [4]float64{r.auxiliaryColor[0], r.auxiliaryColor[1], r.auxiliaryColor[2], 1}
|
vertices[i].color = [4]float64{r.auxiliaryColor[0], r.auxiliaryColor[1], r.auxiliaryColor[2], 1}
|
||||||
if !shaded {
|
if !shaded {
|
||||||
continue
|
continue
|
||||||
@@ -105,6 +111,12 @@ func (r *u3dRender) drawLines(instance u3dInstance, node *U3DNode, mesh *U3DMesh
|
|||||||
vertices[i].color[3] *= r.opacity
|
vertices[i].color[3] *= r.opacity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if point {
|
||||||
|
if err := r.modePoint(vertices[0], style.shader, pass); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
if r.mode == U3DRenderVertices || r.mode == U3DRenderShadedVertices {
|
if r.mode == U3DRenderVertices || r.mode == U3DRenderShadedVertices {
|
||||||
for _, vertex := range vertices {
|
for _, vertex := range vertices {
|
||||||
fresh, err := r.uniquePrimitive(vertex, vertex, style.shader)
|
fresh, err := r.uniquePrimitive(vertex, vertex, style.shader)
|
||||||
|
|||||||
+98
-47
@@ -49,11 +49,13 @@ const (
|
|||||||
u3dLineContextCount
|
u3dLineContextCount
|
||||||
)
|
)
|
||||||
|
|
||||||
// u3dLineState 按顶点累计线段端点属性,预测开销不随顶点度数增长
|
// u3dLineState 按顶点缓存点集及线集属性预测
|
||||||
type u3dLineState struct {
|
type u3dLineState struct {
|
||||||
offsets [11]int
|
offsets [11]int
|
||||||
stride int
|
stride int
|
||||||
stats []float64
|
stats []float64
|
||||||
|
pointOffsets []uint32
|
||||||
|
pointPredicted []bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// u3dLineReader 保存当前续块的独立压缩上下文
|
// u3dLineReader 保存当前续块的独立压缩上下文
|
||||||
@@ -63,17 +65,17 @@ type u3dLineReader struct {
|
|||||||
hist [u3dLineContextCount]u3dHistogram
|
hist [u3dLineContextCount]u3dHistogram
|
||||||
}
|
}
|
||||||
|
|
||||||
// lineDeclaration 读取ECMA-363第9.6.3节的线集声明
|
// primitiveDeclaration 读取ECMA-363第9.6.2和9.6.3节的点集及线集声明
|
||||||
// 入参: r 字段读取器, name 资源名
|
// 入参: r 字段读取器, name 资源名, points 是否为点集
|
||||||
// 返回: error 结构、能力或预算错误
|
// 返回: error 结构、能力或预算错误
|
||||||
func (d *u3dDecoder) lineDeclaration(r *u3dValues, name string) error {
|
func (d *u3dDecoder) primitiveDeclaration(r *u3dValues, name string, points bool) error {
|
||||||
if _, exists := d.meshes[name]; exists {
|
if _, exists := d.meshes[name]; exists {
|
||||||
return fmt.Errorf("duplicate U3D model resource %q", name)
|
return fmt.Errorf("duplicate U3D model resource %q", name)
|
||||||
}
|
}
|
||||||
if r.u32() != 0 || r.u32() != 0 {
|
if r.u32() != 0 || r.u32() != 0 {
|
||||||
return fmt.Errorf("invalid U3D line declaration")
|
return fmt.Errorf("invalid U3D primitive declaration")
|
||||||
}
|
}
|
||||||
decl := &u3dMeshDeclaration{mesh: U3DMesh{Name: name}, line: new(u3dLineState)}
|
decl := &u3dMeshDeclaration{mesh: U3DMesh{Name: name}, line: new(u3dLineState), points: points}
|
||||||
for i := range decl.counts {
|
for i := range decl.counts {
|
||||||
decl.counts[i] = r.u32()
|
decl.counts[i] = r.u32()
|
||||||
}
|
}
|
||||||
@@ -88,11 +90,11 @@ func (d *u3dDecoder) lineDeclaration(r *u3dValues, name string) error {
|
|||||||
}
|
}
|
||||||
for range 3 {
|
for range 3 {
|
||||||
if r.u32() != 0 {
|
if r.u32() != 0 {
|
||||||
return fmt.Errorf("invalid U3D reserved line parameter")
|
return fmt.Errorf("invalid U3D reserved primitive parameter")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.u32() != 0 {
|
if r.u32() != 0 {
|
||||||
return fmt.Errorf("unsupported U3D skeletal line set")
|
return fmt.Errorf("unsupported U3D skeletal primitive set")
|
||||||
}
|
}
|
||||||
if err := r.done(); err != nil {
|
if err := r.done(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -100,7 +102,7 @@ func (d *u3dDecoder) lineDeclaration(r *u3dValues, name string) error {
|
|||||||
if decl.counts[1] == 0 {
|
if decl.counts[1] == 0 {
|
||||||
for _, count := range decl.counts[:6] {
|
for _, count := range decl.counts[:6] {
|
||||||
if count != 0 {
|
if count != 0 {
|
||||||
return fmt.Errorf("invalid U3D empty line set")
|
return fmt.Errorf("invalid U3D empty primitive set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
decl.decoded = true
|
decl.decoded = true
|
||||||
@@ -123,7 +125,11 @@ func (d *u3dDecoder) lineDeclaration(r *u3dValues, name string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c := decl.counts
|
c := decl.counts
|
||||||
bytes := uint64(c[0])*100 + (uint64(c[1])+uint64(c[2]))*12 + (uint64(c[3])+uint64(c[4])+uint64(c[5]))*16
|
primitiveBytes := uint64(100)
|
||||||
|
if points {
|
||||||
|
primitiveBytes = 52
|
||||||
|
}
|
||||||
|
bytes := uint64(c[0])*primitiveBytes + (uint64(c[1])+uint64(c[2]))*12 + (uint64(c[3])+uint64(c[4])+uint64(c[5]))*16
|
||||||
if err := d.reserve(bytes, 1); err != nil {
|
if err := d.reserve(bytes, 1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -131,16 +137,27 @@ func (d *u3dDecoder) lineDeclaration(r *u3dValues, name string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m := &decl.mesh
|
m := &decl.mesh
|
||||||
m.Lines = make([]U3DLine, 0, int(c[0]))
|
if points {
|
||||||
|
m.Points = make([]U3DPoint, 0, int(c[0]))
|
||||||
|
} else {
|
||||||
|
m.Lines = make([]U3DLine, 0, int(c[0]))
|
||||||
|
}
|
||||||
m.Positions, m.Normals = make([][3]float32, 0, int(c[1])), make([][3]float32, 0, int(c[2]))
|
m.Positions, m.Normals = make([][3]float32, 0, int(c[1])), make([][3]float32, 0, int(c[2]))
|
||||||
m.Diffuse, m.Specular, m.TextureCoordinates = make([][4]float32, 0, int(c[3])), make([][4]float32, 0, int(c[4])), make([][4]float32, 0, int(c[5]))
|
m.Diffuse, m.Specular, m.TextureCoordinates = make([][4]float32, 0, int(c[3])), make([][4]float32, 0, int(c[4])), make([][4]float32, 0, int(c[5]))
|
||||||
state.stats = make([]float64, int(c[1])*state.stride)
|
state.stats = make([]float64, int(c[1])*state.stride)
|
||||||
|
if points {
|
||||||
|
if err := d.reserve(uint64(c[1])*5+4, 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
state.pointOffsets = make([]uint32, 1, int(c[1])+1)
|
||||||
|
state.pointPredicted = make([]bool, int(c[1]))
|
||||||
|
}
|
||||||
d.meshes[name] = decl
|
d.meshes[name] = decl
|
||||||
d.meshOrder = append(d.meshOrder, name)
|
d.meshOrder = append(d.meshOrder, name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// average 取得指定顶点各属性的端点算术平均
|
// average 取得指定顶点各属性预测,点集法线使用缓存的球面均值
|
||||||
// 入参: position 顶点索引
|
// 入参: position 顶点索引
|
||||||
// 返回: [11][4]float64 法线、漫反射、镜面及八层纹理预测
|
// 返回: [11][4]float64 法线、漫反射、镜面及八层纹理预测
|
||||||
func (s *u3dLineState) average(position uint32) (values [11][4]float64) {
|
func (s *u3dLineState) average(position uint32) (values [11][4]float64) {
|
||||||
@@ -162,8 +179,14 @@ func (s *u3dLineState) average(position uint32) (values [11][4]float64) {
|
|||||||
// accumulate 累计线段端点的独立属性,重复使用的属性按端点分别计数
|
// accumulate 累计线段端点的独立属性,重复使用的属性按端点分别计数
|
||||||
// 入参: mesh 共享属性数组, line 已完成的线段
|
// 入参: mesh 共享属性数组, line 已完成的线段
|
||||||
func (s *u3dLineState) accumulate(mesh *U3DMesh, line U3DLine) {
|
func (s *u3dLineState) accumulate(mesh *U3DMesh, line U3DLine) {
|
||||||
shading := mesh.Shadings[line.Shading]
|
s.accumulateCorners(mesh, line.Shading, line.Corners[:])
|
||||||
for _, corner := range line.Corners {
|
}
|
||||||
|
|
||||||
|
// accumulateCorners 累计点或线端的属性,用于后续位置预测
|
||||||
|
// 入参: mesh 属性数组, shadingIndex 着色索引, corners 新增角点
|
||||||
|
func (s *u3dLineState) accumulateCorners(mesh *U3DMesh, shadingIndex uint32, corners []U3DCorner) {
|
||||||
|
shading := mesh.Shadings[shadingIndex]
|
||||||
|
for _, corner := range corners {
|
||||||
var values [11][4]float32
|
var values [11][4]float32
|
||||||
var used [11]bool
|
var used [11]bool
|
||||||
normal := mesh.Normals[corner.Normal]
|
normal := mesh.Normals[corner.Normal]
|
||||||
@@ -191,7 +214,7 @@ func (s *u3dLineState) accumulate(mesh *U3DMesh, line U3DLine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// value 按指定上下文和位宽读取线集字段
|
// value 按指定上下文和位宽读取点集或线集字段
|
||||||
// 入参: context 压缩上下文, small 是否为8位字段
|
// 入参: context 压缩上下文, small 是否为8位字段
|
||||||
// 返回: uint32 字段值
|
// 返回: uint32 字段值
|
||||||
func (r *u3dLineReader) value(context int, small bool) uint32 {
|
func (r *u3dLineReader) value(context int, small bool) uint32 {
|
||||||
@@ -209,7 +232,7 @@ func (r *u3dLineReader) value(context int, small bool) uint32 {
|
|||||||
// 返回: uint32 索引, error 读取或范围错误
|
// 返回: uint32 索引, error 读取或范围错误
|
||||||
func (r *u3dLineReader) index(count uint32) (uint32, error) {
|
func (r *u3dLineReader) index(count uint32) (uint32, error) {
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return 0, fmt.Errorf("invalid U3D line position range")
|
return 0, fmt.Errorf("invalid U3D primitive position range")
|
||||||
}
|
}
|
||||||
var value uint32
|
var value uint32
|
||||||
if r.bits != nil {
|
if r.bits != nil {
|
||||||
@@ -221,7 +244,7 @@ func (r *u3dLineReader) index(count uint32) (uint32, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if value >= count {
|
if value >= count {
|
||||||
return 0, fmt.Errorf("invalid U3D line position index")
|
return 0, fmt.Errorf("invalid U3D primitive position index")
|
||||||
}
|
}
|
||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
@@ -241,7 +264,7 @@ func (r *u3dLineReader) err() error {
|
|||||||
func (r *u3dLineReader) reconstruct(prediction [4]float64, scale float32, signContext, componentContext, dimensions int) (values [4]float32, err error) {
|
func (r *u3dLineReader) reconstruct(prediction [4]float64, scale float32, signContext, componentContext, dimensions int) (values [4]float32, err error) {
|
||||||
signs := r.value(signContext, true)
|
signs := r.value(signContext, true)
|
||||||
if signs >= 1<<dimensions {
|
if signs >= 1<<dimensions {
|
||||||
return values, fmt.Errorf("invalid U3D line difference signs")
|
return values, fmt.Errorf("invalid U3D primitive difference signs")
|
||||||
}
|
}
|
||||||
for i := range dimensions {
|
for i := range dimensions {
|
||||||
difference := float64(r.value(componentContext+i, false)) * float64(scale)
|
difference := float64(r.value(componentContext+i, false)) * float64(scale)
|
||||||
@@ -250,7 +273,7 @@ func (r *u3dLineReader) reconstruct(prediction [4]float64, scale float32, signCo
|
|||||||
}
|
}
|
||||||
values[i] = float32(prediction[i] + difference)
|
values[i] = float32(prediction[i] + difference)
|
||||||
if !finite(float64(values[i])) {
|
if !finite(float64(values[i])) {
|
||||||
return values, fmt.Errorf("invalid U3D reconstructed line attribute")
|
return values, fmt.Errorf("invalid U3D reconstructed primitive attribute")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values, r.err()
|
return values, r.err()
|
||||||
@@ -268,7 +291,7 @@ func (r *u3dLineReader) attribute(pool *[][4]float32, limit uint32, prediction [
|
|||||||
return uint32(len(*pool) - 1), nil
|
return uint32(len(*pool) - 1), nil
|
||||||
}
|
}
|
||||||
if flag != 0 || uint64(len(*pool)) >= uint64(limit) {
|
if flag != 0 || uint64(len(*pool)) >= uint64(limit) {
|
||||||
return 0, fmt.Errorf("invalid U3D line attribute pool")
|
return 0, fmt.Errorf("invalid U3D primitive attribute pool")
|
||||||
}
|
}
|
||||||
value, err := r.reconstruct(prediction, scale, sign, component, 4)
|
value, err := r.reconstruct(prediction, scale, sign, component, 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -279,13 +302,13 @@ func (r *u3dLineReader) attribute(pool *[][4]float32, limit uint32, prediction [
|
|||||||
return index, nil
|
return index, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// lineContinuation 按位置分段恢复线集,预测状态跨续块保存
|
// primitiveContinuation 按位置分段恢复点集及线集,预测状态跨续块保存
|
||||||
// 入参: r 字段读取器, name 资源名
|
// 入参: r 字段读取器, name 资源名, points 是否为点集
|
||||||
// 返回: error 结构、数值、预算或取消错误
|
// 返回: error 结构、数值、预算或取消错误
|
||||||
func (d *u3dDecoder) lineContinuation(r *u3dValues, name string) error {
|
func (d *u3dDecoder) primitiveContinuation(r *u3dValues, name string, points bool) error {
|
||||||
decl := d.meshes[name]
|
decl := d.meshes[name]
|
||||||
if decl == nil || decl.line == nil || decl.decoded || r.u32() != 0 {
|
if decl == nil || decl.line == nil || decl.points != points || decl.decoded || r.u32() != 0 {
|
||||||
return fmt.Errorf("invalid U3D line continuation reference")
|
return fmt.Errorf("invalid U3D primitive continuation reference")
|
||||||
}
|
}
|
||||||
start, end := r.u32(), r.u32()
|
start, end := r.u32(), r.u32()
|
||||||
mesh := &decl.mesh
|
mesh := &decl.mesh
|
||||||
@@ -293,7 +316,7 @@ func (d *u3dDecoder) lineContinuation(r *u3dValues, name string) error {
|
|||||||
return r.err
|
return r.err
|
||||||
}
|
}
|
||||||
if uint64(start) != uint64(len(mesh.Positions)) || end <= start || end > decl.counts[1] {
|
if uint64(start) != uint64(len(mesh.Positions)) || end <= start || end > decl.counts[1] {
|
||||||
return fmt.Errorf("invalid U3D line resolution range")
|
return fmt.Errorf("invalid U3D primitive resolution range")
|
||||||
}
|
}
|
||||||
reader := &u3dLineReader{values: r}
|
reader := &u3dLineReader{values: r}
|
||||||
var charged uint64
|
var charged uint64
|
||||||
@@ -322,7 +345,6 @@ func (d *u3dDecoder) lineContinuation(r *u3dValues, name string) error {
|
|||||||
prediction[i] = float64(value)
|
prediction[i] = float64(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attributes := decl.line.average(split)
|
|
||||||
value, err := reader.reconstruct(prediction, mesh.InverseQuantization[0], u3dLinePositionSign, u3dLinePositionX, 3)
|
value, err := reader.reconstruct(prediction, mesh.InverseQuantization[0], u3dLinePositionSign, u3dLinePositionX, 3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -331,8 +353,21 @@ func (d *u3dDecoder) lineContinuation(r *u3dValues, name string) error {
|
|||||||
normalStart := uint32(len(mesh.Normals))
|
normalStart := uint32(len(mesh.Normals))
|
||||||
normals := reader.value(u3dLineNormalCount, false)
|
normals := reader.value(u3dLineNormalCount, false)
|
||||||
if normals > decl.counts[2]-normalStart {
|
if normals > decl.counts[2]-normalStart {
|
||||||
return fmt.Errorf("invalid U3D line normal count")
|
return fmt.Errorf("invalid U3D primitive normal count")
|
||||||
}
|
}
|
||||||
|
if points && position > 0 && normals > 0 && !decl.line.pointPredicted[split] {
|
||||||
|
first, last := decl.line.pointOffsets[split], decl.line.pointOffsets[split+1]
|
||||||
|
prediction, err := d.pointNormalPrediction(mesh, mesh.Points[first:last])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
base := int(split)*decl.line.stride + decl.line.offsets[0]
|
||||||
|
for i, value := range prediction {
|
||||||
|
decl.line.stats[base+i] = value * float64(last-first)
|
||||||
|
}
|
||||||
|
decl.line.pointPredicted[split] = true
|
||||||
|
}
|
||||||
|
attributes := decl.line.average(split)
|
||||||
for i := uint32(0); i < normals; i++ {
|
for i := uint32(0); i < normals; i++ {
|
||||||
if i%256 == 0 {
|
if i%256 == 0 {
|
||||||
if err := d.ctx.Err(); err != nil {
|
if err := d.ctx.Err(); err != nil {
|
||||||
@@ -346,8 +381,8 @@ func (d *u3dDecoder) lineContinuation(r *u3dValues, name string) error {
|
|||||||
mesh.Normals = append(mesh.Normals, [3]float32{value[0], value[1], value[2]})
|
mesh.Normals = append(mesh.Normals, [3]float32{value[0], value[1], value[2]})
|
||||||
}
|
}
|
||||||
lines := reader.value(u3dLineCount, false)
|
lines := reader.value(u3dLineCount, false)
|
||||||
if lines > decl.counts[0]-uint32(len(mesh.Lines)) {
|
if lines > decl.counts[0]-uint32(len(mesh.Lines)+len(mesh.Points)) {
|
||||||
return fmt.Errorf("invalid U3D line count")
|
return fmt.Errorf("invalid U3D primitive count")
|
||||||
}
|
}
|
||||||
for i := uint32(0); i < lines; i++ {
|
for i := uint32(0); i < lines; i++ {
|
||||||
if i%256 == 0 {
|
if i%256 == 0 {
|
||||||
@@ -357,19 +392,24 @@ func (d *u3dDecoder) lineContinuation(r *u3dValues, name string) error {
|
|||||||
}
|
}
|
||||||
line := U3DLine{Shading: reader.value(u3dLineShading, false)}
|
line := U3DLine{Shading: reader.value(u3dLineShading, false)}
|
||||||
if uint64(line.Shading) >= uint64(len(mesh.Shadings)) {
|
if uint64(line.Shading) >= uint64(len(mesh.Shadings)) {
|
||||||
return fmt.Errorf("invalid U3D line shading index")
|
return fmt.Errorf("invalid U3D primitive shading index")
|
||||||
}
|
}
|
||||||
first, err := reader.index(position)
|
corners := line.Corners[:1]
|
||||||
if err != nil {
|
corners[0].Position = position
|
||||||
return err
|
if !points {
|
||||||
|
first, err := reader.index(position)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
line.Corners[0].Position, line.Corners[1].Position = first, position
|
||||||
|
corners = line.Corners[:]
|
||||||
}
|
}
|
||||||
line.Corners[0].Position, line.Corners[1].Position = first, position
|
|
||||||
shading := mesh.Shadings[line.Shading]
|
shading := mesh.Shadings[line.Shading]
|
||||||
for j := range line.Corners {
|
for j := range corners {
|
||||||
corner := &line.Corners[j]
|
corner := &corners[j]
|
||||||
normal := reader.value(u3dLineNormalIndex, false)
|
normal := reader.value(u3dLineNormalIndex, false)
|
||||||
if normal >= normals {
|
if normal >= normals {
|
||||||
return fmt.Errorf("invalid U3D line local normal index")
|
return fmt.Errorf("invalid U3D primitive local normal index")
|
||||||
}
|
}
|
||||||
corner.Normal = normalStart + normal
|
corner.Normal = normalStart + normal
|
||||||
if shading.Attributes&1 != 0 {
|
if shading.Attributes&1 != 0 {
|
||||||
@@ -394,8 +434,15 @@ func (d *u3dDecoder) lineContinuation(r *u3dValues, name string) error {
|
|||||||
if err := reader.err(); err != nil {
|
if err := reader.err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mesh.Lines = append(mesh.Lines, line)
|
if points {
|
||||||
decl.line.accumulate(mesh, line)
|
mesh.Points = append(mesh.Points, U3DPoint{Shading: line.Shading, Corners: [1]U3DCorner{corners[0]}})
|
||||||
|
} else {
|
||||||
|
mesh.Lines = append(mesh.Lines, line)
|
||||||
|
}
|
||||||
|
decl.line.accumulateCorners(mesh, line.Shading, corners)
|
||||||
|
}
|
||||||
|
if points {
|
||||||
|
decl.line.pointOffsets = append(decl.line.pointOffsets, uint32(len(mesh.Points)))
|
||||||
}
|
}
|
||||||
if err := reader.err(); err != nil {
|
if err := reader.err(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -406,13 +453,13 @@ func (d *u3dDecoder) lineContinuation(r *u3dValues, name string) error {
|
|||||||
return io.ErrUnexpectedEOF
|
return io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if b.u32() != 0 {
|
if b.u32() != 0 {
|
||||||
return fmt.Errorf("invalid U3D line compression flush")
|
return fmt.Errorf("invalid U3D primitive compression flush")
|
||||||
}
|
}
|
||||||
if b.err != nil {
|
if b.err != nil {
|
||||||
return b.err
|
return b.err
|
||||||
}
|
}
|
||||||
if (b.pos-16+7)/8 != uint64(len(b.data)) {
|
if (b.pos-16+7)/8 != uint64(len(b.data)) {
|
||||||
return fmt.Errorf("unexpected U3D line compressed data")
|
return fmt.Errorf("unexpected U3D primitive compressed data")
|
||||||
}
|
}
|
||||||
r.pos = len(r.data)
|
r.pos = len(r.data)
|
||||||
}
|
}
|
||||||
@@ -420,14 +467,18 @@ func (d *u3dDecoder) lineContinuation(r *u3dValues, name string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if end == decl.counts[1] {
|
if end == decl.counts[1] {
|
||||||
for i, count := range []int{len(mesh.Lines), len(mesh.Positions), len(mesh.Normals), len(mesh.Diffuse), len(mesh.Specular), len(mesh.TextureCoordinates)} {
|
for i, count := range []int{len(mesh.Lines) + len(mesh.Points), len(mesh.Positions), len(mesh.Normals), len(mesh.Diffuse), len(mesh.Specular), len(mesh.TextureCoordinates)} {
|
||||||
if uint64(count) != uint64(decl.counts[i]) {
|
if uint64(count) != uint64(decl.counts[i]) {
|
||||||
return fmt.Errorf("U3D line set does not match declaration")
|
return fmt.Errorf("U3D primitive set does not match declaration")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
decl.decoded = true
|
decl.decoded = true
|
||||||
d.remaining += uint64(len(decl.line.stats)) * 8
|
d.remaining += uint64(len(decl.line.stats)) * 8
|
||||||
decl.line.stats = nil
|
decl.line.stats = nil
|
||||||
|
if points {
|
||||||
|
d.remaining += uint64(cap(decl.line.pointOffsets))*4 + uint64(len(decl.line.pointPredicted))
|
||||||
|
decl.line.pointOffsets, decl.line.pointPredicted = nil, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
+19
-6
@@ -19,7 +19,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// U3DMesh 保存静态三角面和线段的共享属性数组,索引对应原始作者几何
|
// U3DMesh 保存静态三角面、线段和点的共享属性数组,索引对应原始作者几何
|
||||||
type U3DMesh struct {
|
type U3DMesh struct {
|
||||||
Name string
|
Name string
|
||||||
ExcludeNormals bool
|
ExcludeNormals bool
|
||||||
@@ -28,6 +28,7 @@ type U3DMesh struct {
|
|||||||
Shadings []U3DShading
|
Shadings []U3DShading
|
||||||
Faces []U3DFace
|
Faces []U3DFace
|
||||||
Lines []U3DLine
|
Lines []U3DLine
|
||||||
|
Points []U3DPoint
|
||||||
Quality [3]uint32
|
Quality [3]uint32
|
||||||
InverseQuantization [5]float32
|
InverseQuantization [5]float32
|
||||||
NormalCrease, NormalUpdate, NormalTolerance float32
|
NormalCrease, NormalUpdate, NormalTolerance float32
|
||||||
@@ -52,29 +53,41 @@ type U3DLine struct {
|
|||||||
Corners [2]U3DCorner
|
Corners [2]U3DCorner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// U3DPoint 保存点集着色描述及独立属性索引
|
||||||
|
type U3DPoint struct {
|
||||||
|
Shading uint32
|
||||||
|
Corners [1]U3DCorner
|
||||||
|
}
|
||||||
|
|
||||||
// U3DCorner 保存各属性索引,未启用的属性不可用于访问数组
|
// U3DCorner 保存各属性索引,未启用的属性不可用于访问数组
|
||||||
type U3DCorner struct {
|
type U3DCorner struct {
|
||||||
Position, Normal, Diffuse, Specular uint32
|
Position, Normal, Diffuse, Specular uint32
|
||||||
Texture [8]uint32
|
Texture [8]uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// u3dMeshDeclaration 保存基础网格或线集声明及续块接收状态
|
// u3dMeshDeclaration 保存几何声明及续块接收状态
|
||||||
type u3dMeshDeclaration struct {
|
type u3dMeshDeclaration struct {
|
||||||
mesh U3DMesh
|
mesh U3DMesh
|
||||||
counts [7]uint32
|
counts [7]uint32
|
||||||
decoded bool
|
decoded bool
|
||||||
line *u3dLineState
|
line *u3dLineState
|
||||||
|
points bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// primitive 访问三角面或线段的着色编号和角点,不复制属性数组
|
// primitive 访问三角面、线段或点的着色编号和角点,不复制属性数组
|
||||||
// 入参: index 图元下标,三角面在前,线段在后
|
// 入参: index 图元下标,依次为三角面、线段和点
|
||||||
// 返回: uint32 着色编号, []U3DCorner 只读角点
|
// 返回: uint32 着色编号, []U3DCorner 只读角点
|
||||||
func (m *U3DMesh) primitive(index int) (uint32, []U3DCorner) {
|
func (m *U3DMesh) primitive(index int) (uint32, []U3DCorner) {
|
||||||
if index < len(m.Faces) {
|
if index < len(m.Faces) {
|
||||||
return m.Faces[index].Shading, m.Faces[index].Corners[:]
|
return m.Faces[index].Shading, m.Faces[index].Corners[:]
|
||||||
}
|
}
|
||||||
line := &m.Lines[index-len(m.Faces)]
|
index -= len(m.Faces)
|
||||||
return line.Shading, line.Corners[:]
|
if index < len(m.Lines) {
|
||||||
|
line := &m.Lines[index]
|
||||||
|
return line.Shading, line.Corners[:]
|
||||||
|
}
|
||||||
|
point := &m.Points[index-len(m.Lines)]
|
||||||
|
return point.Shading, point.Corners[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// meshDeclaration 读取完整基础网格声明,不将渐进网格当作完整模型
|
// meshDeclaration 读取完整基础网格声明,不将渐进网格当作完整模型
|
||||||
|
|||||||
+4
-1
@@ -236,12 +236,15 @@ func (r *u3dRender) drawBounds(instance u3dInstance, node *U3DNode, mesh *U3DMes
|
|||||||
minimum := u3dVector{math.Inf(1), math.Inf(1), math.Inf(1)}
|
minimum := u3dVector{math.Inf(1), math.Inf(1), math.Inf(1)}
|
||||||
maximum := u3dVector{math.Inf(-1), math.Inf(-1), math.Inf(-1)}
|
maximum := u3dVector{math.Inf(-1), math.Inf(-1), math.Inf(-1)}
|
||||||
visible := false
|
visible := false
|
||||||
for i := range len(mesh.Faces) + len(mesh.Lines) {
|
for i := range len(mesh.Faces) + len(mesh.Lines) + len(mesh.Points) {
|
||||||
shading, corners := mesh.primitive(i)
|
shading, corners := mesh.primitive(i)
|
||||||
shaders := node.Shaders
|
shaders := node.Shaders
|
||||||
if i >= len(mesh.Faces) {
|
if i >= len(mesh.Faces) {
|
||||||
shaders = node.LineShaders
|
shaders = node.LineShaders
|
||||||
}
|
}
|
||||||
|
if i >= len(mesh.Faces)+len(mesh.Lines) {
|
||||||
|
shaders = node.PointShaders
|
||||||
|
}
|
||||||
if i%256 == 0 {
|
if i%256 == 0 {
|
||||||
if err := r.ctx.Err(); err != nil {
|
if err := r.ctx.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ofdgo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pointNormalPrediction 按ECMA-363第9.6.2.2.4.4节计算点集球面法线预测
|
||||||
|
// 入参: mesh 属性数组, points 分裂位置的点
|
||||||
|
// 返回: u3dVector 预测方向, error 不支持的多方向预测或取消错误
|
||||||
|
func (d *u3dDecoder) pointNormalPrediction(mesh *U3DMesh, points []U3DPoint) (u3dVector, error) {
|
||||||
|
var directions [2]u3dVector
|
||||||
|
var counts [2]int
|
||||||
|
var previous [3]float32
|
||||||
|
var direction u3dVector
|
||||||
|
for i, point := range points {
|
||||||
|
if i%256 == 0 {
|
||||||
|
if err := d.ctx.Err(); err != nil {
|
||||||
|
return u3dVector{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
normal := mesh.Normals[point.Corners[0].Normal]
|
||||||
|
if i == 0 || normal != previous {
|
||||||
|
direction = (u3dVector{float64(normal[0]), float64(normal[1]), float64(normal[2])}).unit()
|
||||||
|
previous = normal
|
||||||
|
}
|
||||||
|
if counts[0] == 0 || direction == directions[0] {
|
||||||
|
directions[0] = direction
|
||||||
|
counts[0]++
|
||||||
|
} else if counts[1] == 0 || direction == directions[1] {
|
||||||
|
directions[1] = direction
|
||||||
|
counts[1]++
|
||||||
|
} else {
|
||||||
|
return u3dVector{}, fmt.Errorf("unsupported U3D point normal prediction with multiple directions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if counts[1] == 0 {
|
||||||
|
return directions[0], nil
|
||||||
|
}
|
||||||
|
a, b := directions[0], directions[1]
|
||||||
|
if a == (u3dVector{}) || b == (u3dVector{}) {
|
||||||
|
return u3dVector{}, fmt.Errorf("unsupported U3D point normal prediction with zero direction")
|
||||||
|
}
|
||||||
|
dot := max(-1, min(1, a.dot(b)))
|
||||||
|
tangent := b.sub(u3dVector{a[0] * dot, a[1] * dot, a[2] * dot})
|
||||||
|
length := math.Sqrt(tangent.dot(tangent))
|
||||||
|
if length == 0 {
|
||||||
|
return u3dVector{}, fmt.Errorf("unsupported U3D antipodal point normal prediction")
|
||||||
|
}
|
||||||
|
angle := math.Atan2(length, dot) * (float64(counts[1]) / float64(len(points)))
|
||||||
|
sine, cosine := math.Sincos(angle)
|
||||||
|
var result u3dVector
|
||||||
|
for i := range result {
|
||||||
|
result[i] = a[i]*cosine + tangent[i]/length*sine
|
||||||
|
}
|
||||||
|
return result.unit(), nil
|
||||||
|
}
|
||||||
+1
-1
@@ -106,7 +106,7 @@ type u3dRender struct {
|
|||||||
order uint64
|
order uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderU3D 绘制静态无纹理网格、线集及其顶点或包围盒,返回不透明图像,不执行脚本或动画
|
// RenderU3D 绘制静态无纹理网格、线集、点集及包围盒,返回不透明图像,不执行脚本或动画
|
||||||
// 采用逐顶点光照及逐像素深度混合,缺失资源回退为空网格、灰色材质或无光源
|
// 采用逐顶点光照及逐像素深度混合,缺失资源回退为空网格、灰色材质或无光源
|
||||||
// 入参: ctx 取消上下文, model 场景, options 视图、输出与资源限制
|
// 入参: ctx 取消上下文, model 场景, options 视图、输出与资源限制
|
||||||
// 返回: *image.RGBA 图像, error 参数、能力、预算或取消错误
|
// 返回: *image.RGBA 图像, error 参数、能力、预算或取消错误
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ func (r *u3dRender) sceneDiagonal() (float64, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mesh := &r.model.Meshes[index]
|
mesh := &r.model.Meshes[index]
|
||||||
for i := range len(mesh.Faces) + len(mesh.Lines) {
|
for i := range len(mesh.Faces) + len(mesh.Lines) + len(mesh.Points) {
|
||||||
_, corners := mesh.primitive(i)
|
_, corners := mesh.primitive(i)
|
||||||
if i%256 == 0 {
|
if i%256 == 0 {
|
||||||
if err := r.ctx.Err(); err != nil {
|
if err := r.ctx.Err(); err != nil {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (r *u3dRender) drawMesh(instance u3dInstance, node *U3DNode, mesh *U3DMesh,
|
|||||||
normals[i] = inverse.normal(u3dVector{float64(n[0]), float64(n[1]), float64(n[2])})
|
normals[i] = inverse.normal(u3dVector{float64(n[0]), float64(n[1]), float64(n[2])})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := r.drawLines(instance, node, mesh, positions, normals, pass, passIndex); err != nil {
|
if err := r.drawPrimitives(instance, node, mesh, positions, normals, pass, passIndex); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for index, face := range mesh.Faces {
|
for index, face := range mesh.Faces {
|
||||||
|
|||||||
Reference in new issue
Block a user