From e20167bc3a18648397bffa219dd8b8de4a72e16a Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 28 Sep 2026 13:09:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=BD=AC=E6=8D=A2=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E8=A1=A5=E5=85=A8=E9=9D=9E=E5=88=86=E7=A6=BB=E6=B7=B7=E5=90=88?= =?UTF-8?q?=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 4 +- ofdgo_pdf_composite_draw.go | 119 ++++++++++++++++++++++++++++++++---- 3 files changed, 111 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index e48a4ea..0963bb9 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 4e4d33d..ffed44a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/ofdgo_pdf_composite_draw.go b/ofdgo_pdf_composite_draw.go index 3a21be8..4161c6e 100644 --- a/ofdgo_pdf_composite_draw.go +++ b/ofdgo_pdf_composite_draw.go @@ -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)} } }