mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
870 lines
28 KiB
Go
870 lines
28 KiB
Go
// 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 (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/xiaoqidun/pdfgo"
|
|
)
|
|
|
|
// draw 按原始顺序在当前颜色空间绘制图元及透明组
|
|
// 入参: nodes 图元, pixels 背景与输出像素, space 混合空间
|
|
// 返回: error 几何或颜色错误
|
|
func (c *pdfCompositor) draw(nodes []pdfCompositeNode, pixels []pdfCompositePixel, space *pdfgo.ColorSpace) error {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
for _, node := range nodes {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if node.group != nil {
|
|
if err := c.drawGroup(node, pixels, space); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
if err := c.drawMark(node, pixels, space); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// drawMark 将填充与描边作为同一对象,保留重叠区域的挖空和叠印语义
|
|
// 入参: node 图元, pixels 背景与输出像素, space 混合空间
|
|
// 返回: error 几何或颜色错误
|
|
func (c *pdfCompositor) drawMark(node pdfCompositeNode, pixels []pdfCompositePixel, space *pdfgo.ColorSpace) error {
|
|
box, err := c.markBounds(node)
|
|
if err != nil || c.pixelBounds(box).Empty() {
|
|
return err
|
|
}
|
|
style, fill, stroke := node.style()
|
|
mask, err := c.mask(style.SoftMask, space)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var initial []pdfCompositePixel
|
|
result := pixels
|
|
grouped := fill && stroke && (style.FillOverprint || style.StrokeOverprint) && style.Fill.Alpha == style.Stroke.Alpha
|
|
if fill && stroke {
|
|
initial = c.acquirePixels(len(pixels))
|
|
defer c.releasePixels(initial)
|
|
copy(initial, pixels)
|
|
if grouped {
|
|
result = c.acquirePixels(len(pixels))
|
|
defer c.releasePixels(result)
|
|
copy(result, pixels)
|
|
for i := range result {
|
|
result[i].effect = 0
|
|
result[i].shape = 0
|
|
}
|
|
}
|
|
}
|
|
for _, outline := range []bool{false, true} {
|
|
if outline && !stroke || !outline && !fill {
|
|
continue
|
|
}
|
|
paint, overprint := style.Fill, style.FillOverprint
|
|
if outline {
|
|
paint, overprint = style.Stroke, style.StrokeOverprint
|
|
}
|
|
mode, paintMask := style.BlendMode, mask
|
|
var knockout []pdfCompositePixel
|
|
if grouped {
|
|
paint.Alpha, mode, paintMask = 1, "Normal", nil
|
|
} else if outline {
|
|
knockout = initial
|
|
}
|
|
if err := c.drawPaint(node, outline, paint, mode, overprint, paintMask, knockout, result, space); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if grouped {
|
|
return pdfCompositeGroup(c.importer.ctx, pixels, initial, result, space, space, style.Fill.Alpha, mask, style.BlendMode, style.RenderingIntent, style.ColorConversion)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// drawPaint 结合几何覆盖与源颜色绘制一个填充或描边
|
|
// 入参: node 图元, outline 描边标志, paint 画刷, mode 混合模式, overprint 叠印标志, mask 蒙版, initial 挖空初始背景, pixels 输出, space 混合空间
|
|
// 返回: 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 {
|
|
coverage, err := c.coverage(node, outline)
|
|
if err != nil || coverage == nil {
|
|
return err
|
|
}
|
|
style, _, _ := node.style()
|
|
var transfer *pdfgo.TransferFunction
|
|
imageShape := false
|
|
if c.transfers != nil {
|
|
transfer, err = c.selectTransfer(node, outline)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if node.image != nil && (node.image.Image.ImageMask || node.image.Image.Mask != nil) {
|
|
soft, err := node.image.Image.HasSoftMask()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
imageShape = !soft
|
|
}
|
|
}
|
|
var processMask [4]bool
|
|
processOverprint := false
|
|
if overprint && space.Model == "DeviceCMYK" && !space.Calibrated() {
|
|
if node.image != nil {
|
|
processMask, processOverprint, err = pdfImageProcessColorants(node.image.Image)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
processMask, processOverprint = paint.Process.CMYKMask()
|
|
}
|
|
}
|
|
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
|
|
compatible := overprint && !processOverprint && style.OverprintMode == 1 && uniform && paint.SourceSpace == "DeviceCMYK" && space.Model == "DeviceCMYK" && !space.Calibrated()
|
|
var marked *[4]bool
|
|
if processOverprint {
|
|
marked = &processMask
|
|
}
|
|
var pattern []pdfCompositePixel
|
|
if paint.Tiling != nil && (node.image == nil || node.image.Image.ImageMask) {
|
|
backdrop := pixels
|
|
if initial != nil {
|
|
backdrop = initial
|
|
}
|
|
pattern, err = c.tiling(paint, backdrop, space)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer c.releasePixels(pattern)
|
|
}
|
|
var shading []pdfShadingPixel
|
|
if paint.Mesh != nil && (node.image == nil || node.image.Image.ImageMask) {
|
|
shading, err = c.mesh(paint.Mesh, space, style.ColorConversion)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if (paint.Axial != nil || paint.Radial != nil || paint.Function != nil) && (node.image == nil || node.image.Image.ImageMask) {
|
|
shading, err = c.gradient(paint, space, &node, outline)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
var uniformValues [4]float64
|
|
uniformReady := false
|
|
var sampler *pdfCompositeColorSampler
|
|
if g := paint.Axial; node.image == nil && shading == nil && g != nil && g.Bounds == nil && g.Background == nil &&
|
|
(g.Start.Y == g.End.Y && c.inverse[2] == 0 || g.Start.X == g.End.X && c.inverse[1] == 0) {
|
|
sampler = &pdfCompositeColorSampler{paint: paint, space: space, intent: style.RenderingIntent, conversion: style.ColorConversion}
|
|
}
|
|
geometry, err := c.geometry(node, outline)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
region := c.pixelBounds(geometry.box)
|
|
for y := region.Min.Y; y < region.Max.Y; y++ {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
for x := region.Min.X; x < region.Max.X; x++ {
|
|
_, _, _, a := coverage.At(x, y).RGBA()
|
|
if a == 0 {
|
|
continue
|
|
}
|
|
index := y*c.width + x
|
|
shape, opacity := float64(a)/65535, paint.Alpha
|
|
selectionShape := 1.0
|
|
if mask != nil {
|
|
opacity *= mask[index]
|
|
}
|
|
var values [4]float64
|
|
if pattern != nil {
|
|
sample := pattern[index]
|
|
values = sample.values
|
|
shape *= sample.shape
|
|
if sample.shape != 0 {
|
|
opacity *= sample.alpha / sample.shape
|
|
}
|
|
if node.image != nil {
|
|
point := c.inverse.Apply(pdfgo.Point{X: c.box.X + (float64(x)+.5)*step, Y: c.box.Y + (float64(y)+.5)*step})
|
|
var alpha float64
|
|
_, alpha, err = c.imageColor(node.image, point, space)
|
|
if imageShape {
|
|
selectionShape = alpha
|
|
}
|
|
opacity *= alpha
|
|
}
|
|
} else if shading != nil {
|
|
values = shading[index].values
|
|
if paint.Mesh == nil {
|
|
shape = shading[index].shape
|
|
} else {
|
|
shape *= shading[index].shape
|
|
}
|
|
if node.image != nil && paint.Mesh != nil {
|
|
point := c.inverse.Apply(pdfgo.Point{X: c.box.X + (float64(x)+.5)*step, Y: c.box.Y + (float64(y)+.5)*step})
|
|
var alpha float64
|
|
_, alpha, err = c.imageColor(node.image, point, space)
|
|
if imageShape {
|
|
selectionShape = alpha
|
|
}
|
|
opacity *= alpha
|
|
}
|
|
} else if uniform {
|
|
if !uniformReady {
|
|
uniformValues, _, err = pdfCompositeColor(paint, pdfgo.Point{}, space, style.RenderingIntent, style.ColorConversion)
|
|
uniformReady = true
|
|
}
|
|
values = uniformValues
|
|
} else {
|
|
point := c.inverse.Apply(pdfgo.Point{X: c.box.X + (float64(x)+.5)*step, Y: c.box.Y + (float64(y)+.5)*step})
|
|
if node.image != nil {
|
|
var alpha float64
|
|
values, alpha, err = c.imageColor(node.image, point, space)
|
|
if imageShape {
|
|
selectionShape = alpha
|
|
}
|
|
opacity *= alpha
|
|
} else {
|
|
var visible bool
|
|
if sampler != nil {
|
|
values, visible, err = sampler.color(point)
|
|
} else {
|
|
values, visible, err = pdfCompositeColor(paint, point, space, style.RenderingIntent, style.ColorConversion)
|
|
}
|
|
if !visible {
|
|
shape = 0
|
|
}
|
|
}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if style.AlphaIsShape {
|
|
shape, opacity = shape*opacity, 1
|
|
}
|
|
if initial != nil {
|
|
pixel := initial[index]
|
|
if err := pdfCompositePaintOver(&pixel, values, opacity, space, mode, compatible, marked); err != nil {
|
|
return err
|
|
}
|
|
if err := pdfCompositeInterpolate(&pixels[index], pixel, shape); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := pdfCompositePaintOver(&pixels[index], values, shape*opacity, space, mode, compatible, marked); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
pixels[index].shape += shape * (1 - pixels[index].shape)
|
|
if c.transfers != nil && shape != 0 && selectionShape != 0 {
|
|
c.transfers[index] = transfer
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pdfCompositeInterpolate 按形状覆盖混合预乘颜色及累计透明度
|
|
// 入参: target 原像素与输出, source 替换像素, shape 覆盖率
|
|
// 返回: error 像素或覆盖率错误
|
|
func pdfCompositeInterpolate(target *pdfCompositePixel, source pdfCompositePixel, shape float64) error {
|
|
pixel := pdfgo.ColorantPixel{Values: target.values[:], Alpha: target.alpha, Shape: target.shape, Effect: target.effect}
|
|
if err := pixel.Interpolate(pdfgo.ColorantPixel{Values: source.values[:], Alpha: source.alpha, Shape: source.shape, Effect: source.effect}, shape); err != nil {
|
|
return err
|
|
}
|
|
target.alpha, target.effect = pixel.Alpha, pixel.Effect
|
|
return nil
|
|
}
|
|
|
|
// drawGroup 按隔离标志保留初始背景,移除背景贡献后应用组透明度
|
|
// 入参: node 透明组, pixels 父组像素, parent 父混合空间
|
|
// 返回: error 颜色或合成错误
|
|
func (c *pdfCompositor) drawGroup(node pdfCompositeNode, pixels []pdfCompositePixel, parent *pdfgo.ColorSpace) error {
|
|
g := node.group
|
|
b, err := c.groupBounds(node)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if b.W <= 0 || b.H <= 0 || b.X >= c.box.X+c.box.W || b.Y >= c.box.Y+c.box.H || b.X+b.W <= c.box.X || b.Y+b.H <= c.box.Y {
|
|
return nil
|
|
}
|
|
blocked := c.transferBlocked
|
|
c.transferBlocked = blocked || g.Alpha != 1 || g.SoftMask != nil || !pdfNormalBlend(g.BlendMode)
|
|
defer func() { c.transferBlocked = blocked }()
|
|
if c.transfers != nil && node.textObject != nil && !c.transferBlocked {
|
|
for _, child := range node.children {
|
|
opaque, err := c.importer.transferOpaque(child)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !opaque {
|
|
c.transferBlocked = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
space := g.ColorSpace
|
|
if space == nil {
|
|
space = parent
|
|
}
|
|
if !g.Isolated && !g.Knockout && g.Alpha == 1 && g.SoftMask == nil && pdfNormalBlend(g.BlendMode) && space.Equal(parent) {
|
|
return c.draw(node.children, pixels, space)
|
|
}
|
|
initial := c.acquirePixels(len(pixels))
|
|
defer c.releasePixels(initial)
|
|
if g.Isolated {
|
|
clear(initial)
|
|
} else {
|
|
sameSpace := space.Equal(parent)
|
|
components := parent.Components()
|
|
for i, pixel := range pixels {
|
|
if i%pdfCompositeTileSize == 0 {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
values := pixel.values
|
|
if !sameSpace {
|
|
var err error
|
|
values, err = space.Convert(values[:components], parent, "RelativeColorimetric")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
initial[i] = pdfCompositePixel{values: values, alpha: pixel.alpha}
|
|
}
|
|
}
|
|
result := c.acquirePixels(len(initial))
|
|
defer c.releasePixels(result)
|
|
copy(result, initial)
|
|
if g.Knockout {
|
|
if err := c.drawKnockout(node.children, initial, result, space); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := c.draw(node.children, result, space); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
mask, err := c.mask(g.SoftMask, parent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if g.AlphaIsShape {
|
|
for i := range result {
|
|
if i%pdfCompositeTileSize == 0 {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
result[i].shape *= g.Alpha
|
|
if mask != nil {
|
|
result[i].shape *= mask[i]
|
|
}
|
|
}
|
|
}
|
|
return pdfCompositeGroup(c.importer.ctx, pixels, initial, result, space, parent, g.Alpha, mask, g.BlendMode, g.RenderingIntent, g.ColorConversion)
|
|
}
|
|
|
|
// drawKnockout 将每个对象与组初始背景合成,再按对象形状替换先前对象
|
|
// 入参: nodes 组图元, initial 初始背景, pixels 组输出, space 混合空间
|
|
// 返回: error 图元或合成错误
|
|
func (c *pdfCompositor) drawKnockout(nodes []pdfCompositeNode, initial, pixels []pdfCompositePixel, space *pdfgo.ColorSpace) error {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
candidate := c.acquirePixels(len(initial))
|
|
defer c.releasePixels(candidate)
|
|
clear(candidate)
|
|
for _, node := range nodes {
|
|
var box Box
|
|
var err error
|
|
if node.group != nil {
|
|
box, err = c.groupBounds(node)
|
|
} else {
|
|
box, err = c.markBounds(node)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
region := c.pixelBounds(box)
|
|
if region.Empty() {
|
|
continue
|
|
}
|
|
for y := region.Min.Y; y < region.Max.Y; y++ {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
start, end := y*c.width+region.Min.X, y*c.width+region.Max.X
|
|
copy(candidate[start:end], initial[start:end])
|
|
}
|
|
if err := c.draw([]pdfCompositeNode{node}, candidate, space); err != nil {
|
|
return err
|
|
}
|
|
for y := region.Min.Y; y < region.Max.Y; y++ {
|
|
for x := region.Min.X; x < region.Max.X; x++ {
|
|
i := y*c.width + x
|
|
if err := pdfCompositeKnockout(&pixels[i], initial[i], candidate[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pdfCompositeKnockout 移除未覆盖区域的初始背景贡献,保留形状与不透明度差异
|
|
// 入参: target 累计组结果, initial 初始背景, source 单个对象合成结果
|
|
// 返回: error 像素错误
|
|
func pdfCompositeKnockout(target *pdfCompositePixel, initial, source pdfCompositePixel) error {
|
|
pixel := pdfgo.ColorantPixel{Values: target.values[:], Alpha: target.alpha, Shape: target.shape, Effect: target.effect}
|
|
if err := pixel.Knockout(
|
|
pdfgo.ColorantPixel{Values: initial.values[:], Alpha: initial.alpha, Shape: initial.shape, Effect: initial.effect},
|
|
pdfgo.ColorantPixel{Values: source.values[:], Alpha: source.alpha, Shape: source.shape, Effect: source.effect},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
target.alpha, target.effect, target.shape = pixel.Alpha, pixel.Effect, pixel.Shape
|
|
return nil
|
|
}
|
|
|
|
// pdfCompositeGroup 移除组初始背景贡献后,将组结果合成到父空间
|
|
// 入参: ctx 取消上下文, pixels 父组输出, initial 初始背景, result 组结果, space 组空间, parent 父空间, opacity 组不透明度, mask 蒙版, mode 混合模式, intent 渲染意图, conversion 设备转换函数
|
|
// 返回: error 颜色或混合错误
|
|
func pdfCompositeGroup(ctx context.Context, pixels, initial, result []pdfCompositePixel, space, parent *pdfgo.ColorSpace, opacity float64, mask []float64, mode, intent pdfgo.Name, conversion pdfgo.ColorConversion) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if len(pixels) != len(initial) || len(pixels) != len(result) || mask != nil && len(mask) != len(pixels) {
|
|
return fmt.Errorf("invalid PDF transparency group buffers")
|
|
}
|
|
group, destination := pdfgo.ColorantGroup{Space: space}, pdfgo.ColorantGroup{Space: parent}
|
|
compositor, err := destination.PrepareGroup(&group, mode, intent, conversion)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
components, parentComponents := space.Components(), parent.Components()
|
|
for i, pixel := range result {
|
|
if i%pdfCompositeTileSize == 0 {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
alpha := opacity
|
|
if mask != nil {
|
|
alpha *= mask[i]
|
|
}
|
|
target := pdfgo.ColorantPixel{Values: pixels[i].values[:parentComponents], Alpha: pixels[i].alpha, Shape: pixels[i].shape, Effect: pixels[i].effect}
|
|
if err := compositor.Composite(&target,
|
|
pdfgo.ColorantPixel{Values: initial[i].values[:components], Alpha: initial[i].alpha, Shape: initial[i].shape, Effect: initial[i].effect},
|
|
pdfgo.ColorantPixel{Values: pixel.values[:components], Alpha: pixel.alpha, Shape: pixel.shape, Effect: pixel.effect},
|
|
alpha,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
pixels[i].alpha, pixels[i].effect, pixels[i].shape = target.Alpha, target.Effect, target.Shape
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// mask 在组颜色空间计算蒙版,保留背景色、组隔离和亮度传递
|
|
// 入参: mask 蒙版或空值, inherited 未指定蒙版空间时继承的空间
|
|
// 返回: []float64 蒙版透明度或空值, error 蒙版错误
|
|
func (c *pdfCompositor) mask(mask *pdfgo.SoftMask, inherited *pdfgo.ColorSpace) ([]float64, error) {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
if mask == nil {
|
|
return nil, nil
|
|
}
|
|
if cached, ok := c.masks[mask]; ok {
|
|
return cached, nil
|
|
}
|
|
nodes, ok := c.cache.masks[mask]
|
|
var err error
|
|
if !ok {
|
|
nodes, err = c.importer.collectCompositeNodes(func(v pdfgo.Visitor) error { return mask.WalkContext(c.importer.ctx, v) })
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.cache.masks[mask] = nodes
|
|
}
|
|
space := mask.ColorSpace
|
|
if space == nil {
|
|
space = inherited
|
|
}
|
|
pixels := c.acquirePixels(c.width * c.height)
|
|
defer c.releasePixels(pixels)
|
|
clear(pixels)
|
|
if mask.Subtype == "Luminosity" {
|
|
for i := range pixels {
|
|
if i%pdfCompositeTileSize == 0 {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
copy(pixels[i].values[:], mask.Backdrop)
|
|
pixels[i].alpha = 1
|
|
}
|
|
}
|
|
local := *c
|
|
local.transfers = nil
|
|
if err := local.draw(nodes, pixels, space); err != nil {
|
|
return nil, err
|
|
}
|
|
result := c.scratch.masks.acquire(len(pixels))
|
|
complete := false
|
|
defer func() {
|
|
if !complete {
|
|
c.scratch.masks.release(result)
|
|
}
|
|
}()
|
|
for i, pixel := range pixels {
|
|
if i%pdfCompositeTileSize == 0 {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
v := pixel.alpha
|
|
if mask.Subtype == "Luminosity" {
|
|
v, err = space.Luminosity(pixel.values[:space.Components()], "RelativeColorimetric")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
result[i], err = mask.Transfer(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
complete = true
|
|
c.masks[mask] = result
|
|
return result, nil
|
|
}
|
|
|
|
// imageColor 采样映射后的原始图像分量,插值在源空间内进行
|
|
// 入参: mark 图像, point 页面坐标, space 混合空间
|
|
// 返回: [4]float64 源颜色, float64 图像透明度, error 解码或变换错误
|
|
func (c *pdfCompositor) imageColor(mark *pdfgo.ImageMark, point pdfgo.Point, space *pdfgo.ColorSpace) ([4]float64, float64, error) {
|
|
inverse, found := c.cache.imageMatrix[mark]
|
|
if !found {
|
|
var ok bool
|
|
inverse, ok = mark.Matrix.Inverse()
|
|
if !ok {
|
|
return [4]float64{}, 0, fmt.Errorf("invalid PDF image matrix")
|
|
}
|
|
if c.cache.imageMatrix == nil {
|
|
c.cache.imageMatrix = make(map[*pdfgo.ImageMark]pdfgo.Matrix)
|
|
}
|
|
c.cache.imageMatrix[mark] = inverse
|
|
}
|
|
point = inverse.Apply(point)
|
|
if point.X < 0 || point.X > 1 || point.Y < 0 || point.Y > 1 {
|
|
return [4]float64{}, 0, nil
|
|
}
|
|
source := c.cache.images[mark.Image]
|
|
if source == nil {
|
|
var err error
|
|
source, err = mark.Image.DecodeComponentsViewContext(c.importer.ctx)
|
|
if err != nil {
|
|
return [4]float64{}, 0, err
|
|
}
|
|
c.cache.images[mark.Image] = source
|
|
}
|
|
x, y := point.X*float64(source.Rect.Dx())-.5, (1-point.Y)*float64(source.Rect.Dy())-.5
|
|
_, nativeProcess := source.Process.CMYKMask()
|
|
process := mark.Style.FillOverprint && space.Model == "DeviceCMYK" && !space.Calibrated() && len(source.Colorants) > 0
|
|
var indices [4]int
|
|
if process && !nativeProcess {
|
|
for i, name := range source.Colorants {
|
|
index := -1
|
|
for j, known := range []pdfgo.Name{"Cyan", "Magenta", "Yellow", "Black"} {
|
|
if name == known {
|
|
index = j
|
|
}
|
|
}
|
|
if index < 0 {
|
|
process = false
|
|
break
|
|
}
|
|
indices[i] = index
|
|
}
|
|
}
|
|
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))
|
|
values, alpha := source.ValuesAt(x, y)
|
|
if process && !nativeProcess {
|
|
values = [4]float64{}
|
|
for j, tint := range source.TintsAt(x, y) {
|
|
values[indices[j]] = float64(tint) / 65535
|
|
}
|
|
}
|
|
return values, alpha
|
|
}
|
|
values, alpha := sample(int(math.Floor(x+.5)), int(math.Floor(y+.5)))
|
|
if mark.Image.Interpolate {
|
|
ix, iy := int(math.Floor(x)), int(math.Floor(y))
|
|
fx, fy := x-float64(ix), y-float64(iy)
|
|
values, alpha = [4]float64{}, 0
|
|
for j := 0; j < 2; j++ {
|
|
for i := 0; i < 2; i++ {
|
|
weight := (1 - fx) * (1 - fy)
|
|
if i == 1 {
|
|
weight = fx * (1 - fy)
|
|
}
|
|
if j == 1 {
|
|
weight = (1 - fx) * fy
|
|
if i == 1 {
|
|
weight = fx * fy
|
|
}
|
|
}
|
|
v, a := sample(ix+i, iy+j)
|
|
alpha += a * weight
|
|
for k := range values {
|
|
values[k] += v[k] * a * weight
|
|
}
|
|
}
|
|
}
|
|
if alpha != 0 {
|
|
for k := range values {
|
|
values[k] = math.Max(0, math.Min(1, values[k]/alpha))
|
|
}
|
|
}
|
|
}
|
|
if mark.Image.ImageMask {
|
|
if mark.Style.Fill.Tiling != nil {
|
|
return [4]float64{}, alpha * (1 - values[0]), nil
|
|
}
|
|
color, visible, err := pdfCompositeColor(mark.Style.Fill, mark.Matrix.Apply(point), space, mark.Style.RenderingIntent, mark.Style.ColorConversion)
|
|
if !visible {
|
|
return color, 0, err
|
|
}
|
|
return color, alpha * (1 - values[0]), err
|
|
}
|
|
if process || space.Equal(source.Space) {
|
|
return values, alpha, nil
|
|
}
|
|
values, err := space.ConvertWith(values[:source.Space.Components()], source.Space, mark.Style.RenderingIntent, mark.Style.ColorConversion)
|
|
return values, alpha, err
|
|
}
|
|
|
|
// pdfImageProcessColorants 识别可在四色设备上直接绘制的专色色料
|
|
// 入参: image PDF图像
|
|
// 返回: [4]bool 参与绘制的四色通道, bool 是否全部属于印刷原色, error 色空间错误
|
|
func pdfImageProcessColorants(image *pdfgo.Image) ([4]bool, bool, error) {
|
|
var mask [4]bool
|
|
process, err := image.ProcessColorants()
|
|
if err != nil {
|
|
return mask, false, err
|
|
}
|
|
if mask, native := process.CMYKMask(); native {
|
|
return mask, true, nil
|
|
}
|
|
names, err := image.Colorants()
|
|
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 将原始画刷变换到混合空间,渐变在源空间求值
|
|
// 入参: paint 画刷, point 页面坐标, space 混合空间, intent 渲染意图, conversion 设备转换函数
|
|
// 返回: [4]float64 混合分量, bool 是否着色, error 颜色错误
|
|
func pdfCompositeColor(paint pdfgo.Paint, point pdfgo.Point, space *pdfgo.ColorSpace, intent pdfgo.Name, conversion pdfgo.ColorConversion) ([4]float64, bool, error) {
|
|
if paint.Tiling != nil {
|
|
return [4]float64{}, false, &pdfgo.UnsupportedError{Feature: "local tiling pattern compositing"}
|
|
}
|
|
var values [4]float64
|
|
var source *pdfgo.ColorSpace
|
|
if paint.Function != nil {
|
|
g := paint.Function
|
|
if g.Bounds != nil {
|
|
inverse, ok := g.PatternMatrix.Inverse()
|
|
if !ok {
|
|
return values, false, fmt.Errorf("singular function shading bounds transform")
|
|
}
|
|
local := inverse.Apply(point)
|
|
if local.X < g.Bounds.XMin || local.X > g.Bounds.XMax || local.Y < g.Bounds.YMin || local.Y > g.Bounds.YMax {
|
|
return values, false, nil
|
|
}
|
|
}
|
|
inverse, ok := g.Matrix.Inverse()
|
|
if !ok {
|
|
return values, false, fmt.Errorf("singular function shading transform")
|
|
}
|
|
local := inverse.Apply(point)
|
|
source, intent = g.Space, g.Intent
|
|
if local.X < g.Domain.XMin || local.X > g.Domain.XMax || local.Y < g.Domain.YMin || local.Y > g.Domain.YMax {
|
|
if g.Background == nil {
|
|
return values, false, nil
|
|
}
|
|
values = *g.Background
|
|
} else {
|
|
var err error
|
|
values, err = g.ValuesAt(local)
|
|
if err != nil {
|
|
return values, false, err
|
|
}
|
|
}
|
|
} else if paint.Axial != nil || paint.Radial != nil {
|
|
var background *[4]float64
|
|
var bounds *pdfgo.Rectangle
|
|
var matrix pdfgo.Matrix
|
|
if g := paint.Axial; g != nil {
|
|
source, intent = g.Space, g.Intent
|
|
background, bounds, matrix = g.Background, g.Bounds, g.Matrix
|
|
} else {
|
|
g := paint.Radial
|
|
source, intent = g.Space, g.Intent
|
|
background, bounds, matrix = g.Background, g.Bounds, g.Matrix
|
|
}
|
|
if bounds != nil {
|
|
local := point
|
|
if matrix != (pdfgo.Matrix{}) {
|
|
inverse, ok := matrix.Inverse()
|
|
if !ok {
|
|
return values, false, fmt.Errorf("singular shading transform")
|
|
}
|
|
local = inverse.Apply(point)
|
|
}
|
|
if local.X < bounds.XMin || local.X > bounds.XMax || local.Y < bounds.YMin || local.Y > bounds.YMax {
|
|
return values, false, nil
|
|
}
|
|
}
|
|
t, visible := pdfShadingPosition(paint, point)
|
|
var err error
|
|
if !visible {
|
|
if background == nil {
|
|
return values, false, nil
|
|
}
|
|
values = *background
|
|
} else if paint.Axial != nil {
|
|
values, err = paint.Axial.ValuesAt(t)
|
|
} else {
|
|
values, err = paint.Radial.ValuesAt(t)
|
|
}
|
|
if err != nil {
|
|
return values, false, err
|
|
}
|
|
} else if paint.Space != nil {
|
|
values, source = paint.Values, paint.Space
|
|
} else if paint.CMYK != nil {
|
|
values, source = *paint.CMYK, &pdfgo.ColorSpace{Model: "DeviceCMYK"}
|
|
} else if paint.SourceSpace == "DeviceGray" {
|
|
values[0], source = paint.RGB[0], &pdfgo.ColorSpace{Model: "DeviceGray"}
|
|
} else {
|
|
copy(values[:], paint.RGB[:])
|
|
source = &pdfgo.ColorSpace{Model: "DeviceRGB"}
|
|
}
|
|
values, err := space.ConvertWith(values[:source.Components()], source, intent, conversion)
|
|
return values, true, err
|
|
}
|
|
|
|
// color 复用精确参数对应的颜色,区域、背景及其他画刷保持原求值路径
|
|
// 入参: point 页面坐标
|
|
// 返回: [4]float64 混合分量, bool 是否着色, error 颜色错误
|
|
func (s *pdfCompositeColorSampler) color(point pdfgo.Point) ([4]float64, bool, error) {
|
|
g := s.paint.Axial
|
|
if g == nil || g.Bounds != nil || g.Background != nil || s.paint.Function != nil || s.paint.Tiling != nil {
|
|
return pdfCompositeColor(s.paint, point, s.space, s.intent, s.conversion)
|
|
}
|
|
position, visible := pdfShadingPosition(s.paint, point)
|
|
if !visible {
|
|
return [4]float64{}, false, nil
|
|
}
|
|
if values, ok := s.values[position]; ok {
|
|
return values, true, nil
|
|
}
|
|
values, visible, err := pdfCompositeColor(s.paint, point, s.space, s.intent, s.conversion)
|
|
if err == nil && visible && len(s.values) < 2*pdfCompositeTileSize {
|
|
if s.values == nil {
|
|
s.values = make(map[float64][4]float64)
|
|
}
|
|
s.values[position] = values
|
|
}
|
|
return values, visible, err
|
|
}
|
|
|
|
// pdfCompositePaintOver 以兼容叠印计算零色料分量,再应用对象混合模式
|
|
// 入参: backdrop 背景及输出, source 源分量, alpha 源透明度, space 混合空间, mode 混合模式, compatible 是否保留零色料背景, marked 原生过程通道,nil使用普通空间
|
|
// 返回: error 未支持的混合模式
|
|
func pdfCompositePaintOver(backdrop *pdfCompositePixel, source [4]float64, alpha float64, space *pdfgo.ColorSpace, mode pdfgo.Name, compatible bool, marked *[4]bool) error {
|
|
if !compatible && marked == nil {
|
|
return pdfCompositeOver(backdrop, source, alpha, space, mode)
|
|
}
|
|
if alpha == 0 {
|
|
return nil
|
|
}
|
|
mask := [4]bool{true, true, true, true}
|
|
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()])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
copy(backdrop.values[:space.Components()], values[:space.Components()])
|
|
backdrop.alpha = combined
|
|
backdrop.effect = alpha + backdrop.effect*(1-alpha)
|
|
return nil
|
|
}
|
|
|
|
// pdfCompositeOver 按PDF透明模型合成源颜色,四色混合使用分量补色
|
|
// 入参: backdrop 背景及输出, source 源分量, alpha 源透明度, space 混合空间, mode 混合模式
|
|
// 返回: error 未支持的混合模式
|
|
func pdfCompositeOver(backdrop *pdfCompositePixel, source [4]float64, alpha float64, space *pdfgo.ColorSpace, mode pdfgo.Name) error {
|
|
if alpha == 0 {
|
|
return nil
|
|
}
|
|
values, combined, err := space.Composite(backdrop.values[:space.Components()], source[:space.Components()], backdrop.alpha, alpha, mode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
copy(backdrop.values[:space.Components()], values[:space.Components()])
|
|
backdrop.alpha = combined
|
|
backdrop.effect = alpha + backdrop.effect*(1-alpha)
|
|
return nil
|
|
}
|