// 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 ( "bytes" "encoding/binary" "fmt" "image" "image/color" _ "image/gif" "math" _ "github.com/xiaoqidun/jbig2" _ "golang.org/x/image/bmp" "golang.org/x/image/draw" _ "golang.org/x/image/tiff" ) // transparentEdgeImage 共享只读像素并按需计算透明边缘和采样边距 type transparentEdgeImage struct { source *image.NRGBA rect image.Rectangle } // transparentEdgeImage64 按需补齐十六位像素边缘,保留原始只读图像 type transparentEdgeImage64 struct { source image.Image rect image.Rectangle } // Bounds 返回含采样边距的图像边界 // 返回: image.Rectangle 图像边界 func (s *transparentEdgeImage) Bounds() image.Rectangle { return s.rect } // ColorModel 返回非预乘颜色模型 // 返回: color.Model 颜色模型 func (s *transparentEdgeImage) ColorModel() color.Model { return color.NRGBAModel } // NRGBAAt 读取原始像素或按邻域补齐透明边缘,边界外透明 // 入参: x 横向坐标, y 纵向坐标 // 返回: color.NRGBA 非预乘颜色 func (s *transparentEdgeImage) NRGBAAt(x, y int) color.NRGBA { if !image.Pt(x, y).In(s.rect) { return color.NRGBA{} } b := s.source.Bounds() sx, sy := b.Min.X+min(max(x-1, 0), b.Dx()-1), b.Min.Y+min(max(y-1, 0), b.Dy()-1) c := s.source.NRGBAAt(sx, sy) if c.A == 0 { if edge, ok := transparentEdgeColor(s.source, sx, sy); ok { c = edge } } if x == 0 || y == 0 || x == s.rect.Max.X-1 || y == s.rect.Max.Y-1 { c = transparentPaddingColor(c) } return c } // At 返回非预乘像素颜色 // 入参: x 横向坐标, y 纵向坐标 // 返回: color.Color 像素颜色 func (s *transparentEdgeImage) At(x, y int) color.Color { return s.NRGBAAt(x, y) } // RGBA64At 返回预乘十六位像素,供采样器无装箱读取 // 入参: x 横向坐标, y 纵向坐标 // 返回: color.RGBA64 预乘颜色 func (s *transparentEdgeImage) RGBA64At(x, y int) color.RGBA64 { r, g, b, a := s.NRGBAAt(x, y).RGBA() return color.RGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: uint16(a)} } // Bounds 返回含采样边距的图像边界 // 返回: image.Rectangle 图像边界 func (s *transparentEdgeImage64) Bounds() image.Rectangle { return s.rect } // ColorModel 返回十六位非预乘颜色模型 // 返回: color.Model 颜色模型 func (s *transparentEdgeImage64) ColorModel() color.Model { return color.NRGBA64Model } // NRGBA64At 读取十六位原始颜色,透明邻域使用最小非零透明度保留边缘颜色 // 入参: x 横向坐标, y 纵向坐标 // 返回: color.NRGBA64 非预乘颜色 func (s *transparentEdgeImage64) NRGBA64At(x, y int) color.NRGBA64 { if !image.Pt(x, y).In(s.rect) { return color.NRGBA64{} } b := s.source.Bounds() sx, sy := b.Min.X+min(max(x-1, 0), b.Dx()-1), b.Min.Y+min(max(y-1, 0), b.Dy()-1) c := imageNRGBA64At(s.source, sx, sy) if c.A == 0 { var best color.NRGBA64 for ny := max(sy-1, b.Min.Y); ny < min(sy+2, b.Max.Y); ny++ { for nx := max(sx-1, b.Min.X); nx < min(sx+2, b.Max.X); nx++ { v := imageNRGBA64At(s.source, nx, ny) if v.A > best.A { best = v } } } if best.A != 0 { best.A = 1 c = best } } if x == 0 || y == 0 || x == s.rect.Max.X-1 || y == s.rect.Max.Y-1 { c.A = 1 } return c } // At 返回非预乘十六位像素颜色 // 入参: x 横向坐标, y 纵向坐标 // 返回: color.Color 像素颜色 func (s *transparentEdgeImage64) At(x, y int) color.Color { return s.NRGBA64At(x, y) } // RGBA64At 返回保留原采样精度的预乘颜色 // 入参: x 横向坐标, y 纵向坐标 // 返回: color.RGBA64 预乘颜色 func (s *transparentEdgeImage64) RGBA64At(x, y int) color.RGBA64 { r, g, b, a := s.NRGBA64At(x, y).RGBA() return color.RGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: uint16(a)} } // decodeImageData 解码图片数据 // 入参: data 图片数据 // 返回: image.Image 图片对象, string 图片格式, error 错误信息 func decodeImageData(data []byte) (image.Image, string, error) { if isJPEGData(data) || isPNGData(data) { img, err := newEncodedImage(data) if err != nil { return nil, "", err } return img, img.format, nil } img, format, err := image.Decode(bytes.NewReader(data)) if err == nil { return img, normalizeSealType(format), nil } if img, err := decodeBMPImage(data); err == nil { return img, "bmp", nil } return nil, "", err } // isJPEGData 判断是否为JPEG图片数据 // 入参: data 图片数据 // 返回: bool 是否为JPEG图片数据 func isJPEGData(data []byte) bool { return len(data) >= 2 && data[0] == 0xFF && data[1] == 0xD8 } // isPNGData 判断是否为PNG图片数据 // 入参: data 图片数据 // 返回: bool 是否为PNG图片数据 func isPNGData(data []byte) bool { return bytes.HasPrefix(data, []byte("\x89PNG\r\n\x1a\n")) } // decodeImageConfigData 解码图片尺寸 // 入参: data 图片数据 // 返回: image.Config 图片尺寸, string 图片格式, error 错误信息 func decodeImageConfigData(data []byte) (image.Config, string, error) { cfg, format, err := image.DecodeConfig(bytes.NewReader(data)) if err == nil { return cfg, normalizeSealType(format), nil } if cfg, err := decodeBMPConfig(data); err == nil { return cfg, "bmp", nil } return image.Config{}, "", err } // decodeBMPConfig 解码BMP尺寸 // 入参: data 图片数据 // 返回: image.Config 图片尺寸, error 错误信息 func decodeBMPConfig(data []byte) (image.Config, error) { offset, width, height, bpp, compression, err := parseBMPHeader(data) if err != nil { return image.Config{}, err } if offset <= 0 || width <= 0 || height == 0 || compression != 0 { return image.Config{}, fmt.Errorf("unsupported bmp") } if bpp != 16 && bpp != 24 && bpp != 32 { return image.Config{}, fmt.Errorf("unsupported bmp") } if height < 0 { height = -height } return image.Config{ColorModel: color.NRGBAModel, Width: width, Height: height}, nil } // decodeBMPImage 解码BMP图片 // 入参: data 图片数据 // 返回: image.Image 图片对象, error 错误信息 func decodeBMPImage(data []byte) (image.Image, error) { offset, width, height, bpp, compression, err := parseBMPHeader(data) if err != nil { return nil, err } if compression != 0 { return nil, fmt.Errorf("unsupported bmp compression") } if bpp != 16 && bpp != 24 && bpp != 32 { return nil, fmt.Errorf("unsupported bmp depth") } topDown := height < 0 if topDown { height = -height } if width <= 0 || height <= 0 { return nil, fmt.Errorf("invalid bmp size") } stride := ((int64(width)*int64(bpp) + 31) / 32) * 4 if stride > int64(len(data)-offset)/int64(height) { return nil, fmt.Errorf("truncated bmp") } rowStride := int(stride) img := image.NewNRGBA(image.Rect(0, 0, width, height)) for y := 0; y < height; y++ { srcY := y if !topDown { srcY = height - 1 - y } row := data[offset+srcY*rowStride:] for x := 0; x < width; x++ { switch bpp { case 16: v := binary.LittleEndian.Uint16(row[x*2:]) img.SetNRGBA(x, y, color.NRGBA{ R: uint8(((v >> 10) & 0x1F) * 255 / 31), G: uint8(((v >> 5) & 0x1F) * 255 / 31), B: uint8((v & 0x1F) * 255 / 31), A: 255, }) case 24: p := x * 3 img.SetNRGBA(x, y, color.NRGBA{R: row[p+2], G: row[p+1], B: row[p], A: 255}) case 32: p := x * 4 img.SetNRGBA(x, y, color.NRGBA{R: row[p+2], G: row[p+1], B: row[p], A: 255}) } } } return img, nil } // parseBMPHeader 解析BMP文件头 // 入参: data 图片数据 // 返回: int 像素偏移, int 宽度, int 高度, uint16 位数, uint32 压缩方式, error 错误信息 func parseBMPHeader(data []byte) (int, int, int, uint16, uint32, error) { if len(data) < 54 || string(data[:2]) != "BM" { return 0, 0, 0, 0, 0, fmt.Errorf("not bmp") } offset := int(binary.LittleEndian.Uint32(data[10:14])) dibSize := binary.LittleEndian.Uint32(data[14:18]) if dibSize < 40 { return 0, 0, 0, 0, 0, fmt.Errorf("unsupported bmp") } if uint64(dibSize)+14 > uint64(len(data)) || offset < 0 || uint64(offset) < uint64(dibSize)+14 || offset > len(data) { return 0, 0, 0, 0, 0, fmt.Errorf("invalid bmp pixel offset") } width := int(int32(binary.LittleEndian.Uint32(data[18:22]))) height := int(int32(binary.LittleEndian.Uint32(data[22:26]))) planes := binary.LittleEndian.Uint16(data[26:28]) bpp := binary.LittleEndian.Uint16(data[28:30]) compression := binary.LittleEndian.Uint32(data[30:34]) if planes != 1 || width <= 0 || height == 0 || int64(height) == -int64(^uint(0)>>1)-1 { return 0, 0, 0, 0, 0, fmt.Errorf("invalid bmp") } return offset, width, height, bpp, compression, nil } // imageWithMask 应用图片蒙版 // 入参: img 图片对象, mask 蒙版图片 // 返回: image.Image 蒙版处理后的图片对象 func imageWithMask(img, mask image.Image) image.Image { if img == nil || mask == nil { return img } bounds := img.Bounds() if bounds.Empty() || mask.Bounds().Empty() { return img } source := imagePixelSource(img) maskSource := imagePixelSource(mask) maskBounds := maskSource.Bounds() var opacity image.Image = maskSource if bounds.Size() != maskBounds.Size() { resized := image.NewGray(bounds) draw.CatmullRom.Scale(resized, bounds, maskSource, maskBounds, draw.Src, nil) opacity = resized maskBounds = bounds } out := image.NewNRGBA(bounds) if src, ok := source.(*image.NRGBA); ok { for y := 0; y < bounds.Dy(); y++ { offset := src.PixOffset(bounds.Min.X, bounds.Min.Y+y) row := out.Pix[y*out.Stride : (y+1)*out.Stride] copy(row, src.Pix[offset:offset+len(row)]) for x := 3; x < len(row); x += 4 { a := imageGrayAt(opacity, maskBounds.Min.X+x/4, maskBounds.Min.Y+y).Y row[x] = uint8(int(row[x]) * int(a) / 255) } } return out } for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { mx := maskBounds.Min.X + x - bounds.Min.X my := maskBounds.Min.Y + y - bounds.Min.Y a := imageGrayAt(opacity, mx, my).Y c := imageNRGBAAt(source, x, y) c.A = uint8(int(c.A) * int(a) / 255) out.SetNRGBA(x, y, c) } } return out } // imageWithAlpha 合并图片透明度 // 入参: img 图片对象, alpha 对象透明度 // 返回: image.Image 合并后的图片对象 func imageWithAlpha(img image.Image, alpha *int) image.Image { if img == nil || alpha == nil { return img } a := clampColor(*alpha) if a == 255 { return img } bounds := img.Bounds() out := image.NewNRGBA(bounds) source := imagePixelSource(img) if src, ok := source.(*image.NRGBA); ok { for y := 0; y < bounds.Dy(); y++ { offset := src.PixOffset(bounds.Min.X, bounds.Min.Y+y) row := out.Pix[y*out.Stride : (y+1)*out.Stride] copy(row, src.Pix[offset:offset+len(row)]) for x := 3; x < len(row); x += 4 { row[x] = uint8(int(row[x]) * a / 255) } } return out } for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { c := imageNRGBAAt(source, x, y) c.A = uint8(int(c.A) * a / 255) out.SetNRGBA(x, y, c) } } return out } // imagePixelSource 获取图片像素源 // 入参: img 图片对象 // 返回: image.Image 图片像素源 func imagePixelSource(img image.Image) image.Image { if decoded, err := imagePixelData(img); err == nil { return decoded } return img } // imagePixelData 获取像素并保留惰性解码错误,供绘制和像素处理入口校验 // 入参: img 图片对象 // 返回: image.Image 解码像素, error 解码错误 func imagePixelData(img image.Image) (image.Image, error) { if src, ok := img.(interface{ Image() (image.Image, error) }); ok { return src.Image() } return img, nil } // imageGrayAt 获取图片灰度像素 // 入参: img 图片对象, x X坐标, y Y坐标 // 返回: color.Gray 灰度像素 func imageGrayAt(img image.Image, x, y int) color.Gray { if src, ok := img.(*image.Gray); ok { return src.GrayAt(x, y) } if src, ok := img.(*image.Paletted); ok { return color.GrayModel.Convert(src.At(x, y)).(color.Gray) } if src, ok := img.(image.RGBA64Image); ok { r, g, b, _ := src.RGBA64At(x, y).RGBA() return color.Gray{Y: uint8((19595*r + 38470*g + 7471*b + 1<<15) >> 24)} } return color.GrayModel.Convert(img.At(x, y)).(color.Gray) } // imageNRGBAAt 获取图片NRGBA像素 // 入参: img 图片对象, x X坐标, y Y坐标 // 返回: color.NRGBA NRGBA像素 func imageNRGBAAt(img image.Image, x, y int) color.NRGBA { if src, ok := img.(*image.NRGBA); ok { return src.NRGBAAt(x, y) } if src, ok := img.(*image.YCbCr); ok { r, g, b, _ := src.YCbCrAt(x, y).RGBA() return color.NRGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: 255} } if src, ok := img.(*image.RGBA); ok { c := src.RGBA64At(x, y) if c.A == 0 { return color.NRGBA{} } r, g, b, a := uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A) if a != 0xffff { r = r * 0xffff / a g = g * 0xffff / a b = b * 0xffff / a } return color.NRGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)} } return color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA) } // imageNRGBA64At 读取十六位非预乘像素,原生图像避免颜色装箱 // 入参: img 图片对象, x X坐标, y Y坐标 // 返回: color.NRGBA64 NRGBA64像素 func imageNRGBA64At(img image.Image, x, y int) color.NRGBA64 { switch src := img.(type) { case *image.NRGBA64: return src.NRGBA64At(x, y) case *image.Gray16: c := src.Gray16At(x, y) return color.NRGBA64{R: c.Y, G: c.Y, B: c.Y, A: 0xffff} case *image.Alpha16: c := src.Alpha16At(x, y) if c.A == 0 { return color.NRGBA64{} } return color.NRGBA64{R: 0xffff, G: 0xffff, B: 0xffff, A: c.A} case *image.RGBA, *image.RGBA64: c := src.(image.RGBA64Image).RGBA64At(x, y) if c.A == 0 { return color.NRGBA64{} } r, g, b, a := uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A) if a != 0xffff { r = r * 0xffff / a g = g * 0xffff / a b = b * 0xffff / a } return color.NRGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: c.A} } return color.NRGBA64Model.Convert(img.At(x, y)).(color.NRGBA64) } // transparentEdgeColor 获取透明像素相邻的可见颜色 // 入参: img 图片对象, x X坐标, y Y坐标 // 返回: color.NRGBA 颜色, bool 是否存在 func transparentEdgeColor(img *image.NRGBA, x, y int) (color.NRGBA, bool) { bounds := img.Bounds() best, alpha := 0, uint8(0) left, right := max(x-1, bounds.Min.X), min(x+2, bounds.Max.X) scan: for ny := max(y-1, bounds.Min.Y); ny < min(y+2, bounds.Max.Y); ny++ { offset := img.PixOffset(left, ny) for nx := left; nx < right; nx++ { if (nx != x || ny != y) && img.Pix[offset+3] > alpha { best, alpha = offset, img.Pix[offset+3] if alpha == 255 { break scan } } offset += 4 } } if alpha == 0 { return color.NRGBA{}, false } return color.NRGBA{R: img.Pix[best], G: img.Pix[best+1], B: img.Pix[best+2], A: 1}, true } // transparentPaddingColor 获取透明补齐颜色 // 入参: c 边缘颜色 // 返回: color.NRGBA 补齐颜色 func transparentPaddingColor(c color.NRGBA) color.NRGBA { c.A = 1 return c } // clipStampImage 裁剪印章图像 // 入参: img 印章图像, box 印章区域, clip 裁剪区域 // 返回: image.Image 裁剪后的印章图像 func clipStampImage(img image.Image, box, clip Box) image.Image { bounds := img.Bounds() x0 := int(math.Floor(clip.X / box.W * float64(bounds.Dx()))) y0 := int(math.Floor(clip.Y / box.H * float64(bounds.Dy()))) x1 := int(math.Ceil((clip.X + clip.W) / box.W * float64(bounds.Dx()))) y1 := int(math.Ceil((clip.Y + clip.H) / box.H * float64(bounds.Dy()))) out := image.NewNRGBA(image.Rect(0, 0, x1-x0, y1-y0)) draw.Draw(out, out.Bounds(), img, image.Pt(bounds.Min.X+x0, bounds.Min.Y+y0), draw.Src) return out } // stampImageWithTransparentWhite 处理印章图片白色底色 // 入参: img 印章图片对象 // 返回: image.Image 处理后的印章图片对象 func stampImageWithTransparentWhite(img image.Image) image.Image { if opaque, ok := img.(interface{ Opaque() bool }); ok && !opaque.Opaque() { return img } bounds := img.Bounds() out := image.NewNRGBA(bounds) hasAlpha := false for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA) if c.A < 255 { hasAlpha = true } out.SetNRGBA(x, y, c) } } if hasAlpha { return img } for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { c := out.NRGBAAt(x, y) if c.R >= 250 && c.G >= 250 && c.B >= 250 { c.A = 0 out.SetNRGBA(x, y, c) } } } return out } // imageWithTransparentEdge 补齐透明图片边缘颜色 // 入参: img 图片对象 // 返回: image.Image 补齐后的图片对象, int 补齐像素数 func imageWithTransparentEdge(img image.Image) (image.Image, int) { if img == nil { return img, 0 } bounds := img.Bounds() w, h := bounds.Dx(), bounds.Dy() if w == 0 || h == 0 { return img, 0 } if src, ok := img.(*EncodedImage); ok && src.MIME() == "image/jpeg" { return img, 0 } source := imagePixelSource(img) if opaque, ok := source.(interface{ Opaque() bool }); ok && opaque.Opaque() { return img, 0 } if model := source.ColorModel(); model == color.NRGBA64Model || model == color.RGBA64Model || model == color.Alpha16Model || model == color.Gray16Model { return imageWithTransparentEdge64(img, source) } hasZero, hasVisible := false, false src, ok := source.(*image.NRGBA) if ok { srcBounds := src.Bounds() hasZero = src.Pix[3] == 0 hasVisible = !hasZero scan: for y := srcBounds.Min.Y; y < srcBounds.Max.Y; y++ { offset := src.PixOffset(srcBounds.Min.X, y) + 3 for x := 0; x < w; x++ { if (src.Pix[offset] == 0) != hasZero { hasZero, hasVisible = true, true break scan } offset += 4 } } } else { src = image.NewNRGBA(image.Rect(0, 0, w, h)) for y := 0; y < h; y++ { for x := 0; x < w; x++ { c := imageNRGBAAt(source, bounds.Min.X+x, bounds.Min.Y+y) src.SetNRGBA(x, y, c) if c.A == 0 { hasZero = true } else { hasVisible = true } } } } if !hasZero || !hasVisible { return img, 0 } return &transparentEdgeImage{source: src, rect: image.Rect(0, 0, w+2, h+2)}, 1 } // imageWithTransparentEdge64 保留十六位颜色和透明度,不经八位图像中转 // 入参: original 原始图像, source 已解码的像素源 // 返回: image.Image 边缘处理结果, int 补齐像素数 func imageWithTransparentEdge64(original, source image.Image) (image.Image, int) { bounds := source.Bounds() hasZero, hasVisible := false, false for y := bounds.Min.Y; y < bounds.Max.Y && !(hasZero && hasVisible); y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { _, _, _, a := source.At(x, y).RGBA() if a == 0 { hasZero = true } else { hasVisible = true } if hasZero && hasVisible { break } } } if !hasZero || !hasVisible { return original, 0 } return &transparentEdgeImage64{source: source, rect: image.Rect(0, 0, bounds.Dx()+2, bounds.Dy()+2)}, 1 }