mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
186 lines
6.4 KiB
Go
186 lines
6.4 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"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"math/bits"
|
|
|
|
"github.com/xiaoqidun/pdfgo"
|
|
)
|
|
|
|
// tiffFaxImage 组合独立解码的传真条带或瓦片,保留紧凑的一位像素存储
|
|
type tiffFaxImage struct {
|
|
blocks []image.Image
|
|
bounds image.Rectangle
|
|
width int
|
|
height int
|
|
across int
|
|
}
|
|
|
|
// decodeTIFFFax 按TIFF传真选项解码各图像块,复用PDFGo的T.4与T.6解码器
|
|
// 入参: ctx 取消上下文,可为nil, data TIFF编码, layout 已校验的主图布局
|
|
// 返回: image.Image 图像, error 字段、解码或取消错误
|
|
func decodeTIFFFax(ctx context.Context, data []byte, layout tiffImageLayout) (image.Image, error) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
if layout.samples != 1 || layout.compression == 3 && layout.t4 & ^uint32(7) != 0 || layout.compression == 4 && layout.t6 & ^uint32(2) != 0 {
|
|
return nil, fmt.Errorf("invalid TIFF fax options")
|
|
}
|
|
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
|
|
}
|
|
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)
|
|
}
|
|
for _, tag := range []uint16{258, 317, 339} {
|
|
v, err := value(tag, 1)
|
|
if err != nil || v != 1 || entries[tag] != nil && order.Uint16(entries[tag][2:]) != 3 {
|
|
return nil, fmt.Errorf("invalid TIFF fax sample field %d", tag)
|
|
}
|
|
}
|
|
photo, err := value(262, 2)
|
|
if err != nil || photo > 1 && photo != 4 || entries[338] != nil {
|
|
return nil, fmt.Errorf("invalid TIFF fax color model")
|
|
}
|
|
width := uint32(layout.width)
|
|
height, err := value(278, ^uint32(0))
|
|
if err != nil || height == 0 {
|
|
return nil, fmt.Errorf("invalid TIFF rows per strip")
|
|
}
|
|
height = min(height, uint32(layout.height))
|
|
locations, lengths := entries[273], entries[279]
|
|
tiled := entries[322] != nil
|
|
if tiled {
|
|
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")
|
|
}
|
|
locations, lengths = entries[324], entries[325]
|
|
if locations != nil && order.Uint16(locations[2:]) != 4 {
|
|
return nil, fmt.Errorf("invalid TIFF tile offsets")
|
|
}
|
|
}
|
|
if uint64(width) > uint64(int(^uint(0)>>1)) || uint64(height) > uint64(int(^uint(0)>>1)) || locations == nil || lengths == nil {
|
|
return nil, fmt.Errorf("invalid TIFF fax blocks")
|
|
}
|
|
across := (uint64(layout.width)-1)/uint64(width) + 1
|
|
down := (uint64(layout.height)-1)/uint64(height) + 1
|
|
blocks := across * down
|
|
if blocks != uint64(order.Uint32(locations[4:])) || blocks != uint64(order.Uint32(lengths[4:])) {
|
|
return nil, fmt.Errorf("invalid TIFF fax block count")
|
|
}
|
|
result := &tiffFaxImage{bounds: image.Rect(0, 0, layout.width, layout.height), width: int(width), height: int(height), across: int(across)}
|
|
for i := uint64(0); i < blocks; i++ {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
at, err := tiffPlaneValue(data, order, locations, uint32(i))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
size, err := tiffPlaneValue(data, order, lengths, uint32(i))
|
|
if err != nil || size == 0 || uint64(at)+uint64(size) > uint64(len(data)) {
|
|
return nil, fmt.Errorf("truncated TIFF fax block")
|
|
}
|
|
encoded := data[uint64(at) : uint64(at)+uint64(size)]
|
|
if layout.fillOrder == 2 {
|
|
reversed := make([]byte, len(encoded))
|
|
for start := 0; start < len(encoded); {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
end := start + min(32768, len(encoded)-start)
|
|
for j := start; j < end; j++ {
|
|
reversed[j] = bits.Reverse8(encoded[j])
|
|
}
|
|
start = end
|
|
}
|
|
encoded = reversed
|
|
}
|
|
rows := height
|
|
if !tiled {
|
|
rows = min(height, uint32(uint64(layout.height)-i*uint64(height)))
|
|
}
|
|
k := pdfgo.Integer(0)
|
|
if layout.compression == 4 {
|
|
k = -1
|
|
} else if layout.compression == 3 && layout.t4&1 != 0 {
|
|
k = 1
|
|
}
|
|
params := pdfgo.Dictionary{
|
|
"K": k, "Columns": pdfgo.Integer(width), "Rows": pdfgo.Integer(rows),
|
|
"EndOfBlock": pdfgo.Boolean(false), "EndOfLine": pdfgo.Boolean(layout.compression == 3),
|
|
"EncodedByteAlign": pdfgo.Boolean(layout.compression == 2), "BlackIs1": pdfgo.Boolean(photo == 1 || photo == 4),
|
|
}
|
|
object := &pdfgo.Image{Width: int(width), Height: int(rows), BitsPerComponent: 1, ColorSpace: pdfgo.Name("DeviceGray"), Stream: &pdfgo.Stream{Dictionary: pdfgo.Dictionary{"Filter": pdfgo.Name("CCITTFaxDecode"), "DecodeParms": params}, Data: encoded}}
|
|
pixels, err := object.DecodeSamplesContext(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("TIFF fax block %d: %w", i, err)
|
|
}
|
|
result.blocks = append(result.blocks, pixels)
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Bounds 返回主图显示区域,不包含边缘瓦片的填充像素
|
|
// 返回: image.Rectangle 图像区域
|
|
func (s *tiffFaxImage) Bounds() image.Rectangle { return s.bounds }
|
|
|
|
// ColorModel 返回二值图像使用的灰度颜色模型
|
|
// 返回: color.Model 灰度模型
|
|
func (s *tiffFaxImage) ColorModel() color.Model { return color.GrayModel }
|
|
|
|
// At 读取所属条带或瓦片中的像素,区域外返回黑色
|
|
// 入参: x 横向像素坐标, y 纵向像素坐标
|
|
// 返回: color.Color 像素颜色
|
|
func (s *tiffFaxImage) At(x, y int) color.Color {
|
|
if !image.Pt(x, y).In(s.bounds) {
|
|
return color.Gray{}
|
|
}
|
|
block := s.blocks[(y/s.height)*s.across+x/s.width]
|
|
return block.At(x%s.width, y%s.height)
|
|
}
|