daShangDao_kfzgw-info/image/imageTool.go
2025-12-22 19:09:56 +08:00

645 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"])
// }
// }
// }
// }
//}