mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:16:55 +08:00
208 lines
6.2 KiB
Go
208 lines
6.2 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 (
|
|
"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
|
|
}
|