mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
226 lines
6.9 KiB
Go
226 lines
6.9 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 (
|
|
"image"
|
|
"image/color"
|
|
"math"
|
|
"slices"
|
|
|
|
"golang.org/x/image/draw"
|
|
)
|
|
|
|
// rasterAxisSample 保存目标像素及其归一化源采样权重
|
|
type rasterAxisSample struct {
|
|
pixel int
|
|
first int
|
|
weights []float64
|
|
}
|
|
|
|
// rasterImagePixel 保存未取整或裁切的预乘颜色累积值
|
|
type rasterImagePixel struct {
|
|
r, g, b, a float64
|
|
}
|
|
|
|
// rasterImageRow 保存正在累积的目标行
|
|
type rasterImageRow struct {
|
|
sample rasterAxisSample
|
|
values []rasterImagePixel
|
|
}
|
|
|
|
// rasterImageSource 为通用图片提供预乘十六位采样
|
|
type rasterImageSource struct{ image.Image }
|
|
|
|
// RGBA64At 读取通用颜色的原始预乘分量
|
|
// 入参: x 横向坐标, y 纵向坐标
|
|
// 返回: color.RGBA64 预乘颜色
|
|
func (s rasterImageSource) RGBA64At(x, y int) color.RGBA64 {
|
|
r, g, b, a := s.At(x, y).RGBA()
|
|
return color.RGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: uint16(a)}
|
|
}
|
|
|
|
// drawRasterAxisImage 按像素中心进行轴向缩小,逐行复用横向采样并保留镜像与小数坐标
|
|
// 入参: dst 目标图像, src 源图像, m 像素变换, op 合成操作
|
|
// 返回: bool 是否使用轴向采样
|
|
func drawRasterAxisImage(dst draw.Image, src image.Image, m RasterMatrix, op draw.Op) bool {
|
|
sx, sy := math.Abs(m[0]), math.Abs(m[3])
|
|
if m[1] != 0 || m[2] != 0 || sx == 0 || sy == 0 || sx > 1 || sy > 1 || sx == 1 && sy == 1 {
|
|
return false
|
|
}
|
|
for _, v := range m {
|
|
if math.IsNaN(v) || math.IsInf(v, 0) {
|
|
return false
|
|
}
|
|
}
|
|
b := src.Bounds()
|
|
if b.Empty() {
|
|
return true
|
|
}
|
|
r := rasterImageBounds(b, m, dst.Bounds())
|
|
if r.Empty() {
|
|
return true
|
|
}
|
|
xs := rasterImageAxis(r.Min.X, r.Max.X, b.Min.X, b.Max.X, m[0], m[4])
|
|
ys := rasterImageAxis(r.Min.Y, r.Max.Y, b.Min.Y, b.Max.Y, m[3], m[5])
|
|
if len(xs) == 0 || len(ys) == 0 {
|
|
return true
|
|
}
|
|
if m[3] < 0 {
|
|
slices.Reverse(ys)
|
|
}
|
|
pixels, ok := src.(image.RGBA64Image)
|
|
if !ok {
|
|
pixels = rasterImageSource{src}
|
|
}
|
|
width := len(xs)
|
|
horizontal := make([]rasterImagePixel, width)
|
|
columns := make([]int, width)
|
|
output := image.NewRGBA64(image.Rect(0, 0, width, 1))
|
|
var active []rasterImageRow
|
|
var free [][]rasterImagePixel
|
|
next := 0
|
|
for y := ys[0].first; next < len(ys) || len(active) != 0; y++ {
|
|
if len(active) == 0 && y < ys[next].first {
|
|
y = ys[next].first
|
|
}
|
|
for next < len(ys) && ys[next].first == y {
|
|
var row []rasterImagePixel
|
|
if n := len(free); n != 0 {
|
|
row, free = free[n-1], free[:n-1]
|
|
clear(row)
|
|
} else {
|
|
row = make([]rasterImagePixel, width)
|
|
}
|
|
active = append(active, rasterImageRow{sample: ys[next], values: row})
|
|
next++
|
|
}
|
|
rasterImageHorizontal(horizontal, pixels, xs, y, columns)
|
|
n := 0
|
|
for _, row := range active {
|
|
weight := row.sample.weights[y-row.sample.first]
|
|
if weight != 0 {
|
|
for x, value := range horizontal {
|
|
p := &row.values[x]
|
|
p.r += value.r * weight
|
|
p.g += value.g * weight
|
|
p.b += value.b * weight
|
|
p.a += value.a * weight
|
|
}
|
|
}
|
|
if y+1 == row.sample.first+len(row.sample.weights) {
|
|
rasterImageWriteRow(dst, output, row.values, xs[0].pixel, row.sample.pixel, op)
|
|
free = append(free, row.values)
|
|
} else {
|
|
active[n] = row
|
|
n++
|
|
}
|
|
}
|
|
active = active[:n]
|
|
}
|
|
return true
|
|
}
|
|
|
|
// rasterImageAxis 按连续源坐标生成CatmullRom权重,不对图像几何边界取整
|
|
// 入参: first 目标起点, end 目标终点, source 源起点, limit 源终点, scale 缩放比例, offset 平移距离
|
|
// 返回: []rasterAxisSample 轴向采样表
|
|
func rasterImageAxis(first, end, source, limit int, scale, offset float64) []rasterAxisSample {
|
|
result := make([]rasterAxisSample, 0, end-first)
|
|
argument := math.Abs(scale)
|
|
support := draw.CatmullRom.Support / argument
|
|
for pixel := first; pixel < end; pixel++ {
|
|
center := (float64(pixel) + .5 - offset) / scale
|
|
if center < float64(source) || center >= float64(limit) {
|
|
continue
|
|
}
|
|
center -= .5
|
|
start := int(max(float64(source), math.Floor(center-support)))
|
|
stop := int(min(float64(limit), math.Ceil(center+support)))
|
|
weights := make([]float64, stop-start)
|
|
total := 0.0
|
|
for i := range weights {
|
|
distance := math.Abs((float64(start+i) - center) * argument)
|
|
if distance < draw.CatmullRom.Support {
|
|
weights[i] = draw.CatmullRom.At(distance)
|
|
total += weights[i]
|
|
}
|
|
}
|
|
for i := range weights {
|
|
weights[i] /= total
|
|
}
|
|
result = append(result, rasterAxisSample{pixel: pixel, first: start, weights: weights})
|
|
}
|
|
return result
|
|
}
|
|
|
|
// rasterImageHorizontal 按源列复用像素,保留各目标像素的权重累加顺序与中间精度
|
|
// 入参: row 横向缓冲, src 源像素, samples 采样表, y 源行坐标, columns 活动列缓冲
|
|
func rasterImageHorizontal(row []rasterImagePixel, src image.RGBA64Image, samples []rasterAxisSample, y int, columns []int) {
|
|
clear(row)
|
|
reversed := samples[0].first > samples[len(samples)-1].first
|
|
start, end := samples[0].first, samples[0].first+len(samples[0].weights)
|
|
for _, sample := range samples[1:] {
|
|
start = min(start, sample.first)
|
|
end = max(end, sample.first+len(sample.weights))
|
|
}
|
|
next, count := 0, 0
|
|
for x := start; x < end; x++ {
|
|
for next < len(samples) {
|
|
index := next
|
|
if reversed {
|
|
index = len(samples) - next - 1
|
|
}
|
|
if samples[index].first > x {
|
|
break
|
|
}
|
|
columns[count] = index
|
|
count++
|
|
next++
|
|
}
|
|
p := src.RGBA64At(x, y)
|
|
n := 0
|
|
for _, index := range columns[:count] {
|
|
sample := samples[index]
|
|
weight := sample.weights[x-sample.first]
|
|
if weight != 0 {
|
|
value := &row[index]
|
|
value.r += float64(p.R) * weight
|
|
value.g += float64(p.G) * weight
|
|
value.b += float64(p.B) * weight
|
|
value.a += float64(p.A) * weight
|
|
}
|
|
if x+1 < sample.first+len(sample.weights) {
|
|
columns[n] = index
|
|
n++
|
|
}
|
|
}
|
|
count = n
|
|
}
|
|
}
|
|
|
|
// rasterImageWriteRow 在两轴采样完成后取整并使用标准预乘合成
|
|
// 入参: dst 目标图像, output 行像素缓冲, values 累积颜色, x 横向起点, y 目标行坐标, op 合成操作
|
|
func rasterImageWriteRow(dst draw.Image, output *image.RGBA64, values []rasterImagePixel, x, y int, op draw.Op) {
|
|
for i, value := range values {
|
|
output.SetRGBA64(i, 0, color.RGBA64{
|
|
R: uint16(min(max(min(value.r, value.a)+.5, 0), 65535)),
|
|
G: uint16(min(max(min(value.g, value.a)+.5, 0), 65535)),
|
|
B: uint16(min(max(min(value.b, value.a)+.5, 0), 65535)),
|
|
A: uint16(min(max(value.a+.5, 0), 65535)),
|
|
})
|
|
}
|
|
draw.Draw(dst, image.Rect(x, y, x+len(values), y+1), output, image.Point{}, op)
|
|
}
|