feat(转换能力): 补全非分离混合渲染
build.yaml / build (push) Successful in 41s

This commit is contained in:
xiaoqidun committed 2026-09-28 13:09:12 +08:00
1 parent 197521edb8
commit e20167bc3a
3 files changed
+111 -14

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ require (
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
github.com/xiaoqidun/jbig2 v1.1.1
github.com/xiaoqidun/pdfgo v0.0.0-20260927160712-be47f27aa7ac
github.com/xiaoqidun/pdfgo v0.0.0-20260928042613-c4f5292e5344
golang.org/x/crypto v0.57.0
golang.org/x/image v0.46.0
golang.org/x/text v0.42.0
+2 -2
View File
@@ -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/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
github.com/xiaoqidun/pdfgo v0.0.0-20260927160712-be47f27aa7ac h1:RLwc4JpZm5PU9vBrMffyvdubmy7u3hknjsAId9gMvB4=
github.com/xiaoqidun/pdfgo v0.0.0-20260927160712-be47f27aa7ac/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
github.com/xiaoqidun/pdfgo v0.0.0-20260928042613-c4f5292e5344 h1:MxA6FBXKannnXdF7f+Zo/QCiiY1vHWodKxq/rwgDT1I=
github.com/xiaoqidun/pdfgo v0.0.0-20260928042613-c4f5292e5344/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
+108 -11
View File
@@ -570,17 +570,26 @@ func pdfCompositeOver(backdrop *pdfCompositePixel, source [4]float64, alpha floa
return nil
}
combined := alpha + backdrop.alpha*(1-alpha)
nonseparable := mode == "Hue" || mode == "Saturation" || mode == "Color" || mode == "Luminosity"
var blend [4]float64
if nonseparable {
blend = pdfNonseparableBlend(backdrop.values, source, space, mode)
}
for i := 0; i < space.Components(); i++ {
b, s := backdrop.values[i], source[i]
if space.Model == "DeviceCMYK" {
b, s = 1-b, 1-s
}
mixed, err := pdfSeparableBlend(b, s, mode)
if err != nil {
return err
}
if space.Model == "DeviceCMYK" {
mixed = 1 - mixed
mixed := blend[i]
if !nonseparable {
b, s := backdrop.values[i], source[i]
if space.Model == "DeviceCMYK" {
b, s = 1-b, 1-s
}
var err error
mixed, err = pdfSeparableBlend(b, s, mode)
if err != nil {
return err
}
if space.Model == "DeviceCMYK" {
mixed = 1 - mixed
}
}
backdrop.values[i] = math.Max(0, math.Min(1, ((1-alpha)*backdrop.alpha*backdrop.values[i]+alpha*((1-backdrop.alpha)*source[i]+backdrop.alpha*mixed))/combined))
}
@@ -589,6 +598,94 @@ func pdfCompositeOver(backdrop *pdfCompositePixel, source [4]float64, alpha floa
return nil
}
// pdfNonseparableBlend 按PDF非分离混合规则计算颜色,CMYK黑版独立取值
// 入参: b 背景颜色, s 源颜色, space 混合空间, mode 混合模式
// 返回: [4]float64 混合分量
func pdfNonseparableBlend(b, s [4]float64, space *pdfgo.ColorSpace, mode pdfgo.Name) [4]float64 {
if space.Model == "DeviceGray" {
if mode == "Luminosity" {
return s
}
return b
}
if space.Model == "DeviceCMYK" {
for i := 0; i < 3; i++ {
b[i], s[i] = 1-b[i], 1-s[i]
}
}
result := b
switch mode {
case "Hue":
result = pdfSetLuminosity(pdfSetSaturation(s, pdfSaturation(b)), pdfLuminosity(b))
case "Saturation":
result = pdfSetLuminosity(pdfSetSaturation(b, pdfSaturation(s)), pdfLuminosity(b))
case "Color":
result = pdfSetLuminosity(s, pdfLuminosity(b))
case "Luminosity":
result = pdfSetLuminosity(b, pdfLuminosity(s))
}
if space.Model == "DeviceCMYK" {
for i := 0; i < 3; i++ {
result[i] = 1 - result[i]
}
result[3] = b[3]
if mode == "Luminosity" {
result[3] = s[3]
}
}
return result
}
// pdfLuminosity 计算非分离混合使用的亮度
// 入参: c 加色分量
// 返回: float64 亮度
func pdfLuminosity(c [4]float64) float64 { return .3*c[0] + .59*c[1] + .11*c[2] }
// pdfSaturation 计算非分离混合使用的饱和度
// 入参: c 加色分量
// 返回: float64 饱和度
func pdfSaturation(c [4]float64) float64 {
return math.Max(c[0], math.Max(c[1], c[2])) - math.Min(c[0], math.Min(c[1], c[2]))
}
// pdfSetSaturation 保持分量顺序并调整饱和度
// 入参: c 加色分量, saturation 目标饱和度
// 返回: [4]float64 调整后分量
func pdfSetSaturation(c [4]float64, saturation float64) [4]float64 {
minimum := math.Min(c[0], math.Min(c[1], c[2]))
span := pdfSaturation(c)
for i := 0; i < 3; i++ {
if span == 0 {
c[i] = 0
} else {
c[i] = (c[i] - minimum) * saturation / span
}
}
return c
}
// pdfSetLuminosity 调整亮度并按PDF ClipColor规则压缩超界分量
// 入参: c 加色分量, luminosity 目标亮度
// 返回: [4]float64 调整后分量
func pdfSetLuminosity(c [4]float64, luminosity float64) [4]float64 {
delta := luminosity - pdfLuminosity(c)
for i := 0; i < 3; i++ {
c[i] += delta
}
minimum, maximum := math.Min(c[0], math.Min(c[1], c[2])), math.Max(c[0], math.Max(c[1], c[2]))
if minimum < 0 {
for i := 0; i < 3; i++ {
c[i] = luminosity + (c[i]-luminosity)*luminosity/(luminosity-minimum)
}
}
if maximum > 1 {
for i := 0; i < 3; i++ {
c[i] = luminosity + (c[i]-luminosity)*(1-luminosity)/(maximum-luminosity)
}
}
return c
}
// pdfSeparableBlend 计算标准可分离混合函数
// 入参: b 背景分量, s 源分量, mode 混合模式
// 返回: float64 混合分量, error 未支持的模式
@@ -644,6 +741,6 @@ func pdfSeparableBlend(b, s float64, mode pdfgo.Name) (float64, error) {
case "Exclusion":
return b + s - 2*b*s, nil
default:
return 0, &pdfgo.UnsupportedError{Feature: "nonseparable blend mode " + string(mode)}
return 0, &pdfgo.UnsupportedError{Feature: "blend mode " + string(mode)}
}
}