package main //import ( // "fmt" // "image" // "image/color" // "image/draw" // "image/png" // "io/ioutil" // "os" // "path/filepath" // "strings" // "sync" // "time" // // "github.com/disintegration/imaging" //) // ////// ImageToPNGConverter 图片去白边并转为PNG ////type ImageToPNGConverter struct { //// Threshold int //// Margin int //// BgColor color.RGBA //// DetectColor *color.RGBA //// KeepTransparent bool //// PNGCompressLevel png.CompressionLevel //// Quality int ////} // //// 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, // } //} // //// 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 //} // //// 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) //} // //// TrimImage 裁剪图片白边 //func (c *ImageToPNGConverter) TrimImage(img image.Image) image.Image { // borders := c.FindBorders(img) // // // 创建一个新的图像并裁剪 // trimmed := imaging.Crop(img, borders) // return trimmed //} // //// 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 //} // //// ProcessImageFile 处理单个图片文件 //func (c *ImageToPNGConverter) ProcessImageFile(inputPath, outputPath string) map[string]interface{} { // result := map[string]interface{}{ // "success": false, // "input_path": inputPath, // "output_path": outputPath, // } // // // 打开图片文件 // file, err := os.Open(inputPath) // if err != nil { // result["error"] = err.Error() // result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err) // return result // } // defer file.Close() // // // 解码图像 // img, format, err := image.Decode(file) // if err != nil { // result["error"] = err.Error() // result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err) // return result // } // // // 获取原始信息 // origBounds := img.Bounds() // origSize := origBounds.Size() // origArea := origSize.X * origSize.Y // // // 转换为PNG // resultImg := c.ConvertToPNG(img, true) // // // 获取处理后的信息 // newBounds := resultImg.Bounds() // newSize := newBounds.Size() // newArea := newSize.X * newSize.Y // // // 计算尺寸减少比例 // sizeReduction := 0.0 // if origArea > 0 { // sizeReduction = 1 - float64(newArea)/float64(origArea) // } // // // 获取原始文件大小 // fileInfo, _ := os.Stat(inputPath) // origFileSize := fileInfo.Size() // // // 保存为PNG // outputFile, err := os.Create(outputPath) // if err != nil { // result["error"] = err.Error() // result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err) // return result // } // defer outputFile.Close() // // encoder := png.Encoder{CompressionLevel: c.PNGCompressLevel} // err = encoder.Encode(outputFile, resultImg) // if err != nil { // result["error"] = err.Error() // result["message"] = fmt.Sprintf("失败: %s - %s", filepath.Base(inputPath), err) // return result // } // // // 获取新文件大小 // newFileInfo, _ := os.Stat(outputPath) // newFileSize := newFileInfo.Size() // // // 计算文件大小变化 // fileSizeChange := 0.0 // if origFileSize > 0 { // fileSizeChange = float64(newFileSize) / float64(origFileSize) // } // // result["success"] = true // result["orig_format"] = format // result["orig_size"] = origSize // result["new_size"] = newSize // result["size_reduction"] = sizeReduction // result["orig_file_size"] = origFileSize // result["new_file_size"] = newFileSize // result["file_size_change"] = fileSizeChange // result["message"] = fmt.Sprintf("成功: %s (%s→PNG, %dx%d→%dx%d)", // filepath.Base(inputPath), format, origSize.X, origSize.Y, newSize.X, newSize.Y) // // return result //} // //// BatchPNGConverter 批量PNG转换器 //type BatchPNGConverter struct { // converter *ImageToPNGConverter // outputDir string // statistics map[string]interface{} // mu sync.Mutex //} // //// NewBatchPNGConverter 创建批量转换器 //func NewBatchPNGConverter(converter *ImageToPNGConverter, outputDir string) *BatchPNGConverter { // os.MkdirAll(outputDir, 0755) // return &BatchPNGConverter{ // converter: converter, // outputDir: outputDir, // statistics: make(map[string]interface{}), // } //} // //// GetOutputPath 生成输出路径 //func (b *BatchPNGConverter) GetOutputPath(inputPath, suffix string) string { // baseName := filepath.Base(inputPath) // ext := filepath.Ext(baseName) // nameWithoutExt := strings.TrimSuffix(baseName, ext) // // outputFilename := nameWithoutExt + suffix + ".png" // return filepath.Join(b.outputDir, outputFilename) //} // //// ProcessSingle 处理单张图片 //func (b *BatchPNGConverter) ProcessSingle(inputPath, outputPath, suffix string) map[string]interface{} { // if outputPath == "" { // outputPath = b.GetOutputPath(inputPath, suffix) // } // // // 确保输出目录存在 // os.MkdirAll(filepath.Dir(outputPath), 0755) // // return b.converter.ProcessImageFile(inputPath, outputPath) //} // //// ProcessBatch 批量处理图片 //func (b *BatchPNGConverter) ProcessBatch(inputPaths []string, suffix string, maxWorkers int) map[string]interface{} { // startTime := time.Now() // // stats := map[string]interface{}{ // "total": len(inputPaths), // "success": 0, // "failed": 0, // "total_size_reduction": 0.0, // "total_file_size_orig": int64(0), // "total_file_size_new": int64(0), // "results": []map[string]interface{}{}, // } // // // 使用工作池 // var wg sync.WaitGroup // semaphore := make(chan struct{}, maxWorkers) // resultsChan := make(chan map[string]interface{}, len(inputPaths)) // // for _, inputPath := range inputPaths { // wg.Add(1) // go func(path string) { // defer wg.Done() // semaphore <- struct{}{} // defer func() { <-semaphore }() // // outputPath := b.GetOutputPath(path, suffix) // result := b.ProcessSingle(path, outputPath, suffix) // resultsChan <- result // }(inputPath) // } // // // 收集结果 // go func() { // wg.Wait() // close(resultsChan) // }() // // completed := 0 // for result := range resultsChan { // completed++ // b.mu.Lock() // stats["results"] = append(stats["results"].([]map[string]interface{}), result) // // if result["success"].(bool) { // stats["success"] = stats["success"].(int) + 1 // stats["total_size_reduction"] = stats["total_size_reduction"].(float64) + result["size_reduction"].(float64) // stats["total_file_size_orig"] = stats["total_file_size_orig"].(int64) + result["orig_file_size"].(int64) // stats["total_file_size_new"] = stats["total_file_size_new"].(int64) + result["new_file_size"].(int64) // } else { // stats["failed"] = stats["failed"].(int) + 1 // } // b.mu.Unlock() // // fmt.Printf("[%d/%d] %s\n", completed, len(inputPaths), result["message"]) // } // // // 计算统计信息 // stats["elapsed_time"] = time.Since(startTime).Seconds() // if stats["success"].(int) > 0 { // stats["avg_size_reduction"] = stats["total_size_reduction"].(float64) / float64(stats["success"].(int)) // if stats["total_file_size_orig"].(int64) > 0 { // stats["total_file_size_change"] = float64(stats["total_file_size_new"].(int64)) / float64(stats["total_file_size_orig"].(int64)) // } else { // stats["total_file_size_change"] = 1.0 // } // } // // return stats //} // //// FindImageFiles 查找目录中的图片文件 //func FindImageFiles(directory string, recursive bool) []string { // supportedExtensions := map[string]bool{ // ".jpg": true, // ".jpeg": true, // ".png": true, // ".gif": true, // ".bmp": true, // ".tif": true, // ".tiff": true, // ".webp": true, // ".jfif": true, // ".ico": true, // ".ppm": true, // ".pgm": true, // ".pbm": true, // ".pnm": true, // } // // var imagePaths []string // // if recursive { // filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { // if err != nil { // return err // } // if !info.IsDir() { // ext := strings.ToLower(filepath.Ext(path)) // if supportedExtensions[ext] { // imagePaths = append(imagePaths, path) // } // } // return nil // }) // } else { // files, err := ioutil.ReadDir(directory) // if err != nil { // return imagePaths // } // // for _, file := range files { // if !file.IsDir() { // ext := strings.ToLower(filepath.Ext(file.Name())) // if supportedExtensions[ext] { // imagePaths = append(imagePaths, filepath.Join(directory, file.Name())) // } // } // } // } // // return imagePaths //} // //// PrintBanner 打印程序标题 //func PrintBanner() { // banner := ` //╔══════════════════════════════════════════════════╗ //║ 图片去白边转PNG工具 v1.0 ║ //║ Image White Border Removal & PNG Converter ║ //╚══════════════════════════════════════════════════╝ //` // fmt.Println(banner) //} // //// PrintSummary 打印处理总结 //func PrintSummary(stats map[string]interface{}) { // fmt.Println("\n" + strings.Repeat("=", 60)) // fmt.Println("📊 处理总结") // fmt.Println(strings.Repeat("=", 60)) // fmt.Printf("📁 总共处理: %d 张图片\n", stats["total"]) // fmt.Printf("✅ 成功: %d 张\n", stats["success"]) // fmt.Printf("❌ 失败: %d 张\n", stats["failed"]) // // if stats["success"].(int) > 0 { // fmt.Printf("⏱️ 耗时: %.2f 秒\n", stats["elapsed_time"].(float64)) // // if avgReduction, ok := stats["avg_size_reduction"]; ok { // fmt.Printf("📐 平均尺寸减少: %.1f%%\n", avgReduction.(float64)*100) // } // // if change, ok := stats["total_file_size_change"]; ok { // changeVal := change.(float64) // if changeVal < 1 { // fmt.Printf("💾 总文件大小减少: %.1f%%\n", (1-changeVal)*100) // } else if changeVal > 1 { // fmt.Printf("💾 总文件大小增加: %.1f%%\n", (changeVal-1)*100) // } else { // fmt.Println("💾 总文件大小基本不变") // } // } // } // fmt.Println(strings.Repeat("=", 60)) //} // //// 辅助函数 //func absDiff(a, b uint8) uint8 { // if a > b { // return a - b // } // return b - a //} // //func max(a, b int) int { // if a > b { // return a // } // return b //} // //func min(a, b int) int { // if a < b { // return a // } // return b //} // //func main() { // // 直接设置参数值,不需要命令行输入 // // // ============ 参数配置区 ============ // // 基础参数 // inputPath := "D:\\isbn_images\\result\\matched\\9771671688095.jpg" // 输入文件或目录路径 // outputPath := "D:\\isbn_images\\result\\matched\\output.png" // 输出文件路径(单文件模式) // outputDir := "D:\\isbn_images\\result\\matched\\" // 输出目录路径(批量模式) // suffix := "_trimmed" // 输出文件名后缀 // // // 处理参数 // threshold := 240 // 背景检测阈值 (0-255) // margin := 0 // 保留边距像素 // transparent := false // 保持透明背景 // compressLevel := 6 // PNG压缩级别 (0-9) // // // 批量处理参数 // recursive := false // 递归处理子目录 // jobs := 4 // 并行处理数 // force := true // 覆盖已存在的输出文件(设置为true不询问) // verbose := true // 显示详细处理信息 // showBanner := true // 显示标题横幅 // // ============ 参数配置结束 ============ // // if showBanner { // PrintBanner() // } // // // 创建转换器 // compressionLevel := png.DefaultCompression // switch { // case compressLevel <= 0: // compressionLevel = png.NoCompression // case compressLevel >= 9: // compressionLevel = png.BestCompression // default: // // 使用默认压缩级别 // } // // converter := NewImageToPNGConverter( // threshold, // margin, // &color.RGBA{R: 255, G: 255, B: 255, A: 255}, // nil, // transparent, // compressionLevel, // 95, // ) // // batchConverter := NewBatchPNGConverter(converter, outputDir) // // // 检查输入路径 // info, err := os.Stat(inputPath) // if err != nil { // fmt.Printf("❌ 错误: 路径不存在 - %s\n", inputPath) // return // } // // if !info.IsDir() { // // 单文件模式 // fmt.Printf("📄 处理单文件: %s\n", inputPath) // // // 如果outputPath为空,则生成默认输出路径 // if outputPath == "" { // outputPath = batchConverter.GetOutputPath(inputPath, suffix) // } // // // 检查输出文件是否存在,如果force为false则询问 // if _, err := os.Stat(outputPath); err == nil && !force { // fmt.Printf("⚠️ 警告: 输出文件已存在 - %s\n", outputPath) // fmt.Println("已设置force=true,直接覆盖") // } // // result := batchConverter.ProcessSingle(inputPath, outputPath, suffix) // // if result["success"].(bool) { // fmt.Printf("\n✅ %s\n", result["message"]) // fmt.Printf("💾 输出文件: %s\n", result["output_path"]) // // if reduction, ok := result["size_reduction"]; ok { // reductionVal := reduction.(float64) // if reductionVal > 0 { // fmt.Printf("📐 尺寸减少: %.1f%%\n", reductionVal*100) // } else if reductionVal < 0 { // fmt.Printf("📐 尺寸增加: %.1f%%\n", -reductionVal*100) // } // } // // if change, ok := result["file_size_change"]; ok { // changeVal := change.(float64) // if changeVal < 1 { // fmt.Printf("💿 文件大小减少: %.1f%%\n", (1-changeVal)*100) // } else if changeVal > 1 { // fmt.Printf("💿 文件大小增加: %.1f%%\n", (changeVal-1)*100) // } // } // } else { // fmt.Printf("\n❌ %s\n", result["message"]) // } // } else { // // 批量模式 // fmt.Printf("📁 扫描目录: %s\n", inputPath) // imageFiles := FindImageFiles(inputPath, recursive) // // if len(imageFiles) == 0 { // fmt.Println("未找到支持的图片文件") // return // } // // fmt.Printf("找到 %d 张图片\n", len(imageFiles)) // // // 检查输出目录,如果force为false则询问 // if _, err := os.Stat(outputDir); err == nil && !force { // files, _ := ioutil.ReadDir(outputDir) // if len(files) > 0 { // fmt.Printf("⚠️ 警告: 输出目录不为空 - %s\n", outputDir) // fmt.Println("已设置force=true,直接继续处理") // } // } // // fmt.Printf("📂 输出目录: %s\n", outputDir) // fmt.Printf("⚡ 并行处理: %d 个线程\n", jobs) // fmt.Println(strings.Repeat("-", 60)) // // // 批量处理 // stats := batchConverter.ProcessBatch(imageFiles, suffix, jobs) // // // 打印总结 // PrintSummary(stats) // // // 显示失败详情 // if stats["failed"].(int) > 0 && verbose { // fmt.Println("\n❌ 失败详情:") // for _, result := range stats["results"].([]map[string]interface{}) { // if !result["success"].(bool) { // fmt.Printf(" %s: %s\n", // filepath.Base(result["input_path"].(string)), // result["error"]) // } // } // } // } //}