mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:25:49 +08:00
190 lines
6.2 KiB
Go
190 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 image
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"image"
|
|
"io"
|
|
"math/bits"
|
|
"slices"
|
|
"sort"
|
|
|
|
"golang.org/x/image/tiff"
|
|
)
|
|
|
|
// tiffBitOrderInput 仅在读取像素块时反转字节内位序,保留目录和原始数据
|
|
type tiffBitOrderInput struct {
|
|
source io.ReaderAt
|
|
ranges [][2]int64
|
|
position int64
|
|
}
|
|
|
|
// tiffBitOrderRanges 收集低位优先的未压缩一位图像块,压缩传真位序由解码器处理
|
|
// 入参: ctx 取消上下文,可为nil, data TIFF编码, layout 已校验的主图布局
|
|
// 返回: [][2]int64 按偏移排序且不重叠的字节区间, error 字段或布局错误
|
|
func tiffBitOrderRanges(ctx context.Context, data []byte, layout TIFFLayout) ([][2]int64, error) {
|
|
if layout.fillOrder != 2 {
|
|
return nil, nil
|
|
}
|
|
var order binary.ByteOrder = binary.LittleEndian
|
|
if data[0] == 'M' {
|
|
order = binary.BigEndian
|
|
}
|
|
offset := int(layout.Offset)
|
|
count := int(order.Uint16(data[offset:]))
|
|
entries := make(map[uint16][]byte, count)
|
|
for i := range count {
|
|
entry := data[offset+2+i*12 : offset+14+i*12]
|
|
entries[order.Uint16(entry)] = entry
|
|
}
|
|
compression := entries[259]
|
|
method := uint16(1)
|
|
if compression != nil {
|
|
if order.Uint16(compression[2:]) != 3 || order.Uint32(compression[4:]) != 1 {
|
|
return nil, fmt.Errorf("invalid TIFF compression field")
|
|
}
|
|
method = order.Uint16(compression[8:])
|
|
}
|
|
if method == 3 || method == 4 {
|
|
return nil, nil
|
|
}
|
|
if method != 1 {
|
|
return nil, fmt.Errorf("unsupported TIFF fill order for compression %d", method)
|
|
}
|
|
if entry := entries[258]; entry != nil {
|
|
if order.Uint16(entry[2:]) != 3 || order.Uint32(entry[4:]) != layout.Samples {
|
|
return nil, fmt.Errorf("invalid TIFF sample depths")
|
|
}
|
|
for i := uint32(0); i < layout.Samples; i++ {
|
|
depth, err := tiffPlaneValue(data, order, entry, i)
|
|
if err != nil || depth != 1 {
|
|
return nil, fmt.Errorf("unsupported TIFF fill order for sample depth")
|
|
}
|
|
}
|
|
}
|
|
locations, lengths := entries[273], entries[279]
|
|
value := func(tag uint16, fallback uint32) (uint32, error) {
|
|
entry := entries[tag]
|
|
if entry == nil {
|
|
return fallback, nil
|
|
}
|
|
if order.Uint32(entry[4:]) != 1 {
|
|
return 0, fmt.Errorf("invalid TIFF scalar field %d", tag)
|
|
}
|
|
return tiffPlaneValue(data, order, entry, 0)
|
|
}
|
|
rows, err := value(278, ^uint32(0))
|
|
if err != nil || rows == 0 {
|
|
return nil, fmt.Errorf("invalid TIFF rows per strip")
|
|
}
|
|
expected := (uint64(layout.Height)-1)/uint64(rows) + 1
|
|
if entries[322] != nil {
|
|
locations, lengths = entries[324], entries[325]
|
|
width, err := value(322, 0)
|
|
if err != nil || width == 0 || width%16 != 0 {
|
|
return nil, fmt.Errorf("invalid TIFF tile width")
|
|
}
|
|
height, err := value(323, 0)
|
|
if err != nil || height == 0 || height%16 != 0 {
|
|
return nil, fmt.Errorf("invalid TIFF tile height")
|
|
}
|
|
expected = ((uint64(layout.Width)-1)/uint64(width) + 1) * ((uint64(layout.Height)-1)/uint64(height) + 1)
|
|
if locations != nil && order.Uint16(locations[2:]) != 4 {
|
|
return nil, fmt.Errorf("invalid TIFF tile offsets")
|
|
}
|
|
}
|
|
if layout.Planar {
|
|
if expected > uint64(^uint32(0))/uint64(layout.Samples) {
|
|
return nil, fmt.Errorf("too many TIFF image blocks")
|
|
}
|
|
expected *= uint64(layout.Samples)
|
|
}
|
|
if locations == nil || lengths == nil {
|
|
return nil, fmt.Errorf("missing TIFF block arrays")
|
|
}
|
|
blocks := order.Uint32(locations[4:])
|
|
if blocks == 0 || uint64(blocks) != expected || order.Uint32(lengths[4:]) != blocks {
|
|
return nil, fmt.Errorf("invalid TIFF block count")
|
|
}
|
|
var ranges [][2]int64
|
|
for i := uint32(0); i < blocks; i++ {
|
|
if ctx != nil {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
at, err := tiffPlaneValue(data, order, locations, i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
size, err := tiffPlaneValue(data, order, lengths, i)
|
|
if err != nil || size == 0 || uint64(at)+uint64(size) > uint64(len(data)) {
|
|
return nil, fmt.Errorf("truncated TIFF image block")
|
|
}
|
|
ranges = append(ranges, [2]int64{int64(at), int64(at) + int64(size)})
|
|
}
|
|
slices.SortFunc(ranges, func(a, b [2]int64) int { return cmp.Compare(a[0], b[0]) })
|
|
merged := ranges[:0]
|
|
for _, span := range ranges {
|
|
if len(merged) != 0 && span[0] <= merged[len(merged)-1][1] {
|
|
merged[len(merged)-1][1] = max(merged[len(merged)-1][1], span[1])
|
|
} else {
|
|
merged = append(merged, span)
|
|
}
|
|
}
|
|
return merged, nil
|
|
}
|
|
|
|
// decodeTIFFBitOrder 使用只读视图校正位序,不分配整幅编码的副本
|
|
// 入参: input 原始解码视图, ranges 需要反转位序的像素区间
|
|
// 返回: image.Image 解码图像, error 解码错误
|
|
func decodeTIFFBitOrder(input interface {
|
|
io.Reader
|
|
io.ReaderAt
|
|
}, ranges [][2]int64) (image.Image, error) {
|
|
if len(ranges) == 0 {
|
|
return tiff.Decode(input)
|
|
}
|
|
return tiff.Decode(&tiffBitOrderInput{source: input, ranges: ranges})
|
|
}
|
|
|
|
// Read 顺序读取并校正像素字节的位序
|
|
// 入参: data 接收缓冲
|
|
// 返回: int 读取字节数, error 读取错误
|
|
func (r *tiffBitOrderInput) Read(data []byte) (int, error) {
|
|
n, err := r.ReadAt(data, r.position)
|
|
r.position += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
// ReadAt 按文件偏移读取,只反转实际读取到的像素字节
|
|
// 入参: data 接收缓冲, offset 文件偏移
|
|
// 返回: int 读取字节数, error 读取错误
|
|
func (r *tiffBitOrderInput) ReadAt(data []byte, offset int64) (int, error) {
|
|
n, err := r.source.ReadAt(data, offset)
|
|
index := sort.Search(len(r.ranges), func(i int) bool { return r.ranges[i][1] > offset })
|
|
for ; index < len(r.ranges) && r.ranges[index][0] < offset+int64(n); index++ {
|
|
span := r.ranges[index]
|
|
for i := max(int64(0), span[0]-offset); i < min(int64(n), span[1]-offset); i++ {
|
|
data[i] = bits.Reverse8(data[i])
|
|
}
|
|
}
|
|
return n, err
|
|
}
|