package main // #include import "C" import ( "encoding/json" "fmt" "github.com/disintegration/imaging" "github.com/nfnt/resize" "golang.org/x/image/draw" "image" "image/color" "image/jpeg" _ "image/jpeg" "image/png" _ "image/png" "os" "path/filepath" "strings" "unsafe" ) // Config 配置结构体 type Config struct { OutputDir string // 输出目录路径 FileName string // 文件名 MatchDir string // 满足条件的图片目录名 UnmatchDir string // 不满足条件的图片目录名 EqualHeightDir string // 等高的图片目录名 WhiteDir string // 白色底图的图片目录名 WhiteBorderPngDir string // 去白边转PNG的图片目录名 MinWhitePct float64 // 纯白占比下限(0-1) MaxWhitePct float64 // 纯白占比上限(0-1) Extensions []string // 支持的图片扩展名 } // 检查图片 func validateConfig(config *Config) error { // 检查百分比范围 if config.MinWhitePct < 0 || config.MinWhitePct > 1 { return fmt.Errorf("纯白占比下限必须在0-1之间") } if config.MaxWhitePct < 0 || config.MaxWhitePct > 1 { return fmt.Errorf("纯白占比上限必须在0-1之间") } if config.MinWhitePct > config.MaxWhitePct { return fmt.Errorf("下限不能大于上限") } return nil } func createDirs(config *Config) error { // 创建输出根目录 if err := os.MkdirAll(config.OutputDir, 0755); err != nil { return err } // 创建匹配目录 matchPath := filepath.Join(config.OutputDir, config.MatchDir) if err := os.MkdirAll(matchPath, 0755); err != nil { return err } // 创建不匹配目录 unmatchPath := filepath.Join(config.OutputDir, config.UnmatchDir) if err := os.MkdirAll(unmatchPath, 0755); err != nil { return err } equalHeightPath := filepath.Join(config.OutputDir, config.EqualHeightDir) if err := os.MkdirAll(equalHeightPath, 0755); err != nil { return err } whitePath := filepath.Join(config.OutputDir, config.WhiteDir) if err := os.MkdirAll(whitePath, 0755); err != nil { return err } whiteBorderPngPath := filepath.Join(config.OutputDir, config.WhiteBorderPngDir) if err := os.MkdirAll(whiteBorderPngPath, 0755); err != nil { return err } return nil } // 计算纯白占比 func calculateWhitePercentage(imagePath string) (float64, error) { // 打开图片文件 file, err := os.Open(imagePath) if err != nil { return 0, err } defer file.Close() // 解码图片 img, _, err := image.Decode(file) if err != nil { return 0, err } bounds := img.Bounds() totalPixels := bounds.Dx() * bounds.Dy() whitePixels := 0 // 遍历每个像素,判断是否为纯白色 for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { pixel := color.RGBAModel.Convert(img.At(x, y)).(color.RGBA) // 判断是否为纯白色 (R=255, G=255, B=255) if pixel.R == 255 && pixel.G == 255 && pixel.B == 255 { whitePixels++ } } } return float64(whitePixels) / float64(totalPixels), nil } // 复制文件到相应目录 func copyToDestination(srcPath string, config *Config, isMatch bool) error { filename := filepath.Base(srcPath) // 确定目标目录 var destDir string if isMatch { destDir = filepath.Join(config.OutputDir, config.MatchDir) } else { destDir = filepath.Join(config.OutputDir, config.UnmatchDir) } destPath := filepath.Join(destDir, filename) // 读取源文件 data, err := os.ReadFile(srcPath) if err != nil { return err } // 写入目标文件 return os.WriteFile(destPath, data, 0644) } // 保存文件 func saveImage(outputPath string, img image.Image, format string) error { // 创建输出文件 outFile, err := os.Create(outputPath) if err != nil { return err } defer outFile.Close() // 根据原始格式保存图片 switch format { case "jpeg", "jpg": // JPEG 格式可以设置质量参数 return jpeg.Encode(outFile, img, &jpeg.Options{Quality: 95}) case "png": // PNG 格式通常不需要质量参数 return png.Encode(outFile, img) default: // 默认使用 JPEG 格式 return jpeg.Encode(outFile, img, &jpeg.Options{Quality: 95}) } } // 检测图片纯白占比 func processImage(config *Config) error { // 创建输出目录 if err := createDirs(config); err != nil { return fmt.Errorf("创建目录失败: %v\n", err) } if err := validateConfig(config); err != nil { return err } // 计算纯白占比 whitePct, err := calculateWhitePercentage(config.FileName) if err != nil { return fmt.Errorf("错误: %v\n", err) } // 判断是否在范围内 isMatch := whitePct >= config.MinWhitePct && whitePct <= config.MaxWhitePct status := "❌ 不满足" if isMatch { status = "✅ 满足" } fmt.Printf("纯白占比: %.2f%% %s\n", whitePct*100, status) // 复制文件到相应目录 if err = copyToDestination(config.FileName, config, isMatch); err != nil { return fmt.Errorf("复制失败: %v\n", err) } return nil } // 根据原始图片生成新的白底图片 func createWhiteBottomCenteredImage(config *Config, width, height int) (string, error) { // 创建输出目录 if err := createDirs(config); err != nil { fmt.Printf("创建目录失败: %v\n", err) os.Exit(1) } // 打开图片 file, err := os.Open(config.FileName) if err != nil { return "", fmt.Errorf("打开图片失败: %v", err) } defer file.Close() // 解码原始图片 img, format, err := image.Decode(file) if err != nil { return "", fmt.Errorf("图片解码失败: %v", err) } // 创建透明背景 dst := image.NewRGBA(image.Rect(0, 0, width, height)) // 设置背景颜色 var bgColor color.Color bgColor = color.RGBA{255, 255, 255, 255} // 白色 // 填充透明背景 draw.Draw(dst, dst.Bounds(), &image.Uniform{bgColor}, image.Point{}, draw.Src) // 计算居中位置 srcBounds := img.Bounds() srcWidth := srcBounds.Dx() srcHeight := srcBounds.Dy() x := (width - srcWidth) / 2 y := (height - srcHeight) / 2 // 将原图绘制到中央 draw.Draw(dst, image.Rect(x, y, x+srcWidth, y+srcHeight), img, image.Point{}, draw.Over) filename := filepath.Base(config.FileName) destPath := filepath.Join(config.OutputDir, config.WhiteDir, filename) saveImage(destPath, dst, format) return destPath, nil } // 根据高度生成等比例图片 func resizeToHeightQuality(config *Config, targetHeight int) (string, error) { // 创建输出目录 if err := createDirs(config); err != nil { fmt.Printf("创建目录失败: %v\n", err) os.Exit(1) } // 打开图片 file, err := os.Open(config.FileName) if err != nil { return "", fmt.Errorf("打开图片失败: %v", err) } defer file.Close() // 解码原始图片 img, format, err := image.Decode(file) if err != nil { return "", fmt.Errorf("图片解码失败: %v", err) } bounds := img.Bounds() srcWidth := bounds.Dx() srcHeight := bounds.Dy() // 计算等比例缩放后的宽度 targetWidth := uint(float64(srcWidth) * float64(targetHeight) / float64(srcHeight)) // 使用 Lanczos3 插值算法进行高质量缩放 imageNew := resize.Resize(targetWidth, uint(targetHeight), img, resize.Lanczos3) // 保存图片到指定目录下 filename := filepath.Base(config.FileName) destPath := filepath.Join(config.OutputDir, config.EqualHeightDir, filename) saveImage(destPath, imageNew, format) return destPath, nil } // ImageToPNGConverter 图片去白边并转为PNG type ImageToPNGConverter struct { Threshold int Margin int BgColor color.RGBA DetectColor *color.RGBA KeepTransparent bool PNGCompressLevel png.CompressionLevel Quality int } // 去掉白边并转PNG图片工具 func removeWhiteBorderAndPNG(config *Config) (string, error) { // 创建输出目录 if err := createDirs(config); err != nil { fmt.Printf("创建目录失败: %v\n", err) os.Exit(1) } // 打开图片 file, err := os.Open(config.FileName) if err != nil { return "", fmt.Errorf("打开图片失败: %v", err) } defer file.Close() // 解码原始图片 img, _, err := image.Decode(file) if err != nil { return "", fmt.Errorf("图片解码失败: %v", err) } compressLevel := 6 // 创建转换器 compressionLevel := png.DefaultCompression switch { case compressLevel <= 0: compressionLevel = png.NoCompression case compressLevel >= 9: compressionLevel = png.BestCompression default: // 使用默认压缩级别 } converter := newImageToPNGConverter( 240, 0, &color.RGBA{R: 255, G: 255, B: 255, A: 255}, nil, false, compressionLevel, 95, ) toPNG := converter.convertToPNG(img, true) // 保存图片到指定目录下 filename := filepath.Base(config.FileName) // 去除扩展名 nameWithoutExt := strings.TrimSuffix(filename, filepath.Ext(filename)) destPath := filepath.Join(config.OutputDir, config.WhiteBorderPngDir, nameWithoutExt+".png") saveImage(destPath, toPNG, "png") return destPath, nil } // newImageToPNGConverter 创建新的转换器 func newImageToPNGConverter(threshold, margin int, bgColor, detectColor *color.RGBA, keepTransparent bool, compressLevel png.CompressionLevel, quality int) *ImageToPNGConverter { // 默认背景色为白色 bg := color.RGBA{R: 255, G: 255, B: 255, A: 255} if bgColor != nil { bg = *bgColor } return &ImageToPNGConverter{ Threshold: threshold, Margin: margin, BgColor: bg, DetectColor: detectColor, KeepTransparent: keepTransparent, PNGCompressLevel: compressLevel, Quality: quality, } } // ConvertToPNG 转换图片为PNG格式 func (c *ImageToPNGConverter) convertToPNG(img image.Image, addBackground bool) image.Image { // 先裁剪白边 trimmed := c.trimImage(img) // 检查是否有alpha通道 _, hasAlpha := trimmed.(*image.NRGBA) if !hasAlpha { _, hasAlpha = trimmed.(*image.RGBA) } if hasAlpha { if c.KeepTransparent { // 保持透明 return trimmed } else if addBackground { // 添加背景色 bg := image.NewRGBA(trimmed.Bounds()) draw.Draw(bg, bg.Bounds(), &image.Uniform{C: c.BgColor}, image.Point{}, draw.Src) draw.Draw(bg, bg.Bounds(), trimmed, trimmed.Bounds().Min, draw.Over) return bg } } else { // 非透明图像 if c.KeepTransparent { // 转换为RGBA rgba := image.NewRGBA(trimmed.Bounds()) draw.Draw(rgba, rgba.Bounds(), trimmed, trimmed.Bounds().Min, draw.Src) return rgba } return trimmed } return trimmed } // TrimImage 裁剪图片白边 func (c *ImageToPNGConverter) trimImage(img image.Image) image.Image { borders := c.findBorders(img) // 创建一个新的图像并裁剪 trimmed := imaging.Crop(img, borders) return trimmed } // FindBorders 查找图片的有效边界 func (c *ImageToPNGConverter) findBorders(img image.Image) image.Rectangle { bounds := img.Bounds() width := bounds.Dx() height := bounds.Dy() // 检查图像是否有alpha通道 _, hasAlpha := img.(*image.NRGBA) if !hasAlpha { _, hasAlpha = img.(*image.RGBA) } // 初始化边界 left := width top := height right := 0 bottom := 0 // 查找非背景区域 for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { pixel := img.At(x, y) if !c.isBackgroundColor(pixel, hasAlpha) { if x < left { left = x } if x > right { right = x } if y < top { top = y } if y > bottom { bottom = y } } } } // 如果没有找到非背景区域,返回整个图像 if left > right || top > bottom { return bounds } // 添加边距 left = max(bounds.Min.X, left-c.Margin) top = max(bounds.Min.Y, top-c.Margin) right = min(bounds.Max.X, right+c.Margin+1) bottom = min(bounds.Max.Y, bottom+c.Margin+1) return image.Rect(left, top, right, bottom) } // IsBackgroundColor 判断像素是否为背景色 func (c *ImageToPNGConverter) isBackgroundColor(pixel color.Color, hasAlpha bool) bool { r, g, b, a := pixel.RGBA() // 转换为8位值 r8 := uint8(r >> 8) g8 := uint8(g >> 8) b8 := uint8(b >> 8) a8 := uint8(a >> 8) // 检查透明度 if hasAlpha && a8 < 25 { // 透明度 > 90% return true } // 如果指定了检测颜色 if c.DetectColor != nil { dr, dg, db, _ := c.DetectColor.RGBA() dr8 := uint8(dr >> 8) dg8 := uint8(dg >> 8) db8 := uint8(db >> 8) // threshold 是 int 类型,需要转换为 uint8 比较 threshold8 := uint8(255 - c.Threshold) return absDiff(r8, dr8) <= threshold8 && absDiff(g8, dg8) <= threshold8 && absDiff(b8, db8) <= threshold8 } // 自动检测白色/浅色背景 // 注意:这里的 c.Threshold 是 int,需要转换为 uint8 threshold8 := uint8(c.Threshold) return r8 >= threshold8 && g8 >= threshold8 && b8 >= threshold8 } // 辅助函数 func absDiff(a, b uint8) uint8 { if a > b { return a - b } return b - a } // =================== C 导出函数 ======================= // 检测图片纯白占比 // //export ProcessImage func ProcessImage(jsonConfig *C.char) *C.char { configStr := C.GoString(jsonConfig) var config *Config if err := json.Unmarshal([]byte(configStr), &config); err != nil { return C.CString(fmt.Sprintf("解析 config 失败: %v", err)) } if err := processImage(config); err != nil { return C.CString(fmt.Sprintf("%v", err)) } return C.CString("成功") } // 根据原始图片生成新的白底图片 // //export CreateWhiteBottomCenteredImage func CreateWhiteBottomCenteredImage(jsonConfig *C.char, width, height C.int) *C.char { configStr := C.GoString(jsonConfig) widthInt := int(width) heightInt := int(height) var config *Config if err := json.Unmarshal([]byte(configStr), &config); err != nil { return C.CString(fmt.Sprintf("解析 config 失败: %v", err)) } fileName, err := createWhiteBottomCenteredImage(config, widthInt, heightInt) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } return C.CString(fileName) } // 根据高度生成等比例图片 // //export ResizeToHeightQuality func ResizeToHeightQuality(jsonConfig *C.char, targetHeight C.int) *C.char { configStr := C.GoString(jsonConfig) targetHeightInt := int(targetHeight) var config *Config if err := json.Unmarshal([]byte(configStr), &config); err != nil { return C.CString(fmt.Sprintf("解析 config 失败: %v", err)) } fileName, err := resizeToHeightQuality(config, targetHeightInt) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } return C.CString(fileName) } // 去掉白边并转PNG图片工具 // //export RemoveWhiteBorderAndPNG func RemoveWhiteBorderAndPNG(jsonConfig *C.char) *C.char { configStr := C.GoString(jsonConfig) var config *Config if err := json.Unmarshal([]byte(configStr), &config); err != nil { return C.CString(fmt.Sprintf("解析 config 失败: %v", err)) } fileName, err := removeWhiteBorderAndPNG(config) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } return C.CString(fileName) } // 导出函数:释放C字符串内存 // //export FreeCString func FreeCString(str *C.char) { C.free(unsafe.Pointer(str)) } //func main() { // //}