mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 15:06:46 +08:00
1 parent
ee3fd02a0a
commit
64f9ec4241
87 files changed
+2693
-2289
No files matched your search
@@ -0,0 +1,185 @@
|
||||
// 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 (
|
||||
"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 TIFFLayout) (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)
|
||||
}
|
||||
Reference in new issue
Block a user