mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:31:12 +08:00
1 parent
8f9e194c0c
commit
bb3abdea1d
22 files changed
+322
-122
No files matched your search
@@ -8,9 +8,9 @@ require (
|
|||||||
github.com/go-text/typesetting v0.3.5
|
github.com/go-text/typesetting v0.3.5
|
||||||
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-20261010204837-88a7f6b56918
|
github.com/tdewolff/font v0.0.0-20261010204837-88a7f6b56918
|
||||||
github.com/xiaoqidun/j2kgo v1.0.1
|
github.com/xiaoqidun/j2kgo v1.0.2
|
||||||
github.com/xiaoqidun/jbig2 v1.1.7
|
github.com/xiaoqidun/jbig2 v1.1.8
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261015160218-fa5114475eb5
|
github.com/xiaoqidun/pdfgo v0.0.0-20261016003351-18348c283ac1
|
||||||
golang.org/x/crypto v0.58.0
|
golang.org/x/crypto v0.58.0
|
||||||
golang.org/x/image v0.47.0
|
golang.org/x/image v0.47.0
|
||||||
golang.org/x/text v0.43.0
|
golang.org/x/text v0.43.0
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ github.com/tdewolff/parse/v2 v2.8.16 h1:bLk5svUOQRkW/Y2SJ+DeENSIkZBcTIkq+Atyv5D8
|
|||||||
github.com/tdewolff/parse/v2 v2.8.16/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ=
|
github.com/tdewolff/parse/v2 v2.8.16/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ=
|
||||||
github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
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/j2kgo v1.0.1 h1:W0SytzjvgiOhXwq+JNRSVKLB847fjGOvGWTNKqmwvo0=
|
github.com/xiaoqidun/j2kgo v1.0.2 h1:OAjjEOfaBaQzBNcj9wx+eM68UI5pewR6SzNZa1R1DIw=
|
||||||
github.com/xiaoqidun/j2kgo v1.0.1/go.mod h1:DCwAfuDzCDYqUiAlZ7BDwigTjaXw7o9L1FtEma/D1aA=
|
github.com/xiaoqidun/j2kgo v1.0.2/go.mod h1:DCwAfuDzCDYqUiAlZ7BDwigTjaXw7o9L1FtEma/D1aA=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.7 h1:118d2lQn2ZsupmXazK+2p23Jp5xQ09btBmPUoYoa/+Y=
|
github.com/xiaoqidun/jbig2 v1.1.8 h1:dERPW7F8gs18a/DOD9gXHR3fP1Vl6KhCNsLnq5l3BIo=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.7/go.mod h1:9jromqVwMqlptUDEt6Yip8vqYA5I1YjhG7FgMl/Z2dA=
|
github.com/xiaoqidun/jbig2 v1.1.8/go.mod h1:9jromqVwMqlptUDEt6Yip8vqYA5I1YjhG7FgMl/Z2dA=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261015160218-fa5114475eb5 h1:eJuai2A2q9R8Mo/oKXiIpNc41uzolkKplPWXUmYiHcU=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261016003351-18348c283ac1 h1:PNucIHG+qid5/GCN3wUGEqfsyrykhoazBbtPaGysWHk=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261015160218-fa5114475eb5/go.mod h1:FUQ8+TfNoec6tZCdsckx+rrD25JACm7EzuZyFH37QHg=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261016003351-18348c283ac1/go.mod h1:AaabzsObZ8Z76AcaWbo6swRROrJsblyIGB7HbieeJA4=
|
||||||
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=
|
||||||
|
|||||||
+32
-20
@@ -1036,6 +1036,9 @@ func getCFFCharsetInfo(data []byte, numGlyphs int) ([]int, string, string, int,
|
|||||||
if topDictData == nil {
|
if topDictData == nil {
|
||||||
return nil, "", "", 0, false
|
return nil, "", "", 0, false
|
||||||
}
|
}
|
||||||
|
if _, _, _, err := scanCFFIndex(data, stringIndexOff); err != nil {
|
||||||
|
return nil, "", "", 0, false
|
||||||
|
}
|
||||||
td := parseCFFDict(topDictData)
|
td := parseCFFDict(topDictData)
|
||||||
registry, ordering := getCFFROS(data, stringIndexOff, td)
|
registry, ordering := getCFFROS(data, stringIndexOff, td)
|
||||||
actual, err := parseCFFAndCountGlyphs(data)
|
actual, err := parseCFFAndCountGlyphs(data)
|
||||||
@@ -1067,7 +1070,18 @@ func getCmapFromCFF(data []byte, numGlyphs int) map[rune]uint16 {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
mapping := make(map[rune]uint16)
|
return cffCharsetMapping(data, sids, registry, ordering, stringIndexOff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cffCharsetMapping 根据已解析的CFF字符集恢复Unicode映射
|
||||||
|
// 入参: data CFF数据, sids 字符标识, registry 注册机构, ordering 字符集, stringIndexOff 字符串索引偏移
|
||||||
|
// 返回: map[rune]uint16 恢复的映射表
|
||||||
|
func cffCharsetMapping(data []byte, sids []int, registry, ordering string, stringIndexOff int) map[rune]uint16 {
|
||||||
|
capacity := max(0, len(sids)-1)
|
||||||
|
if registry == "" || registry == "Adobe" && ordering == "GB1" {
|
||||||
|
capacity *= 2
|
||||||
|
}
|
||||||
|
mapping := make(map[rune]uint16, capacity)
|
||||||
if registry == "Adobe" && ordering == "GB1" {
|
if registry == "Adobe" && ordering == "GB1" {
|
||||||
for gid, cid := range sids {
|
for gid, cid := range sids {
|
||||||
if gid == 0 {
|
if gid == 0 {
|
||||||
@@ -1100,7 +1114,7 @@ func getCmapFromCFF(data []byte, numGlyphs int) map[rune]uint16 {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
idx := sid - 391
|
idx := sid - 391
|
||||||
name = readStringIndexItem(data, stringIndexOff, idx)
|
name = string(readStringIndexItem(data, stringIndexOff, idx))
|
||||||
}
|
}
|
||||||
r := rune(0)
|
r := rune(0)
|
||||||
if name != "" {
|
if name != "" {
|
||||||
@@ -1127,11 +1141,11 @@ func GetCFFCIDRuneMap(data []byte) map[uint16]rune {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sids, registry, _, _, ok := getCFFCharsetInfo(cffData, numGlyphs)
|
sids, registry, ordering, stringIndexOff, ok := getCFFCharsetInfo(cffData, numGlyphs)
|
||||||
if !ok || registry == "" {
|
if !ok || registry == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
mapping := getCmapFromCFF(cffData, numGlyphs)
|
mapping := cffCharsetMapping(cffData, sids, registry, ordering, stringIndexOff)
|
||||||
if len(mapping) == 0 {
|
if len(mapping) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1187,7 +1201,7 @@ func getCFFData(data []byte) []byte {
|
|||||||
}
|
}
|
||||||
offset := int(binary.BigEndian.Uint32(data[pos+8 : pos+12]))
|
offset := int(binary.BigEndian.Uint32(data[pos+8 : pos+12]))
|
||||||
length := int(binary.BigEndian.Uint32(data[pos+12 : pos+16]))
|
length := int(binary.BigEndian.Uint32(data[pos+12 : pos+16]))
|
||||||
if offset < 0 || length < 0 || offset+length > len(data) {
|
if offset < 0 || length < 0 || offset > len(data) || length > len(data)-offset {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return data[offset : offset+length]
|
return data[offset : offset+length]
|
||||||
@@ -1269,31 +1283,29 @@ func parseCFFCharset(data []byte, offset, count int) ([]int, error) {
|
|||||||
|
|
||||||
// readStringIndexItem 读取CFF字符串索引项
|
// readStringIndexItem 读取CFF字符串索引项
|
||||||
// 入参: data CFF数据, offset 索引偏移, idx 索引号
|
// 入参: data CFF数据, offset 索引偏移, idx 索引号
|
||||||
// 返回: string 读取的字符串
|
// 返回: []byte 共享原始数据的只读字符串,索引无效时为nil
|
||||||
func readStringIndexItem(data []byte, offset int, idx int) string {
|
func readStringIndexItem(data []byte, offset int, idx int) []byte {
|
||||||
if offset >= len(data) {
|
if offset < 0 || offset > len(data)-2 || idx < 0 {
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
count := int(binary.BigEndian.Uint16(data[offset:]))
|
count := int(binary.BigEndian.Uint16(data[offset:]))
|
||||||
|
if idx >= count || offset > len(data)-3 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
offSize := int(data[offset+2])
|
offSize := int(data[offset+2])
|
||||||
if idx >= count {
|
if offSize < 1 || offSize > 4 || count+1 > (len(data)-offset-3)/offSize {
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
offArrayStart := offset + 3
|
offArrayStart := offset + 3
|
||||||
p1 := offArrayStart + idx*offSize
|
p1 := offArrayStart + idx*offSize
|
||||||
p2 := p1 + offSize
|
p2 := p1 + offSize
|
||||||
if p2+offSize > len(data) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
loc1 := readCFFOffset(data, p1, offSize)
|
loc1 := readCFFOffset(data, p1, offSize)
|
||||||
loc2 := readCFFOffset(data, p2, offSize)
|
loc2 := readCFFOffset(data, p2, offSize)
|
||||||
dataStart := offArrayStart + (count+1)*offSize
|
dataStart := offArrayStart + (count+1)*offSize
|
||||||
start := dataStart + loc1 - 1
|
if loc1 < 1 || loc2 < loc1 || loc2-1 > len(data)-dataStart {
|
||||||
length := loc2 - loc1
|
return nil
|
||||||
if start < 0 || start+length > len(data) {
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
return string(data[start : start+length])
|
return data[dataStart+loc1-1 : dataStart+loc2-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCFFROS 读取CID字体ROS信息
|
// getCFFROS 读取CID字体ROS信息
|
||||||
@@ -1317,7 +1329,7 @@ func getCFFSIDString(data []byte, stringIndexOff int, sid int) string {
|
|||||||
return cffStandardStrings[sid]
|
return cffStandardStrings[sid]
|
||||||
}
|
}
|
||||||
if sid > 390 {
|
if sid > 390 {
|
||||||
return readStringIndexItem(data, stringIndexOff, sid-391)
|
return string(readStringIndexItem(data, stringIndexOff, sid-391))
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func centerArc(start Point, arc Segment) (geometryArc, error) {
|
|||||||
}
|
}
|
||||||
angle := math.Mod(arc.Rotation, 360) * math.Pi / 180
|
angle := math.Mod(arc.Rotation, 360) * math.Pi / 180
|
||||||
sine, cosine := math.Sincos(angle)
|
sine, cosine := math.Sincos(angle)
|
||||||
dx, dy := (start.X-arc.End.X)/2, (start.Y-arc.End.Y)/2
|
dx, dy := arcCoordinateMean(start.X, -arc.End.X), arcCoordinateMean(start.Y, -arc.End.Y)
|
||||||
x, y := cosine*dx+sine*dy, -sine*dx+cosine*dy
|
x, y := cosine*dx+sine*dy, -sine*dx+cosine*dy
|
||||||
scale := math.Hypot(x/rx, y/ry)
|
scale := math.Hypot(x/rx, y/ry)
|
||||||
if scale > 1 {
|
if scale > 1 {
|
||||||
@@ -139,13 +139,24 @@ func centerArc(start Point, arc Segment) (geometryArc, error) {
|
|||||||
if !arc.Sweep && delta > 0 {
|
if !arc.Sweep && delta > 0 {
|
||||||
delta -= 2 * math.Pi
|
delta -= 2 * math.Pi
|
||||||
}
|
}
|
||||||
center := Point{cosine*cx - sine*cy + (start.X+arc.End.X)/2, sine*cx + cosine*cy + (start.Y+arc.End.Y)/2}
|
center := Point{cosine*cx - sine*cy + arcCoordinateMean(start.X, arc.End.X), sine*cx + cosine*cy + arcCoordinateMean(start.Y, arc.End.Y)}
|
||||||
if !finite(center.X) || !finite(center.Y) || !finite(rx) || !finite(ry) || !finite(delta) || !finite(theta) || delta == 0 {
|
if !finite(center.X) || !finite(center.Y) || !finite(rx) || !finite(ry) || !finite(delta) || !finite(theta) || delta == 0 {
|
||||||
return geometryArc{}, fmt.Errorf("invalid arc extent")
|
return geometryArc{}, fmt.Errorf("invalid arc extent")
|
||||||
}
|
}
|
||||||
return geometryArc{center, Point{rx * cosine, rx * sine}, Point{-ry * sine, ry * cosine}, theta, delta}, nil
|
return geometryArc{center, Point{rx * cosine, rx * sine}, Point{-ry * sine, ry * cosine}, theta, delta}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arcCoordinateMean 计算有限坐标均值,仅在求和溢出时先减半,避免微小坐标提前下溢
|
||||||
|
// 入参: a 第一个坐标, b 第二个坐标
|
||||||
|
// 返回: float64 坐标均值
|
||||||
|
func arcCoordinateMean(a, b float64) float64 {
|
||||||
|
value := (a + b) / 2
|
||||||
|
if math.IsInf(value, 0) {
|
||||||
|
return a/2 + b/2
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
// point 计算椭圆参数对应的坐标
|
// point 计算椭圆参数对应的坐标
|
||||||
// 入参: angle 参数角
|
// 入参: angle 参数角
|
||||||
// 返回: Point 椭圆上的点
|
// 返回: Point 椭圆上的点
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ func (m Matrix) Invert() (Matrix, bool) {
|
|||||||
return n, true
|
return n, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// matrixProductDifference 计算两个乘积之差,检查溢出、下溢及相消造成的精度损失
|
// matrixProductDifference 补偿乘积相减的舍入残差,检查溢出、下溢及严重相消
|
||||||
// 入参: a 第一个乘数, b 第二个乘数, c 第三个乘数, d 第四个乘数
|
// 入参: a 第一个乘数, b 第二个乘数, c 第三个乘数, d 第四个乘数
|
||||||
// 返回: float64 乘积之差, bool 是否可直接用于求逆
|
// 返回: float64 乘积之差, bool 是否可直接用于求逆
|
||||||
func matrixProductDifference(a, b, c, d float64) (float64, bool) {
|
func matrixProductDifference(a, b, c, d float64) (float64, bool) {
|
||||||
@@ -134,6 +134,9 @@ func matrixProductDifference(a, b, c, d float64) (float64, bool) {
|
|||||||
if !finite(value) || x == 0 && a != 0 && b != 0 || y == 0 && c != 0 && d != 0 {
|
if !finite(value) || x == 0 && a != 0 && b != 0 || y == 0 && c != 0 && d != 0 {
|
||||||
return value, false
|
return value, false
|
||||||
}
|
}
|
||||||
|
if x != 0 && y != 0 {
|
||||||
|
value = math.FMA(a, b, -y) + math.FMA(-c, d, y)
|
||||||
|
}
|
||||||
return value, x == 0 && y == 0 || math.Abs(value) >= 0x1p-1022 && math.Abs(value) >= 0x1p-48*math.Max(math.Abs(x), math.Abs(y))
|
return value, x == 0 && y == 0 || math.Abs(value) >= 0x1p-1022 && math.Abs(value) >= 0x1p-48*math.Max(math.Abs(x), math.Abs(y))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +172,7 @@ func TranslationMatrix(x, y float64) Matrix {
|
|||||||
// YScale 获取Y轴缩放比例
|
// YScale 获取Y轴缩放比例
|
||||||
// 返回: float64 缩放比例
|
// 返回: float64 缩放比例
|
||||||
func (m Matrix) YScale() float64 {
|
func (m Matrix) YScale() float64 {
|
||||||
return math.Sqrt(m.c*m.c + m.d*m.d)
|
return math.Hypot(m.c, m.d)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal 比较毫米坐标,使用固定绝对误差,不受绘图库全局精度影响
|
// Equal 比较毫米坐标,使用固定绝对误差,不受绘图库全局精度影响
|
||||||
|
|||||||
@@ -33,10 +33,7 @@ func (p Path) Transform(matrix Matrix) (Path, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
matrixScale := math.Max(math.Max(math.Abs(matrix.a), math.Abs(matrix.b)), math.Max(math.Abs(matrix.c), math.Abs(matrix.d)))
|
matrixScale := math.Max(math.Max(math.Abs(matrix.a), math.Abs(matrix.b)), math.Max(math.Abs(matrix.c), math.Abs(matrix.d)))
|
||||||
determinant := 0.0
|
determinant, _ := matrixProductSum(matrix.a, matrix.d, -matrix.b, matrix.c)
|
||||||
if matrixScale != 0 {
|
|
||||||
determinant = (matrix.a/matrixScale)*(matrix.d/matrixScale) - (matrix.b/matrixScale)*(matrix.c/matrixScale)
|
|
||||||
}
|
|
||||||
transform := func(p Point) Point {
|
transform := func(p Point) Point {
|
||||||
x, y := matrix.Transform(p.X, p.Y)
|
x, y := matrix.Transform(p.X, p.Y)
|
||||||
return Point{x, y}
|
return Point{x, y}
|
||||||
|
|||||||
@@ -451,13 +451,11 @@ func GrayAt(img image.Image, x, y int) color.Gray {
|
|||||||
case *colorImage:
|
case *colorImage:
|
||||||
return GrayAt(src.source, x, y)
|
return GrayAt(src.source, x, y)
|
||||||
case *maskedTIFFImage:
|
case *maskedTIFFImage:
|
||||||
if src.NRGBA64At(x, y).A == 0 {
|
if src.alphaAt(x, y) == 0 {
|
||||||
return color.Gray{}
|
return color.Gray{}
|
||||||
}
|
}
|
||||||
bounds, size := src.source.Bounds(), src.bounds.Size()
|
point := src.samplePoint(src.source.Bounds(), x, y)
|
||||||
sx := bounds.Min.X + int((uint64(x-src.bounds.Min.X)*uint64(bounds.Dx())+uint64(bounds.Dx()/2))/uint64(size.X))
|
return GrayAt(src.source, point.X, point.Y)
|
||||||
sy := bounds.Min.Y + int((uint64(y-src.bounds.Min.Y)*uint64(bounds.Dy())+uint64(bounds.Dy()/2))/uint64(size.Y))
|
|
||||||
return GrayAt(src.source, sx, sy)
|
|
||||||
case *transparentEdgeImage:
|
case *transparentEdgeImage:
|
||||||
if x <= src.rect.Min.X || y <= src.rect.Min.Y || x >= src.rect.Max.X-1 || y >= src.rect.Max.Y-1 {
|
if x <= src.rect.Min.X || y <= src.rect.Min.Y || x >= src.rect.Max.X-1 || y >= src.rect.Max.Y-1 {
|
||||||
return color.Gray{}
|
return color.Gray{}
|
||||||
@@ -534,6 +532,39 @@ func NRGBAAt(img image.Image, x, y int) color.NRGBA {
|
|||||||
// 返回: uint32 十六位透明度
|
// 返回: uint32 十六位透明度
|
||||||
func AlphaAt(img image.Image, x, y int) uint32 {
|
func AlphaAt(img image.Image, x, y int) uint32 {
|
||||||
switch source := img.(type) {
|
switch source := img.(type) {
|
||||||
|
case *maskedTIFFImage:
|
||||||
|
return source.alphaAt(x, y)
|
||||||
|
case *image.YCbCr, *image.CMYK, *image.Gray, *image.Gray16:
|
||||||
|
return 65535
|
||||||
|
case *image.NYCbCrA:
|
||||||
|
if !image.Pt(x, y).In(source.Rect) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return uint32(source.A[source.AOffset(x, y)]) * 257
|
||||||
|
case *orientedTIFFImage:
|
||||||
|
point, ok := source.sourcePoint(x, y)
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return AlphaAt(source.source, point.X, point.Y)
|
||||||
|
case *planarTIFFImage:
|
||||||
|
if !image.Pt(x, y).In(source.bounds) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if source.alpha != 0 {
|
||||||
|
index := 3
|
||||||
|
if source.cmyk {
|
||||||
|
index = 4
|
||||||
|
} else if source.gray || len(source.palette) != 0 {
|
||||||
|
index = 1
|
||||||
|
}
|
||||||
|
return uint32(source.sampleAt(index, x, y))
|
||||||
|
}
|
||||||
|
if len(source.palette) != 0 {
|
||||||
|
index := uint32(source.sampleAt(0, x, y)) * uint32(len(source.palette)-1) / 65535
|
||||||
|
return uint32(source.palette[index].A)
|
||||||
|
}
|
||||||
|
return 65535
|
||||||
case *image.RGBA:
|
case *image.RGBA:
|
||||||
if !(image.Point{X: x, Y: y}).In(source.Rect) {
|
if !(image.Point{X: x, Y: y}).In(source.Rect) {
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -49,6 +49,36 @@ func (s *maskedTIFFImage) Bounds() image.Rectangle { return s.bounds }
|
|||||||
// 返回: color.Model 颜色模型
|
// 返回: color.Model 颜色模型
|
||||||
func (s *maskedTIFFImage) ColorModel() color.Model { return color.NRGBA64Model }
|
func (s *maskedTIFFImage) ColorModel() color.Model { return color.NRGBA64Model }
|
||||||
|
|
||||||
|
// samplePoint 将显示坐标映射到主图或蒙版的像素中心
|
||||||
|
// 入参: bounds 采样图像范围, x 横坐标, y 纵坐标
|
||||||
|
// 返回: image.Point 采样坐标
|
||||||
|
func (s *maskedTIFFImage) samplePoint(bounds image.Rectangle, x, y int) image.Point {
|
||||||
|
size := s.bounds.Size()
|
||||||
|
x, y = x-s.bounds.Min.X, y-s.bounds.Min.Y
|
||||||
|
if bounds.Size() == size {
|
||||||
|
return bounds.Min.Add(image.Pt(x, y))
|
||||||
|
}
|
||||||
|
return image.Pt(
|
||||||
|
bounds.Min.X+int((uint64(x)*uint64(bounds.Dx())+uint64(bounds.Dx()/2))/uint64(size.X)),
|
||||||
|
bounds.Min.Y+int((uint64(y)*uint64(bounds.Dy())+uint64(bounds.Dy()/2))/uint64(size.Y)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// alphaAt 读取蒙版与主图透明度,不解算主图颜色
|
||||||
|
// 入参: x 横坐标, y 纵坐标
|
||||||
|
// 返回: uint32 十六位透明度
|
||||||
|
func (s *maskedTIFFImage) alphaAt(x, y int) uint32 {
|
||||||
|
if !image.Pt(x, y).In(s.bounds) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
mask := s.samplePoint(s.mask.Bounds(), x, y)
|
||||||
|
if NRGBA64At(s.mask, mask.X, mask.Y).R == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
source := s.samplePoint(s.source.Bounds(), x, y)
|
||||||
|
return AlphaAt(s.source, source.X, source.Y)
|
||||||
|
}
|
||||||
|
|
||||||
// NRGBA64At 按像素中心读取主图及蒙版,零位隐藏原图并保留隐藏颜色
|
// NRGBA64At 按像素中心读取主图及蒙版,零位隐藏原图并保留隐藏颜色
|
||||||
// 入参: x 横坐标, y 纵坐标
|
// 入参: x 横坐标, y 纵坐标
|
||||||
// 返回: color.NRGBA64 非预乘十六位颜色
|
// 返回: color.NRGBA64 非预乘十六位颜色
|
||||||
@@ -56,16 +86,10 @@ func (s *maskedTIFFImage) NRGBA64At(x, y int) color.NRGBA64 {
|
|||||||
if !image.Pt(x, y).In(s.bounds) {
|
if !image.Pt(x, y).In(s.bounds) {
|
||||||
return color.NRGBA64{}
|
return color.NRGBA64{}
|
||||||
}
|
}
|
||||||
size := s.bounds.Size()
|
source := s.samplePoint(s.source.Bounds(), x, y)
|
||||||
x, y = x-s.bounds.Min.X, y-s.bounds.Min.Y
|
pixel := NRGBA64At(s.source, source.X, source.Y)
|
||||||
b := s.source.Bounds()
|
mask := s.samplePoint(s.mask.Bounds(), x, y)
|
||||||
sx := b.Min.X + int((uint64(x)*uint64(b.Dx())+uint64(b.Dx()/2))/uint64(size.X))
|
if NRGBA64At(s.mask, mask.X, mask.Y).R == 0 {
|
||||||
sy := b.Min.Y + int((uint64(y)*uint64(b.Dy())+uint64(b.Dy()/2))/uint64(size.Y))
|
|
||||||
pixel := NRGBA64At(s.source, sx, sy)
|
|
||||||
b = s.mask.Bounds()
|
|
||||||
mx := b.Min.X + int((uint64(x)*uint64(b.Dx())+uint64(b.Dx()/2))/uint64(size.X))
|
|
||||||
my := b.Min.Y + int((uint64(y)*uint64(b.Dy())+uint64(b.Dy()/2))/uint64(size.Y))
|
|
||||||
if NRGBA64At(s.mask, mx, my).R == 0 {
|
|
||||||
pixel.A = 0
|
pixel.A = 0
|
||||||
}
|
}
|
||||||
return pixel
|
return pixel
|
||||||
|
|||||||
@@ -130,12 +130,7 @@ func (e *Editor) outputSnapshot(ctx context.Context, options WriteOptions) (*Edi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
snapshot := *e
|
snapshot := *e
|
||||||
snapshot.OnWriteProgress = func(stage string, completed, total int) error {
|
snapshot.OnWriteProgress = editorProgress(e.OnWriteProgress).withContext(ctx)
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return editorProgress(e.OnWriteProgress).report(stage, completed, total)
|
|
||||||
}
|
|
||||||
snapshot.output = &outputOptimization{ctx: ctx, options: options.Compression}
|
snapshot.output = &outputOptimization{ctx: ctx, options: options.Compression}
|
||||||
if options.Compression.Mode == CompressionUnchanged {
|
if options.Compression.Mode == CompressionUnchanged {
|
||||||
return &snapshot, nil
|
return &snapshot, nil
|
||||||
|
|||||||
@@ -84,13 +84,16 @@ func (e *Editor) prepareOutputImages(keys []string, load func(string) ([]byte, e
|
|||||||
if err := e.output.ctx.Err(); err != nil {
|
if err := e.output.ctx.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := editorProgress(e.OnWriteProgress).report("compress", e.output.completed, 0); err != nil {
|
if err := editorProgress(e.OnWriteProgress).withContext(e.output.ctx).report("compress", e.output.completed, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data, err := load(key)
|
data, err := load(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := e.output.ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-3
@@ -353,17 +353,21 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ
|
|||||||
return report, err
|
return report, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAt 读取指定位置的数据并响应取消
|
// ReadAt 读取前后响应取消,保留读取字节数和源错误
|
||||||
// 入参: data 目标缓冲, offset 源偏移
|
// 入参: data 目标缓冲, offset 源偏移
|
||||||
// 返回: int 读取字节数, error 读取错误
|
// 返回: int 读取字节数, error 读取错误
|
||||||
func (r convertReader) ReadAt(data []byte, offset int64) (int, error) {
|
func (r convertReader) ReadAt(data []byte, offset int64) (int, error) {
|
||||||
if err := r.context.Err(); err != nil {
|
if err := r.context.Err(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return r.source.ReadAt(data, offset)
|
n, err := r.source.ReadAt(data, offset)
|
||||||
|
if err == nil {
|
||||||
|
err = r.context.Err()
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write 写出数据并响应取消及短写
|
// Write 写入前后响应取消,保留写入及短写错误
|
||||||
// 入参: data 待写数据
|
// 入参: data 待写数据
|
||||||
// 返回: int 写入字节数, error 写入错误
|
// 返回: int 写入字节数, error 写入错误
|
||||||
func (w convertWriter) Write(data []byte) (int, error) {
|
func (w convertWriter) Write(data []byte) (int, error) {
|
||||||
@@ -375,5 +379,26 @@ func (w convertWriter) Write(data []byte) (int, error) {
|
|||||||
if err == nil && n != len(data) {
|
if err == nil && n != len(data) {
|
||||||
err = io.ErrShortWrite
|
err = io.ErrShortWrite
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = w.context.Err()
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString 写入字符串并保留底层原生写入能力,前后响应取消
|
||||||
|
// 入参: data 待写文本
|
||||||
|
// 返回: int 写入字节数, error 写入、短写或取消错误
|
||||||
|
func (w convertWriter) WriteString(data string) (int, error) {
|
||||||
|
if err := w.context.Err(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n, err := io.WriteString(w.writer, data)
|
||||||
|
*w.count += int64(n)
|
||||||
|
if err == nil && n != len(data) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = w.context.Err()
|
||||||
|
}
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
+14
-22
@@ -151,10 +151,9 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
|||||||
if !finite(renderer.DPI) || renderer.DPI <= 0 {
|
if !finite(renderer.DPI) || renderer.DPI <= 0 {
|
||||||
return nil, PDFImportReport{}, fmt.Errorf("invalid PDF raster DPI")
|
return nil, PDFImportReport{}, fmt.Errorf("invalid PDF raster DPI")
|
||||||
}
|
}
|
||||||
if options.OnProgress != nil {
|
progress := editorProgress(options.OnProgress).withContext(ctx)
|
||||||
if err := options.OnProgress("open", 0, 0); err != nil {
|
if err := progress.report("open", 0, 0); err != nil {
|
||||||
return nil, PDFImportReport{}, err
|
return nil, PDFImportReport{}, err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
editor := NewEditor()
|
editor := NewEditor()
|
||||||
editor.SetRenderBackends(renderer.Backends())
|
editor.SetRenderBackends(renderer.Backends())
|
||||||
@@ -168,7 +167,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
|||||||
readWarning = func(diagnostic pdfgo.Diagnostic) { diagnostics = append(diagnostics, diagnostic) }
|
readWarning = func(diagnostic pdfgo.Diagnostic) { diagnostics = append(diagnostics, diagnostic) }
|
||||||
readOptions.Warning = func(diagnostic pdfgo.Diagnostic) { readWarning(diagnostic) }
|
readOptions.Warning = func(diagnostic pdfgo.Diagnostic) { readWarning(diagnostic) }
|
||||||
}
|
}
|
||||||
reader, err := pdfgo.NewReaderWithOptions(source, size, readOptions)
|
reader, err := pdfgo.NewReaderWithOptions(convertReader{context: ctx, source: source}, size, readOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, PDFImportReport{}, err
|
return nil, PDFImportReport{}, err
|
||||||
}
|
}
|
||||||
@@ -206,10 +205,8 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
|||||||
}
|
}
|
||||||
readWarning = importer.warning
|
readWarning = importer.warning
|
||||||
}
|
}
|
||||||
if options.OnProgress != nil {
|
if err := progress.report("pages", 0, 0); err != nil {
|
||||||
if err := options.OnProgress("pages", 0, 0); err != nil {
|
return nil, PDFImportReport{}, err
|
||||||
return nil, PDFImportReport{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
err = reader.WalkPages(ctx, func(index int, page *pdfgo.Page) error {
|
err = reader.WalkPages(ctx, func(index int, page *pdfgo.Page) error {
|
||||||
_, width, height := codecpdf.PageMatrix(page)
|
_, width, height := codecpdf.PageMatrix(page)
|
||||||
@@ -220,10 +217,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
|||||||
importer.pageOrder = append(importer.pageOrder, page)
|
importer.pageOrder = append(importer.pageOrder, page)
|
||||||
importer.pageIDs[page.Reference] = editor.pages[index].ID
|
importer.pageIDs[page.Reference] = editor.pages[index].ID
|
||||||
importer.pageIndexes[page.Reference] = index
|
importer.pageIndexes[page.Reference] = index
|
||||||
if options.OnProgress != nil {
|
return progress.report("pages", index+1, 0)
|
||||||
return options.OnProgress("pages", index+1, 0)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, PDFImportReport{}, err
|
return nil, PDFImportReport{}, err
|
||||||
@@ -241,10 +235,8 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
|||||||
}
|
}
|
||||||
importer.page = 0
|
importer.page = 0
|
||||||
}
|
}
|
||||||
if options.OnProgress != nil {
|
if err := progress.report("convert", 0, len(editor.pages)); err != nil {
|
||||||
if err := options.OnProgress("convert", 0, len(editor.pages)); err != nil {
|
return nil, PDFImportReport{}, err
|
||||||
return nil, PDFImportReport{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
err = reader.WalkPages(ctx, func(index int, page *pdfgo.Page) error {
|
err = reader.WalkPages(ctx, func(index int, page *pdfgo.Page) error {
|
||||||
matrix, width, height := codecpdf.PageMatrix(page)
|
matrix, width, height := codecpdf.PageMatrix(page)
|
||||||
@@ -284,13 +276,13 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
|
|||||||
return fmt.Errorf("import PDF page %d: %w", index+1, err)
|
return fmt.Errorf("import PDF page %d: %w", index+1, err)
|
||||||
}
|
}
|
||||||
importer.report.Pages++
|
importer.report.Pages++
|
||||||
if options.OnProgress != nil {
|
if err := progress.report("convert", index+1, len(editor.pages)); err != nil {
|
||||||
if err := options.OnProgress("convert", index+1, len(editor.pages)); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if options.Progress != nil {
|
if options.Progress != nil {
|
||||||
return options.Progress(index + 1)
|
if err := options.Progress(index + 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -231,6 +231,8 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
}
|
}
|
||||||
var uniformValues [4]float64
|
var uniformValues [4]float64
|
||||||
var uniformPoint pdfgo.Point
|
var uniformPoint pdfgo.Point
|
||||||
|
uniformChannels := pdfTransferChannels
|
||||||
|
selectChannels := (c.channels != nil || transfer != nil && c.transferModel == "DeviceCMYK") && (compatible || marked != nil || patternChannels != nil)
|
||||||
uniformReady := false
|
uniformReady := false
|
||||||
var sampler *pdfCompositeColorSampler
|
var sampler *pdfCompositeColorSampler
|
||||||
if g := paint.Axial; (node.image == nil || node.image.Image.ImageMask) && 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 {
|
||||||
@@ -259,8 +261,14 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
if err := c.importer.ctx.Err(); err != nil {
|
if err := c.importer.ctx.Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
row := pdfCoverageRow(coverage, y, region.Min.X, region.Max.X)
|
||||||
for x := region.Min.X; x < region.Max.X; x++ {
|
for x := region.Min.X; x < region.Max.X; x++ {
|
||||||
a := baseimage.AlphaAt(coverage, x, y)
|
var a uint32
|
||||||
|
if row != nil {
|
||||||
|
a = uint32(row[4*(x-region.Min.X)+3]) * 257
|
||||||
|
} else {
|
||||||
|
a = baseimage.AlphaAt(coverage, x, y)
|
||||||
|
}
|
||||||
if a == 0 {
|
if a == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -325,6 +333,14 @@ func (c *pdfCompositor) drawPaint(node pdfCompositeNode, outline bool, paint pdf
|
|||||||
} else {
|
} else {
|
||||||
uniformValues, _, err = codecpdf.CompositeColor(paint, pdfgo.Point{}, space, style.RenderingIntent, style.ColorConversion)
|
uniformValues, _, err = codecpdf.CompositeColor(paint, pdfgo.Point{}, space, style.RenderingIntent, style.ColorConversion)
|
||||||
}
|
}
|
||||||
|
if selectChannels {
|
||||||
|
uniformChannels = 0
|
||||||
|
for j, selected := range codecpdf.PaintChannels(uniformValues, compatible, marked) {
|
||||||
|
if selected {
|
||||||
|
uniformChannels |= 1 << j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
uniformReady = true
|
uniformReady = true
|
||||||
}
|
}
|
||||||
values = uniformValues
|
values = uniformValues
|
||||||
@@ -391,11 +407,15 @@ 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
|
bits := pdfTransferChannels
|
||||||
if (c.channels != nil || transfer != nil && c.transferModel == "DeviceCMYK") && (compatible || marked != nil || patternChannels != nil) {
|
if selectChannels {
|
||||||
bits = 0
|
if uniformReady {
|
||||||
for j, selected := range codecpdf.PaintChannels(values, compatible, marked) {
|
bits = uniformChannels
|
||||||
if selected {
|
} else {
|
||||||
bits |= 1 << j
|
bits = 0
|
||||||
|
for j, selected := range codecpdf.PaintChannels(values, compatible, marked) {
|
||||||
|
if selected {
|
||||||
|
bits |= 1 << j
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if patternChannels != nil {
|
if patternChannels != nil {
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ type pdfCompositeScratch struct {
|
|||||||
rasters [8]*image.RGBA
|
rasters [8]*image.RGBA
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pdfCoverageRow 读取完整范围内的原生覆盖行,其他类型沿用逐点采样
|
||||||
|
// 入参: coverage 覆盖图像, y 行坐标, left 起始列, right 结束列
|
||||||
|
// 返回: []byte RGBA像素或空值
|
||||||
|
func pdfCoverageRow(coverage image.Image, y, left, right int) []byte {
|
||||||
|
buffer, ok := coverage.(*image.RGBA)
|
||||||
|
if !ok || y < buffer.Rect.Min.Y || y >= buffer.Rect.Max.Y || left < buffer.Rect.Min.X || right > buffer.Rect.Max.X || left >= right {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
start := buffer.PixOffset(left, y)
|
||||||
|
return buffer.Pix[start : start+4*(right-left)]
|
||||||
|
}
|
||||||
|
|
||||||
// acquireCoverage 借用当前块的独占画布,保留活动画布供嵌套合成读取
|
// acquireCoverage 借用当前块的独占画布,保留活动画布供嵌套合成读取
|
||||||
// 返回: *image.RGBA 未清空画布,不支持缓冲绘制的后端返回nil, error 无效绘制区域
|
// 返回: *image.RGBA 未清空画布,不支持缓冲绘制的后端返回nil, error 无效绘制区域
|
||||||
func (c *pdfCompositor) acquireCoverage() (*image.RGBA, error) {
|
func (c *pdfCompositor) acquireCoverage() (*image.RGBA, error) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
basegeom "github.com/xiaoqidun/ofdgo/internal/base/geom"
|
basegeom "github.com/xiaoqidun/ofdgo/internal/base/geom"
|
||||||
|
baseimage "github.com/xiaoqidun/ofdgo/internal/base/image"
|
||||||
codecpdf "github.com/xiaoqidun/ofdgo/internal/codec/pdf"
|
codecpdf "github.com/xiaoqidun/ofdgo/internal/codec/pdf"
|
||||||
"github.com/xiaoqidun/pdfgo"
|
"github.com/xiaoqidun/pdfgo"
|
||||||
)
|
)
|
||||||
@@ -612,7 +613,7 @@ func (c *pdfCompositor) gradient(paint pdfgo.Paint, space *pdfgo.ColorSpace, nod
|
|||||||
for sx := range samples {
|
for sx := range samples {
|
||||||
weight := 1.0
|
weight := 1.0
|
||||||
if coverage != nil {
|
if coverage != nil {
|
||||||
_, _, _, a := coverage.At(x*samples+sx, y*samples+sy).RGBA()
|
a := baseimage.AlphaAt(coverage, x*samples+sx, y*samples+sy)
|
||||||
weight = float64(a) / 65535
|
weight = float64(a) / 65535
|
||||||
if weight == 0 {
|
if weight == 0 {
|
||||||
continue
|
continue
|
||||||
|
|||||||
+4
-5
@@ -130,15 +130,14 @@ func (m RasterMatrix) Apply(p RasterPoint) RasterPoint {
|
|||||||
return RasterPoint{m[0]*p.X + m[2]*p.Y + m[4], m[1]*p.X + m[3]*p.Y + m[5]}
|
return RasterPoint{m[0]*p.X + m[2]*p.Y + m[4], m[1]*p.X + m[3]*p.Y + m[5]}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inverse 求逆矩阵,退化或行列式非有限时返回错误
|
// Inverse 求有限逆矩阵,复用几何变换的溢出、下溢及相消处理
|
||||||
// 返回: RasterMatrix 逆矩阵, error 无效矩阵
|
// 返回: RasterMatrix 逆矩阵, error 无效矩阵
|
||||||
func (m RasterMatrix) Inverse() (RasterMatrix, error) {
|
func (m RasterMatrix) Inverse() (RasterMatrix, error) {
|
||||||
det := m[0]*m[3] - m[1]*m[2]
|
inverse, ok := MatrixFromValues([6]float64(m)).Invert()
|
||||||
if det == 0 || math.IsNaN(det) || math.IsInf(det, 0) {
|
if !ok {
|
||||||
return RasterMatrix{}, fmt.Errorf("invalid raster transform")
|
return RasterMatrix{}, fmt.Errorf("invalid raster transform")
|
||||||
}
|
}
|
||||||
return RasterMatrix{m[3] / det, -m[1] / det, -m[2] / det, m[0] / det,
|
return RasterMatrix(inverse.Values()), nil
|
||||||
(m[2]*m[5] - m[3]*m[4]) / det, (m[1]*m[4] - m[0]*m[5]) / det}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// At 采样局部坐标下的渐变,使用预乘颜色插值
|
// At 采样局部坐标下的渐变,使用预乘颜色插值
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
baseimage "github.com/xiaoqidun/ofdgo/internal/base/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rasterClipKey 区分页面尺寸、分辨率和裁剪路径
|
// rasterClipKey 区分页面尺寸、分辨率和裁剪路径
|
||||||
@@ -102,9 +104,10 @@ func rasterClipMask(backend RasterBackend, page *RasterPage, path []RasterSegmen
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
start := mask.PixOffset(bounds.Min.X, y)
|
||||||
_, _, _, alpha := pixels.At(x-bounds.Min.X, y-bounds.Min.Y).RGBA()
|
row := mask.Pix[start : start+bounds.Dx()]
|
||||||
mask.SetAlpha(x, y, color.Alpha{uint8(alpha >> 8)})
|
for x := range row {
|
||||||
|
row[x] = uint8(baseimage.AlphaAt(pixels, x, y-bounds.Min.Y) >> 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-3
@@ -172,6 +172,8 @@ func (r *Renderer) RenderToText(page *PageContent, writer io.Writer) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var written int64
|
||||||
|
writer = convertWriter{context: ctx, writer: writer, count: &written}
|
||||||
_, err = text.WriteTo(writer)
|
_, err = text.WriteTo(writer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -201,6 +203,8 @@ func (r *Renderer) RenderToMultiPageText(writer io.Writer, indices ...int) error
|
|||||||
if err := r.exportProgress(0, len(indices)); err != nil {
|
if err := r.exportProgress(0, len(indices)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var count int64
|
||||||
|
writer = convertWriter{context: ctx, writer: writer, count: &count}
|
||||||
written := false
|
written := false
|
||||||
for i, index := range indices {
|
for i, index := range indices {
|
||||||
text, err := r.PageTextByIndexContext(ctx, index)
|
text, err := r.PageTextByIndexContext(ctx, index)
|
||||||
@@ -235,11 +239,13 @@ func (r *Renderer) RenderToImage(page *PageContent) (image.Image, error) {
|
|||||||
|
|
||||||
// RenderToPNG 渲染为PNG,像素尺寸由DPI决定
|
// RenderToPNG 渲染为PNG,像素尺寸由DPI决定
|
||||||
// 入参: page 页面内容, writer 输出流
|
// 入参: page 页面内容, writer 输出流
|
||||||
// 返回: error 错误信息
|
// 返回: error 渲染、写入或取消错误
|
||||||
func (r *Renderer) RenderToPNG(page *PageContent, writer io.Writer) error {
|
func (r *Renderer) RenderToPNG(page *PageContent, writer io.Writer) error {
|
||||||
if err := r.Compression.Validate(); err != nil {
|
if err := r.Compression.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var written int64
|
||||||
|
writer = convertWriter{context: r.outputContext(), writer: writer, count: &written}
|
||||||
img, err := r.RenderToImage(page)
|
img, err := r.RenderToImage(page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -262,11 +268,13 @@ func (r *Renderer) RenderToPNG(page *PageContent, writer io.Writer) error {
|
|||||||
|
|
||||||
// RenderToJPEG 渲染为白底JPEG,像素尺寸由DPI决定
|
// RenderToJPEG 渲染为白底JPEG,像素尺寸由DPI决定
|
||||||
// 入参: page 页面内容, writer 输出流, options 编码选项,nil使用标准库默认值
|
// 入参: page 页面内容, writer 输出流, options 编码选项,nil使用标准库默认值
|
||||||
// 返回: error 错误信息
|
// 返回: error 渲染、写入或取消错误
|
||||||
func (r *Renderer) RenderToJPEG(page *PageContent, writer io.Writer, options *jpeg.Options) error {
|
func (r *Renderer) RenderToJPEG(page *PageContent, writer io.Writer, options *jpeg.Options) error {
|
||||||
if err := r.Compression.Validate(); err != nil {
|
if err := r.Compression.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var written int64
|
||||||
|
writer = convertWriter{context: r.outputContext(), writer: writer, count: &written}
|
||||||
img, err := r.RenderToImage(page)
|
img, err := r.RenderToImage(page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -320,7 +328,7 @@ func (r *Renderer) RenderToPDF(page *PageContent, writer io.Writer) error {
|
|||||||
|
|
||||||
// RenderToEPS 渲染为EPS
|
// RenderToEPS 渲染为EPS
|
||||||
// 入参: page 页面内容, writer 输出流
|
// 入参: page 页面内容, writer 输出流
|
||||||
// 返回: error 错误信息
|
// 返回: error 渲染、写入或取消错误
|
||||||
func (r *Renderer) RenderToEPS(page *PageContent, writer io.Writer) error {
|
func (r *Renderer) RenderToEPS(page *PageContent, writer io.Writer) error {
|
||||||
if err := r.Compression.Validate(); err != nil {
|
if err := r.Compression.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -328,6 +336,8 @@ func (r *Renderer) RenderToEPS(page *PageContent, writer io.Writer) error {
|
|||||||
if r.backends.EPS == nil {
|
if r.backends.EPS == nil {
|
||||||
return fmt.Errorf("eps: %w", ErrBackendUnavailable)
|
return fmt.Errorf("eps: %w", ErrBackendUnavailable)
|
||||||
}
|
}
|
||||||
|
var written int64
|
||||||
|
writer = convertWriter{context: r.outputContext(), writer: writer, count: &written}
|
||||||
return r.backends.EPS.RenderEPS(r, page, writer)
|
return r.backends.EPS.RenderEPS(r, page, writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -64,7 +64,7 @@ func (r *Renderer) RenderToSVGWithObjects(page *PageContent, writer io.Writer) (
|
|||||||
|
|
||||||
// renderSVG 将SVG输出及对象分组交给指定后端
|
// renderSVG 将SVG输出及对象分组交给指定后端
|
||||||
// 入参: page 页面内容, writer 输出流, mode 资源封装方式
|
// 入参: page 页面内容, writer 输出流, mode 资源封装方式
|
||||||
// 返回: SVGResources 字体和图片资源, error 输出错误
|
// 返回: SVGResources 字体和图片资源, error 渲染、写入或取消错误
|
||||||
func (r *Renderer) renderSVG(page *PageContent, writer io.Writer, mode SVGMode) (SVGResources, error) {
|
func (r *Renderer) renderSVG(page *PageContent, writer io.Writer, mode SVGMode) (SVGResources, error) {
|
||||||
if err := r.Compression.Validate(); err != nil {
|
if err := r.Compression.Validate(); err != nil {
|
||||||
return SVGResources{}, err
|
return SVGResources{}, err
|
||||||
@@ -72,5 +72,7 @@ func (r *Renderer) renderSVG(page *PageContent, writer io.Writer, mode SVGMode)
|
|||||||
if r.backends.SVG == nil {
|
if r.backends.SVG == nil {
|
||||||
return SVGResources{}, fmt.Errorf("svg: %w", ErrBackendUnavailable)
|
return SVGResources{}, fmt.Errorf("svg: %w", ErrBackendUnavailable)
|
||||||
}
|
}
|
||||||
|
var written int64
|
||||||
|
writer = convertWriter{context: r.outputContext(), writer: writer, count: &written}
|
||||||
return r.backends.SVG.RenderSVG(r, page, writer, mode)
|
return r.backends.SVG.RenderSVG(r, page, writer, mode)
|
||||||
}
|
}
|
||||||
+43
-16
@@ -99,6 +99,7 @@ func (b CanvasBackend) renderImage(page *RasterPage, img *image.RGBA) (image.Ima
|
|||||||
}
|
}
|
||||||
r := rasterizer.FromImage(img, canvas.DPMM(page.DPI/25.4), space)
|
r := rasterizer.FromImage(img, canvas.DPMM(page.DPI/25.4), space)
|
||||||
masks := renderCache[[32]byte, *image.Alpha]{limit: 16 << 20}
|
masks := renderCache[[32]byte, *image.Alpha]{limit: 16 << 20}
|
||||||
|
var pathData []float64
|
||||||
for i, cmd := range page.Commands {
|
for i, cmd := range page.Commands {
|
||||||
if cmd.Image != nil {
|
if cmd.Image != nil {
|
||||||
source, err := baseimage.PixelData(cmd.Image)
|
source, err := baseimage.PixelData(cmd.Image)
|
||||||
@@ -119,23 +120,11 @@ func (b CanvasBackend) renderImage(page *RasterPage, img *image.RGBA) (image.Ima
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p := &canvas.Path{}
|
p, err := rasterCanvasPath(cmd.Path, pathData)
|
||||||
for _, s := range cmd.Path {
|
if err != nil {
|
||||||
switch s.Verb {
|
return nil, fmt.Errorf("canvas command %d: %w", i, err)
|
||||||
case RasterMove:
|
|
||||||
p.MoveTo(s.End.X, s.End.Y)
|
|
||||||
case RasterLine:
|
|
||||||
p.LineTo(s.End.X, s.End.Y)
|
|
||||||
case RasterQuad:
|
|
||||||
p.QuadTo(s.Control1.X, s.Control1.Y, s.End.X, s.End.Y)
|
|
||||||
case RasterCubic:
|
|
||||||
p.CubeTo(s.Control1.X, s.Control1.Y, s.Control2.X, s.Control2.Y, s.End.X, s.End.Y)
|
|
||||||
case RasterClose:
|
|
||||||
p.Close()
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("canvas command %d: unsupported path verb %d", i, s.Verb)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
pathData = p.Data()[:0]
|
||||||
paint := canvas.Paint{Color: cmd.Paint.Color}
|
paint := canvas.Paint{Color: cmd.Paint.Color}
|
||||||
var sampled rasterGradientCanvas
|
var sampled rasterGradientCanvas
|
||||||
if g := cmd.Paint.Gradient; g != nil {
|
if g := cmd.Paint.Gradient; g != nil {
|
||||||
@@ -220,6 +209,44 @@ func (b CanvasBackend) renderImage(page *RasterPage, img *image.RGBA) (image.Ima
|
|||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rasterCanvasPath 预留路径容量,保留光栅指令的构建及闭合规则
|
||||||
|
// 入参: path 只读光栅路径, buffer 可复用的独占路径缓冲
|
||||||
|
// 返回: *canvas.Path 使用独占缓冲的绘制路径, error 不支持的指令
|
||||||
|
func rasterCanvasPath(path []RasterSegment, buffer []float64) (*canvas.Path, error) {
|
||||||
|
capacity := 0
|
||||||
|
for _, segment := range path {
|
||||||
|
switch segment.Verb {
|
||||||
|
case RasterQuad:
|
||||||
|
capacity += 6
|
||||||
|
case RasterCubic:
|
||||||
|
capacity += 8
|
||||||
|
default:
|
||||||
|
capacity += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cap(buffer) < capacity {
|
||||||
|
buffer = make([]float64, 0, capacity)
|
||||||
|
}
|
||||||
|
p := canvas.NewPathFromData(buffer[:0])
|
||||||
|
for _, s := range path {
|
||||||
|
switch s.Verb {
|
||||||
|
case RasterMove:
|
||||||
|
p.MoveTo(s.End.X, s.End.Y)
|
||||||
|
case RasterLine:
|
||||||
|
p.LineTo(s.End.X, s.End.Y)
|
||||||
|
case RasterQuad:
|
||||||
|
p.QuadTo(s.Control1.X, s.Control1.Y, s.End.X, s.End.Y)
|
||||||
|
case RasterCubic:
|
||||||
|
p.CubeTo(s.Control1.X, s.Control1.Y, s.Control2.X, s.Control2.Y, s.End.X, s.End.Y)
|
||||||
|
case RasterClose:
|
||||||
|
p.Close()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported path verb %d", s.Verb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
// At 返回原局部坐标的描边渐变颜色
|
// At 返回原局部坐标的描边渐变颜色
|
||||||
// 入参: x 页面横坐标, y 页面纵坐标
|
// 入参: x 页面横坐标, y 页面纵坐标
|
||||||
// 返回: color.RGBA 预乘颜色
|
// 返回: color.RGBA 预乘颜色
|
||||||
|
|||||||
@@ -63,21 +63,39 @@ func (r *epsImageRenderer) RenderImage(img image.Image, matrix canvas.Matrix) {
|
|||||||
var encoded bytes.Buffer
|
var encoded bytes.Buffer
|
||||||
compressor, _ := zlib.NewWriterLevel(&encoded, zlib.BestCompression)
|
compressor, _ := zlib.NewWriterLevel(&encoded, zlib.BestCompression)
|
||||||
opaque := true
|
opaque := true
|
||||||
|
native, _ := img.(image.RGBA64Image)
|
||||||
for y := 0; y < height; y++ {
|
for y := 0; y < height; y++ {
|
||||||
if r.err = r.ctx.Err(); r.err != nil {
|
if r.err = r.ctx.Err(); r.err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clear(pixels)
|
clear(pixels)
|
||||||
for x := 0; x < width; x++ {
|
for x := 0; x < width; x++ {
|
||||||
red, green, blue, alpha := img.At(bounds.Min.X+x, bounds.Min.Y+y).RGBA()
|
if x&4095 == 0 {
|
||||||
|
if r.err = r.ctx.Err(); r.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var red, green, blue, alpha uint32
|
||||||
|
if native != nil {
|
||||||
|
pixel := native.RGBA64At(bounds.Min.X+x, bounds.Min.Y+y)
|
||||||
|
red, green, blue, alpha = uint32(pixel.R), uint32(pixel.G), uint32(pixel.B), uint32(pixel.A)
|
||||||
|
} else {
|
||||||
|
red, green, blue, alpha = img.At(bounds.Min.X+x, bounds.Min.Y+y).RGBA()
|
||||||
|
}
|
||||||
if alpha != 65535 {
|
if alpha != 65535 {
|
||||||
opaque = false
|
opaque = false
|
||||||
}
|
}
|
||||||
if alpha != 0 {
|
if alpha == 65535 {
|
||||||
|
offset := x * 3
|
||||||
|
pixels[offset], pixels[offset+1], pixels[offset+2] = byte(red>>8), byte(green>>8), byte(blue>>8)
|
||||||
|
} else if alpha != 0 {
|
||||||
offset := x * 3
|
offset := x * 3
|
||||||
pixels[offset], pixels[offset+1], pixels[offset+2] = byte(red*65535/alpha>>8), byte(green*65535/alpha>>8), byte(blue*65535/alpha>>8)
|
pixels[offset], pixels[offset+1], pixels[offset+2] = byte(red*65535/alpha>>8), byte(green*65535/alpha>>8), byte(blue*65535/alpha>>8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.err = r.ctx.Err(); r.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if _, r.err = compressor.Write(pixels); r.err != nil {
|
if _, r.err = compressor.Write(pixels); r.err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -101,6 +119,9 @@ func (r *epsImageRenderer) RenderImage(img image.Image, matrix canvas.Matrix) {
|
|||||||
data, filter = optimized, "DCTDecode"
|
data, filter = optimized, "DCTDecode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.err = r.ctx.Err(); r.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
m := matrix.Scale(float64(width), float64(height))
|
m := matrix.Scale(float64(width), float64(height))
|
||||||
_, r.err = fmt.Fprintf(r.writer, " gsave /DeviceRGB setcolorspace [%s %s %s %s %s %s] concat <</ImageType 1 /BitsPerComponent 8 /Decode [0 1 0 1 0 1] /Interpolate true /Width %d /Height %d /ImageMatrix [%d 0 0 -%d 0 %d] /DataSource currentfile /ASCII85Decode filter /%s filter>>image\n", ofdNumber(m[0][0]), ofdNumber(m[1][0]), ofdNumber(m[0][1]), ofdNumber(m[1][1]), ofdNumber(m[0][2]), ofdNumber(m[1][2]), width, height, width, height, height, filter)
|
_, r.err = fmt.Fprintf(r.writer, " gsave /DeviceRGB setcolorspace [%s %s %s %s %s %s] concat <</ImageType 1 /BitsPerComponent 8 /Decode [0 1 0 1 0 1] /Interpolate true /Width %d /Height %d /ImageMatrix [%d 0 0 -%d 0 %d] /DataSource currentfile /ASCII85Decode filter /%s filter>>image\n", ofdNumber(m[0][0]), ofdNumber(m[1][0]), ofdNumber(m[0][1]), ofdNumber(m[1][1]), ofdNumber(m[0][2]), ofdNumber(m[1][2]), width, height, width, height, height, filter)
|
||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ func (r *Renderer) imageWithClip(img image.Image, clipPath *canvas.Path, m canva
|
|||||||
}
|
}
|
||||||
if baseimage.HighPrecision(img) || baseimage.HighPrecision(result) {
|
if baseimage.HighPrecision(img) || baseimage.HighPrecision(result) {
|
||||||
out := image.NewNRGBA64(bounds)
|
out := image.NewNRGBA64(bounds)
|
||||||
|
source64, _ := img.(*image.NRGBA64)
|
||||||
maskBounds := result.Bounds()
|
maskBounds := result.Bounds()
|
||||||
opaque := true
|
opaque := true
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||||
@@ -274,6 +275,11 @@ func (r *Renderer) imageWithClip(img image.Image, clipPath *canvas.Path, m canva
|
|||||||
r.renderError = err
|
r.renderError = err
|
||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
row := out.Pix[(y-bounds.Min.Y)*out.Stride:][:w*8]
|
||||||
|
if source64 != nil {
|
||||||
|
start := source64.PixOffset(bounds.Min.X, y)
|
||||||
|
copy(row, source64.Pix[start:start+len(row)])
|
||||||
|
}
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||||
if (x-bounds.Min.X)&255 == 0 {
|
if (x-bounds.Min.X)&255 == 0 {
|
||||||
if err := work.Err(); err != nil {
|
if err := work.Err(); err != nil {
|
||||||
@@ -281,9 +287,15 @@ func (r *Renderer) imageWithClip(img image.Image, clipPath *canvas.Path, m canva
|
|||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pixel := baseimage.NRGBA64At(img, x, y)
|
|
||||||
alpha := baseimage.AlphaAt(result, maskBounds.Min.X+x-bounds.Min.X, maskBounds.Min.Y+y-bounds.Min.Y)
|
alpha := baseimage.AlphaAt(result, maskBounds.Min.X+x-bounds.Min.X, maskBounds.Min.Y+y-bounds.Min.Y)
|
||||||
opaque = opaque && alpha == 65535
|
opaque = opaque && alpha == 65535
|
||||||
|
if source64 != nil {
|
||||||
|
i := (x-bounds.Min.X)*8 + 6
|
||||||
|
a := (uint32(row[i])<<8 | uint32(row[i+1])) * alpha / 65535
|
||||||
|
row[i], row[i+1] = byte(a>>8), byte(a)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pixel := baseimage.NRGBA64At(img, x, y)
|
||||||
pixel.A = uint16(uint32(pixel.A) * alpha / 65535)
|
pixel.A = uint16(uint32(pixel.A) * alpha / 65535)
|
||||||
out.SetNRGBA64(x, y, pixel)
|
out.SetNRGBA64(x, y, pixel)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user