Files
ofdgo/ofdgo_geom.go
T
2026-09-28 15:17:45 +08:00

262 lines
6.7 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 (
"fmt"
"math"
"slices"
"strconv"
"strings"
)
// IdentityMatrix 单位矩阵
var IdentityMatrix = Matrix{1, 0, 0, 1, 0, 0}
// Box 矩形区域
type Box struct {
X, Y, W, H float64
}
// Point 平面坐标点
type Point struct {
X, Y float64
}
// Matrix 2D仿射变换矩阵
type Matrix struct {
a, b, c, d, e, f float64
}
// ParseBox 解析Box字符串
// 入参: s 字符串
// 返回: Box 矩形对象, error 错误信息
func ParseBox(s string) (Box, error) {
var values [4]float64
i := 0
for part := range strings.FieldsSeq(s) {
if i == len(values) {
return Box{}, fmt.Errorf("invalid box: %s", s)
}
value, err := strconv.ParseFloat(part, 64)
if err != nil {
return Box{}, fmt.Errorf("invalid box: %s", s)
}
values[i] = value
i++
}
if i != len(values) {
return Box{}, fmt.Errorf("invalid box: %s", s)
}
return Box{X: values[0], Y: values[1], W: values[2], H: values[3]}, nil
}
// MatrixFromValues 从a、b、c、d、e、f构造矩阵,不经过文本格式化
// 入参: values 矩阵分量
// 返回: Matrix 变换矩阵
func MatrixFromValues(values [6]float64) Matrix {
return Matrix{a: values[0], b: values[1], c: values[2], d: values[3], e: values[4], f: values[5]}
}
// Values 返回a、b、c、d、e、f的独立副本,保留原始浮点精度
// 返回: [6]float64 矩阵分量
func (m Matrix) Values() [6]float64 {
return [6]float64{m.a, m.b, m.c, m.d, m.e, m.f}
}
// NewMatrix 解析CTM字符串
// 入参: s 字符串
// 返回: Matrix 矩阵对象
func NewMatrix(s string) Matrix {
floats := parseFloats(s)
if len(floats) != 6 {
return IdentityMatrix
}
return Matrix{
a: floats[0], b: floats[1],
c: floats[2], d: floats[3],
e: floats[4], f: floats[5],
}
}
// Multiply 矩阵乘法 (m * o)
// 入参: o 右侧矩阵
// 返回: Matrix 结果矩阵
func (m Matrix) Multiply(o Matrix) Matrix {
return Matrix{
a: m.a*o.a + m.c*o.b,
b: m.b*o.a + m.d*o.b,
c: m.a*o.c + m.c*o.d,
d: m.b*o.c + m.d*o.d,
e: m.a*o.e + m.c*o.f + m.e,
f: m.b*o.e + m.d*o.f + m.f,
}
}
// Transform 应用变换矩阵
// 入参: x X坐标, y Y坐标
// 返回: float64 变换后X, float64 变换后Y
func (m Matrix) Transform(x, y float64) (float64, float64) {
nx := m.a*x + m.c*y + m.e
ny := m.b*x + m.d*y + m.f
return nx, ny
}
// TransformBox 获取矩形经仿射变换后的轴对齐边界
// 入参: box 原始矩形范围
// 返回: Box 变换后的轴对齐边界
func (m Matrix) TransformBox(box Box) Box {
x, y := m.Transform(box.X, box.Y)
left, top, right, bottom := x, y, x, y
for _, point := range [][2]float64{{box.X + box.W, box.Y}, {box.X, box.Y + box.H}, {box.X + box.W, box.Y + box.H}} {
x, y := m.Transform(point[0], point[1])
left, top, right, bottom = math.Min(left, x), math.Min(top, y), math.Max(right, x), math.Max(bottom, y)
}
return Box{X: left, Y: top, W: right - left, H: bottom - top}
}
// String 返回标准CTM属性值
// 返回: string 六个空格分隔的矩阵分量
func (m Matrix) String() string {
return fmt.Sprintf("%s %s %s %s %s %s", ofdNumber(m.a), ofdNumber(m.b), ofdNumber(m.c), ofdNumber(m.d), ofdNumber(m.e), ofdNumber(m.f))
}
// Invert 求逆矩阵
// 返回: Matrix 逆矩阵, bool 是否可逆
func (m Matrix) Invert() (Matrix, bool) {
det := m.a*m.d - m.b*m.c
if math.Abs(det) < 1e-9 {
return IdentityMatrix, false
}
return Matrix{
a: m.d / det,
b: -m.b / det,
c: -m.c / det,
d: m.a / det,
e: (m.c*m.f - m.d*m.e) / det,
f: (m.b*m.e - m.a*m.f) / det,
}, true
}
// TranslationMatrix 构造平移矩阵
// 入参: x X轴偏移, y Y轴偏移
// 返回: Matrix 平移矩阵
func TranslationMatrix(x, y float64) Matrix {
return Matrix{a: 1, d: 1, e: x, f: y}
}
// YScale 获取Y轴缩放比例
// 返回: float64 缩放比例
func (m Matrix) YScale() float64 {
return math.Sqrt(m.c*m.c + m.d*m.d)
}
// geometryEqual 比较毫米坐标,使用固定绝对误差,不受绘图库全局精度影响
// 入参: a、b 待比较的坐标
// 返回: bool 是否在容差内相等
func geometryEqual(a, b float64) bool {
return math.Abs(a-b) <= 1e-10
}
// axisAlignedMatrix 判断变换是否保持坐标轴平行,含直角旋转与镜像
// 入参: m 仿射变换矩阵
// 返回: bool 是否为非退化的轴对齐变换
func axisAlignedMatrix(m Matrix) bool {
return m.b == 0 && m.c == 0 && m.a != 0 && m.d != 0 || m.a == 0 && m.d == 0 && m.b != 0 && m.c != 0
}
// matrixVector 变换位移,不包含平移分量
// 入参: m 仿射变换矩阵, x、y 原始位移
// 返回: float64 变换后的X位移, float64 变换后的Y位移
func matrixVector(m Matrix, x, y float64) (float64, float64) {
return m.a*x + m.c*y, m.b*x + m.d*y
}
// parseFloats 解析浮点数数组
// 入参: s 字符串
// 返回: []float64 浮点数数组
func parseFloats(s string) []float64 {
if s == "" {
return nil
}
if strings.Contains(s, "g") {
return parseFloatsWithG(s)
}
s = strings.ReplaceAll(s, ",", " ")
parts := strings.Fields(s)
result := make([]float64, 0, len(parts))
for _, p := range parts {
if v, err := strconv.ParseFloat(p, 64); err == nil {
result = append(result, v)
}
}
return result
}
// parseFloatsWithG 解析带g的压缩浮点数数组
// 入参: s 字符串
// 返回: []float64 浮点数数组
func parseFloatsWithG(s string) []float64 {
var result []float64
gFlag := false
gCount := 0
for p := range strings.FieldsSeq(s) {
if p == "g" {
gFlag = true
continue
}
if gFlag {
var err error
gCount, err = strconv.Atoi(p)
if err != nil {
gCount = 0
}
gFlag = false
continue
}
if gCount > 0 {
v, _ := strconv.ParseFloat(p, 64)
result = slices.Grow(result, gCount)
for j := 0; j < gCount; j++ {
result = append(result, v)
}
gCount = 0
} else {
if v, err := strconv.ParseFloat(p, 64); err == nil {
result = append(result, v)
}
}
}
return result
}
// parseInts 解析整数数组
// 入参: s 字符串
// 返回: []int 整数数组
func parseInts(s string) []int {
if s == "" {
return nil
}
s = strings.ReplaceAll(s, ",", " ")
parts := strings.Fields(s)
result := make([]int, 0, len(parts))
for _, p := range parts {
if v, err := strconv.Atoi(p); err == nil {
result = append(result, v)
}
}
return result
}