package util import ( "bytes" "fmt" "image" sdraw "image/draw" // 标准库draw,用于裁切 "image/jpeg" _ "image/png" // Register PNG decoder just in case xdraw "golang.org/x/image/draw" // 扩展库draw,用于高质量缩放 ) // ImageProcessingOptions 图像处理选项 type ImageProcessingOptions struct { // 原图压缩 SourceCompressionEnabled bool SourceQuality int // 1-100,100为不压缩 // 缩略图配置 ThumbnailMode string // "cut"(裁切) 或 "shrink"(缩放) ThumbnailShrinkRatio float64 // 缩放/裁切比例 (0-1) ThumbnailQuality int // 缩略图压缩质量 (1-100) } // DefaultImageProcessingOptions 返回默认的图像处理选项 // 默认值:原图不压缩;缩略图裁切模式,比例0.5,质量75 func DefaultImageProcessingOptions() *ImageProcessingOptions { return &ImageProcessingOptions{ SourceCompressionEnabled: false, SourceQuality: 100, ThumbnailMode: "cut", ThumbnailShrinkRatio: 0.5, ThumbnailQuality: 75, } } // ProcessedImages 处理后的图像结果 type ProcessedImages struct { FaceData []byte // 人脸图(不压缩) ThumbData []byte // 缩略图 SourceData []byte // 原图(可能压缩) } // ProcessImages 根据配置处理所有图像 // faceData: 人脸图数据 // srcData: 原图数据 // faceRect: 人脸区域坐标 (leftTopX, leftTopY, rightBtmX, rightBtmY) // opts: 处理选项(如果为nil,使用默认值) func ProcessImages(faceData, srcData []byte, faceRect [4]int, opts *ImageProcessingOptions) (*ProcessedImages, error) { if opts == nil { opts = DefaultImageProcessingOptions() } result := &ProcessedImages{} // 1. 人脸图不压缩,直接使用原始数据 result.FaceData = faceData // 2. 处理原图(根据配置决定是否压缩) if len(srcData) > 0 { var err error if opts.SourceCompressionEnabled && opts.SourceQuality < 100 { result.SourceData, err = CompressImage(srcData, opts.SourceQuality) if err != nil { return nil, fmt.Errorf("compress source image failed: %v", err) } } else { result.SourceData = srcData } } // 3. 生成缩略图 if len(srcData) > 0 { var err error switch opts.ThumbnailMode { case "shrink": // 整体缩放模式 result.ThumbData, err = GenerateThumbnailShrink(srcData, opts.ThumbnailShrinkRatio, opts.ThumbnailQuality) default: // "cut" 或其他默认为裁切模式 // 以人脸为中心裁切 result.ThumbData, err = GenerateThumbnailCut(srcData, faceRect, opts.ThumbnailShrinkRatio, opts.ThumbnailQuality) } if err != nil { return nil, fmt.Errorf("generate thumbnail failed: %v", err) } } return result, nil } // CompressImage 压缩图片到指定质量 func CompressImage(srcData []byte, quality int) ([]byte, error) { img, _, err := image.Decode(bytes.NewReader(srcData)) if err != nil { return nil, fmt.Errorf("decode image failed: %v", err) } var buf bytes.Buffer err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality}) if err != nil { return nil, fmt.Errorf("encode image failed: %v", err) } return buf.Bytes(), nil } // GenerateThumbnailShrink 整体缩放模式生成缩略图 // ratio: 缩放比例(0-1),如0.5表示缩放到原图50%大小 // quality: JPEG压缩质量(1-100) func GenerateThumbnailShrink(srcData []byte, ratio float64, quality int) ([]byte, error) { img, _, err := image.Decode(bytes.NewReader(srcData)) if err != nil { return nil, fmt.Errorf("decode image failed: %v", err) } bounds := img.Bounds() origW := bounds.Dx() origH := bounds.Dy() // 计算目标尺寸 targetW := int(float64(origW) * ratio) targetH := int(float64(origH) * ratio) if targetW == 0 || targetH == 0 { return nil, fmt.Errorf("image too small to generate thumbnail with ratio %.2f", ratio) } // 创建目标图像并使用高质量缩放 dst := image.NewRGBA(image.Rect(0, 0, targetW, targetH)) xdraw.CatmullRom.Scale(dst, dst.Bounds(), img, bounds, xdraw.Over, nil) // 编码为JPEG var buf bytes.Buffer err = jpeg.Encode(&buf, dst, &jpeg.Options{Quality: quality}) if err != nil { return nil, fmt.Errorf("encode thumbnail failed: %v", err) } return buf.Bytes(), nil } // GenerateThumbnailCut 以人脸为中心裁切模式生成缩略图 // faceRect: 人脸区域坐标 [leftTopX, leftTopY, rightBtmX, rightBtmY] // ratio: 裁切比例(0-1),裁切区域为原图尺寸的ratio倍 // quality: JPEG压缩质量(1-100) func GenerateThumbnailCut(srcData []byte, faceRect [4]int, ratio float64, quality int) ([]byte, error) { img, _, err := image.Decode(bytes.NewReader(srcData)) if err != nil { return nil, fmt.Errorf("decode image failed: %v", err) } bounds := img.Bounds() origW := bounds.Dx() origH := bounds.Dy() // 计算目标尺寸(原图的ratio倍) targetW := int(float64(origW) * ratio) targetH := int(float64(origH) * ratio) if targetW == 0 || targetH == 0 { return nil, fmt.Errorf("image too small to generate thumbnail with ratio %.2f", ratio) } // 计算人脸中心 centerX := (faceRect[0] + faceRect[2]) / 2 centerY := (faceRect[1] + faceRect[3]) / 2 // 如果人脸坐标无效,使用图片中心 if centerX <= 0 || centerY <= 0 { centerX = origW / 2 centerY = origH / 2 } // 计算裁切起点(以人脸中心为中心) cropX := centerX - targetW/2 cropY := centerY - targetH/2 // 约束到图像边界 if cropX < 0 { cropX = 0 } if cropY < 0 { cropY = 0 } if cropX+targetW > origW { cropX = origW - targetW } if cropY+targetH > origH { cropY = origH - targetH } // 再次检查边界(处理图像尺寸小于目标尺寸的情况) if cropX < 0 { cropX = 0 } if cropY < 0 { cropY = 0 } // 执行裁切 cropRect := image.Rect(cropX, cropY, cropX+targetW, cropY+targetH) var dst image.Image // 尝试使用SubImage优化性能 type SubImager interface { SubImage(r image.Rectangle) image.Image } if sub, ok := img.(SubImager); ok { dst = sub.SubImage(cropRect) } else { // 回退方案:创建新图像并绘制 rgba := image.NewRGBA(image.Rect(0, 0, targetW, targetH)) sdraw.Draw(rgba, rgba.Bounds(), img, cropRect.Min, sdraw.Src) dst = rgba } // 编码为JPEG var buf bytes.Buffer err = jpeg.Encode(&buf, dst, &jpeg.Options{Quality: quality}) if err != nil { return nil, fmt.Errorf("encode thumbnail failed: %v", err) } return buf.Bytes(), nil } // GenerateThumbnail creates a thumbnail by cropping the original image. // The thumbnail size is 1/2 of the original dimensions. // The crop is centered on the provided coordinates (centerX, centerY), constrained to image bounds. // 已废弃:请使用 GenerateThumbnailCut 或 GenerateThumbnailShrink func GenerateThumbnail(srcData []byte, centerX, centerY int) ([]byte, error) { return GenerateThumbnailCut(srcData, [4]int{centerX, centerY, centerX, centerY}, 0.5, 85) } // GenerateThumbnailFromCoords Helper to process using LeftTop and RightBottom coordinates // 已废弃:请使用 ProcessImages func GenerateThumbnailFromCoords(srcData []byte, ltX, ltY, rbX, rbY int) ([]byte, error) { return GenerateThumbnailCut(srcData, [4]int{ltX, ltY, rbX, rbY}, 0.5, 85) }