mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 14:41:27 +08:00
1 parent
3f797e656b
commit
91e5b57e24
43 files changed
+2018
-384
No files matched your search
@@ -0,0 +1,207 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
// readTIFFColorProfile 读取主图的ICC配置,校验颜色模型和数据范围
|
||||
// 入参: ctx 取消上下文,可为nil, data TIFF编码, layout 主图布局
|
||||
// 返回: *colorProfile 颜色配置,未声明时为nil, error 配置或取消错误
|
||||
func readTIFFColorProfile(ctx context.Context, data []byte, layout tiffImageLayout) (*colorProfile, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if layout.profileSize == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
at, size := uint64(layout.profileOffset), uint64(layout.profileSize)
|
||||
if at+size > uint64(len(data)) {
|
||||
return nil, fmt.Errorf("truncated TIFF color profile")
|
||||
}
|
||||
kind := ""
|
||||
switch layout.photometric {
|
||||
case 0, 1:
|
||||
kind = "GRAY"
|
||||
case 2, 3:
|
||||
kind = "RGB"
|
||||
case 5:
|
||||
kind = "CMYK"
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported TIFF profile color model")
|
||||
}
|
||||
profile, err := parseColorProfile(kind, data[at:at+size])
|
||||
if cancelErr := ctx.Err(); cancelErr != nil {
|
||||
return nil, cancelErr
|
||||
}
|
||||
return profile, err
|
||||
}
|
||||
|
||||
// applyTIFFColorProfile 将源颜色转换为十六位sRGB,保留独立透明度和隐藏颜色
|
||||
// 入参: ctx 取消上下文,可为nil, source 解码图像, profile 已校验的颜色配置
|
||||
// 返回: image.Image 独立透明度图像, error 颜色变换或取消错误
|
||||
func applyTIFFColorProfile(ctx context.Context, source image.Image, profile *colorProfile) (image.Image, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bounds := source.Bounds()
|
||||
if bounds.Dx() <= 0 || bounds.Dy() <= 0 || bounds.Dx() > math.MaxInt/8/bounds.Dy() {
|
||||
return nil, fmt.Errorf("TIFF profile image dimensions exceed capacity")
|
||||
}
|
||||
cmyk, _ := source.(*planarTIFFImage)
|
||||
if cmyk != nil && !cmyk.cmyk {
|
||||
cmyk = nil
|
||||
}
|
||||
model := source.ColorModel()
|
||||
premultiplied := model == color.RGBAModel || model == color.RGBA64Model
|
||||
table := newTIFFProfileTable(source)
|
||||
result := image.NewNRGBA64(bounds)
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
if (x-bounds.Min.X)&255 == 0 {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
slot := -1
|
||||
if len(table) != 0 {
|
||||
index, alpha := tiffProfileSample(source, x, y, len(table))
|
||||
slot = index
|
||||
if pixel := table[index]; pixel.A != 0 {
|
||||
pixel.A = alpha
|
||||
result.SetNRGBA64(x, y, pixel)
|
||||
continue
|
||||
}
|
||||
}
|
||||
var values [4]float64
|
||||
var alpha uint16
|
||||
if cmyk != nil {
|
||||
ink := cmyk.inksAt(x, y)
|
||||
alpha = ink[4]
|
||||
if cmyk.alpha == 1 && alpha == 0 {
|
||||
continue
|
||||
}
|
||||
maximum := float64(65535)
|
||||
if cmyk.alpha == 1 {
|
||||
maximum = float64(alpha)
|
||||
}
|
||||
for i := range values {
|
||||
values[i] = min(1, float64(ink[i])/maximum)
|
||||
}
|
||||
} else if premultiplied {
|
||||
var pixel color.RGBA64
|
||||
if native, ok := source.(image.RGBA64Image); ok {
|
||||
pixel = native.RGBA64At(x, y)
|
||||
} else {
|
||||
pixel = color.RGBA64Model.Convert(source.At(x, y)).(color.RGBA64)
|
||||
}
|
||||
alpha = pixel.A
|
||||
if alpha == 0 {
|
||||
continue
|
||||
}
|
||||
for i, value := range [3]uint16{pixel.R, pixel.G, pixel.B} {
|
||||
values[i] = min(1, float64(value)/float64(alpha))
|
||||
}
|
||||
} else {
|
||||
pixel := imageNRGBA64At(source, x, y)
|
||||
alpha = pixel.A
|
||||
values = [4]float64{float64(pixel.R) / 65535, float64(pixel.G) / 65535, float64(pixel.B) / 65535}
|
||||
}
|
||||
rgb, err := profile.rgb(values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var channels [3]uint16
|
||||
for i, value := range rgb {
|
||||
channels[i] = uint16(math.Round(max(0, min(1, value)) * 65535))
|
||||
}
|
||||
if slot >= 0 {
|
||||
table[slot] = color.NRGBA64{R: channels[0], G: channels[1], B: channels[2], A: 65535}
|
||||
}
|
||||
result.SetNRGBA64(x, y, color.NRGBA64{R: channels[0], G: channels[1], B: channels[2], A: alpha})
|
||||
}
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// newTIFFProfileTable 为离散颜色分配查找表,预乘颜色及小图保持逐像素变换
|
||||
// 入参: source 已解码且尺寸有效的图像
|
||||
// 返回: []color.NRGBA64 颜色表,Alpha非零表示已有结果,不适用时为nil
|
||||
func newTIFFProfileTable(source image.Image) []color.NRGBA64 {
|
||||
count := 0
|
||||
switch source := source.(type) {
|
||||
case *image.Gray:
|
||||
count = 256
|
||||
case *image.Gray16:
|
||||
count = 65536
|
||||
case *image.Paletted:
|
||||
count = len(source.Palette)
|
||||
case *planarTIFFImage:
|
||||
if source.alpha != 1 {
|
||||
if len(source.palette) != 0 {
|
||||
count = len(source.palette)
|
||||
} else if source.gray {
|
||||
count = 256
|
||||
if source.packed[0] != 0 {
|
||||
count = 1 << source.packed[0]
|
||||
} else if _, ok := source.planes[0].(*image.Gray16); ok {
|
||||
count = 65536
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if count == 0 || count >= source.Bounds().Dx()*source.Bounds().Dy() {
|
||||
return nil
|
||||
}
|
||||
return make([]color.NRGBA64, count)
|
||||
}
|
||||
|
||||
// tiffProfileSample 读取查找表索引与独立透明度,颜色键不包含透明度
|
||||
// 入参: source 适用查找表的图像, x 横坐标, y 纵坐标, count 颜色表长度
|
||||
// 返回: int 颜色索引, uint16 透明度
|
||||
func tiffProfileSample(source image.Image, x, y, count int) (int, uint16) {
|
||||
switch source := source.(type) {
|
||||
case *image.Gray:
|
||||
return int(source.GrayAt(x, y).Y), 65535
|
||||
case *image.Gray16:
|
||||
return int(source.Gray16At(x, y).Y), 65535
|
||||
case *image.Paletted:
|
||||
index := source.ColorIndexAt(x, y)
|
||||
_, _, _, alpha := source.Palette[index].RGBA()
|
||||
return int(index), uint16(alpha)
|
||||
case *planarTIFFImage:
|
||||
index := int(uint32(source.sampleAt(0, x, y)) * uint32(count-1) / 65535)
|
||||
alpha := uint16(65535)
|
||||
if source.alpha != 0 {
|
||||
alpha = source.sampleAt(1, x, y)
|
||||
}
|
||||
return index, alpha
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
Reference in new issue
Block a user