package main // #include import "C" import ( "encoding/json" "fmt" "github.com/disintegration/imaging" "github.com/makiuchi-d/gozxing" "github.com/makiuchi-d/gozxing/qrcode" "github.com/nfnt/resize" "golang.org/x/image/draw" "image" "image/color" "image/jpeg" _ "image/jpeg" "image/png" _ "image/png" "math" "os" "path/filepath" "strings" "unsafe" ) // Config 配置结构体 type Config struct { OutputDir string // 输出目录路径 FileName string // 文件名 MatchDir string // 满足条件的图片目录名 UnmatchDir string // 不满足条件的图片目录名 EqualHeightDir string // 等高的图片目录名 WhiteDir string // 白色底图的图片目录名 WhiteBorderPngDir string // 去白边转PNG的图片目录名 WhiteHeightZoomDir string // 缩放的图片目录 CropDir string // 裁切的图片目录 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 } whiteHeightZoomPath := filepath.Join(config.OutputDir, config.WhiteHeightZoomDir) if err := os.MkdirAll(whiteHeightZoomPath, 0755); err != nil { return err } cropPath := filepath.Join(config.OutputDir, config.CropDir) if err := os.MkdirAll(cropPath, 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 resizeWTToHeightQuality(config *Config, dsWidth, dsHeight 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) } // 使用Lanczos3算法缩放图片 optimized := resizeImageOptimized(img, dsWidth, dsHeight) // 保存图片到指定目录下 filename := filepath.Base(config.FileName) destPath := filepath.Join(config.OutputDir, config.WhiteHeightZoomDir, filename) err = saveImage(destPath, optimized, format) if err != nil { return "", fmt.Errorf("保存图片失败: %v", err) } return destPath, nil } // 使用Lanczos3算法缩放图片 func resizeImageOptimized(src image.Image, dstWidth, dstHeight int) image.Image { srcBounds := src.Bounds() srcWidth := srcBounds.Dx() srcHeight := srcBounds.Dy() // 创建目标图片 dst := image.NewRGBA(image.Rect(0, 0, dstWidth, dstHeight)) // 计算缩放比例 xScale := float64(srcWidth) / float64(dstWidth) yScale := float64(srcHeight) / float64(dstHeight) // Lanczos3算法半径 radius := 3.0 // 预计算x方向的权重 xWeights := make([][]float64, dstWidth) xIndices := make([][]int, dstWidth) for x := 0; x < dstWidth; x++ { srcX := float64(x) * xScale startX := int(math.Max(0, math.Floor(srcX-radius+0.5))) endX := int(math.Min(float64(srcWidth-1), math.Floor(srcX+radius))) weights := make([]float64, 0, endX-startX+1) indices := make([]int, 0, endX-startX+1) for sx := startX; sx <= endX; sx++ { xDist := float64(sx) + 0.5 - srcX weight := lanczos3(xDist) if weight != 0 { weights = append(weights, weight) indices = append(indices, sx) } } xWeights[x] = weights xIndices[x] = indices } // 处理每一行 for y := 0; y < dstHeight; y++ { srcY := float64(y) * yScale startY := int(math.Max(0, math.Floor(srcY-radius+0.5))) endY := int(math.Min(float64(srcHeight-1), math.Floor(srcY+radius))) // 预计算y方向的权重 yWeights := make([]float64, 0, endY-startY+1) yIndices := make([]int, 0, endY-startY+1) for sy := startY; sy <= endY; sy++ { yDist := float64(sy) + 0.5 - srcY weight := lanczos3(yDist) if weight != 0 { yWeights = append(yWeights, weight) yIndices = append(yIndices, sy) } } // 处理每一列 for x := 0; x < dstWidth; x++ { var rSum, gSum, bSum, aSum, weightSum float64 // 应用预计算的权重 for i, sy := range yIndices { yWeight := yWeights[i] for j, sx := range xIndices[x] { xWeight := xWeights[x][j] weight := xWeight * yWeight // 获取源像素颜色 srcColor := src.At(sx+srcBounds.Min.X, sy+srcBounds.Min.Y) r, g, b, a := srcColor.RGBA() // 累加加权颜色值 rSum += float64(r>>8) * weight gSum += float64(g>>8) * weight bSum += float64(b>>8) * weight aSum += float64(a>>8) * weight weightSum += weight } } // 防止除以零 if weightSum == 0 { weightSum = 1 } // 计算最终颜色值 r := clamp(rSum / weightSum) g := clamp(gSum / weightSum) b := clamp(bSum / weightSum) a := clamp(aSum / weightSum) // 设置目标像素 dst.SetRGBA(x, y, color.RGBA{r, g, b, a}) } } return dst } // 函数计算Lanczos3核函数值 func lanczos3(x float64) float64 { if x == 0 { return 1.0 } if x < -3 || x > 3 { return 0.0 } return (3 * math.Sin(math.Pi*x) * math.Sin(math.Pi*x/3)) / (math.Pi * math.Pi * x * x) } // 将值限制在0-255范围内 func clamp(v float64) uint8 { if v < 0 { return 0 } if v > 255 { return 255 } return uint8(v + 0.5) } // 图片裁切 func cropImage(config *Config, x, y, 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) } fmt.Printf("输入图片: %s (%dx%d, 格式: %s)\n", config.FileName, img.Bounds().Dx(), img.Bounds().Dy(), format) imgFile, err := basicCrop(img, x, y, width, height) if err != nil { return "", err } //// 执行裁切 //var cropped image.Image //if width > 0 && height > 0 { // cropped, err = SmartCrop(img, x, y, width, height int) //} else { // // 如果未指定尺寸,使用中心裁切 // cropped, err = CropCenter(img, config.Width, config.Height) //} // //if err != nil { // return "", err //} // // 保存图片到指定目录下 filename := filepath.Base(config.FileName) destPath := filepath.Join(config.OutputDir, config.CropDir, filename) err = saveImage(destPath, imgFile, format) if err != nil { return "", fmt.Errorf("保存图片失败: %v", err) } return destPath, nil } // basicCrop 基础裁切功能 func basicCrop(src image.Image, x, y, width, height int) (image.Image, error) { srcBounds := src.Bounds() srcWidth := srcBounds.Dx() srcHeight := srcBounds.Dy() // 验证裁切参数 if x < 0 || y < 0 || width <= 0 || height <= 0 { return nil, fmt.Errorf("invalid crop parameters: x=%d, y=%d, width=%d, height=%d", x, y, width, height) } if x >= srcWidth || y >= srcHeight { return nil, fmt.Errorf("crop start point (%d, %d) is outside image bounds (%dx%d)", x, y, srcWidth, srcHeight) } // 调整裁切尺寸以避免超出边界 if x+width > srcWidth { width = srcWidth - x } if y+height > srcHeight { height = srcHeight - y } // 从源图像中裁切 cropped := image.NewRGBA(image.Rect(0, 0, width, height)) for cy := 0; cy < height; cy++ { for cx := 0; cx < width; cx++ { cropped.Set(cx, cy, src.At(x+cx, y+cy)) } } return cropped, nil } //// smartCrop 智能裁切,处理边界和自动调整 -- 暂时没用 //func smartCrop(src image.Image, x, y, width, height int) (image.Image, error) { // srcBounds := src.Bounds() // srcWidth := srcBounds.Dx() // srcHeight := srcBounds.Dy() // // // 如果宽度或高度为0,使用图像的最大可能尺寸 // if width == 0 { // width = srcWidth - x // } // if height == 0 { // height = srcHeight - y // } // // // 调整裁切区域以确保在边界内 // newX := max(0, min(x, srcWidth-1)) // newY := max(0, min(x, srcHeight-1)) // width = max(1, min(width, srcWidth-x)) // height = max(1, min(height, srcHeight-y)) // // // 如果需要保持宽高比,调整裁切区域 // if width > 0 && height > 0 { // currentRatio := float64(width) / float64(height) // originalRatio := float64(srcWidth) / float64(srcHeight) // // if math.Abs(currentRatio-originalRatio) > 0.01 { // // 调整宽度以匹配原始宽高比 // newWidth := int(float64(height) * originalRatio) // if newWidth <= (srcWidth - newX) { // width = newWidth // } else { // // 调整高度以匹配原始宽高比 // newHeight := int(float64(width) / originalRatio) // if newHeight <= (srcHeight - newY) { // height = newHeight // } // } // } // } // // // 执行裁切 // cropped, err := basicCrop(src, newX, newY, width, height) // if err != nil { // return nil, err // } // // return cropped, nil //} //// CropCenter 中心裁切 -- 暂时没用 //func CropCenter(src image.Image, width, height int) (image.Image, error) { // srcBounds := src.Bounds() // srcWidth := srcBounds.Dx() // srcHeight := srcBounds.Dy() // // // 如果裁切尺寸大于原图,返回原图或调整裁切尺寸 // if width >= srcWidth && height >= srcHeight { // return src, nil // } // // // 计算中心裁切的起始点 // x := (srcWidth - width) / 2 // y := (srcHeight - height) / 2 // // // 确保不超出边界 // x = max(0, min(x, srcWidth-width)) // y = max(0, min(y, srcHeight-height)) // // return BasicCrop(src, x, y, width, height) //} // 识别二维码 func scanQRCode(fileName string) (bool, string, error) { file, err := os.Open(fileName) if err != nil { return false, "", fmt.Errorf("打开图片失败: %v", err) } defer file.Close() // 解码图像 img, _, err := image.Decode(file) if err != nil { return false, "", fmt.Errorf("解码图片失败: %v", err) } // 创建二维码读取器 bmp, err := gozxing.NewBinaryBitmapFromImage(img) if err != nil { return false, "", fmt.Errorf("创建位图失败: %v", err) } // 解码二维码 reader := qrcode.NewQRCodeReader() result, err := reader.Decode(bmp, nil) if err != nil { return false, "", analyzeQRCodeError(err) } // 6. 打印二维码内容 fmt.Printf("二维码内容: %s\n", result.GetText()) // 7. 获取二维码位置点 points := result.GetResultPoints() if len(points) < 3 { fmt.Println("未找到足够的定位点") } return true, result.GetText(), nil } // 识别二维码 func scanQRCodeNew(fileName string) (bool, string, error) { file, err := os.Open(fileName) if err != nil { return false, "", fmt.Errorf("打开图片失败: %v", err) } defer file.Close() // 解码图像 img, _, err := image.Decode(file) if err != nil { return false, "", fmt.Errorf("解码图片失败: %v", err) } // 创建二维码读取器 bmp, err := gozxing.NewBinaryBitmapFromImage(img) if err != nil { return false, "", fmt.Errorf("创建位图失败: %v", err) } // 解码二维码 reader := qrcode.NewQRCodeReader() result, err := reader.Decode(bmp, nil) if err != nil { return false, "", analyzeQRCodeError(err) } // 6. 打印二维码内容 fmt.Printf("二维码内容: %s\n", result.GetText()) // 7. 获取二维码位置点 points := result.GetResultPoints() if len(points) < 3 { fmt.Println("未找到足够的定位点") } // 8. 计算二维码边界框 minX, minY := int(points[0].GetX()), int(points[0].GetY()) maxX, maxY := minX, minY for _, point := range points { x, y := int(point.GetX()), int(point.GetY()) if x < minX { minX = x } if x > maxX { maxX = x } if y < minY { minY = y } if y > maxY { maxY = y } } // 9. 添加一些边距(可选) margin := 10 minX -= margin minY -= margin maxX += margin maxY += margin // 确保坐标不超出图片范围 bounds := img.Bounds() if minX < bounds.Min.X { minX = bounds.Min.X } if minY < bounds.Min.Y { minY = bounds.Min.Y } if maxX > bounds.Max.X { maxX = bounds.Max.X } if maxY > bounds.Max.Y { maxY = bounds.Max.Y } // 10. 创建裁剪区域 qrRect := image.Rect(minX, minY, maxX, maxY) fmt.Printf("二维码位置: %v\n", qrRect) // 11. 创建新图片并复制二维码区域 qrImg := image.NewRGBA(qrRect) draw.Draw(qrImg, qrImg.Bounds(), img, qrRect.Min, draw.Src) // 12. 保存二维码图片 outputFile, err := os.Create("image/qrcode.png") if err != nil { panic(err) } defer outputFile.Close() err = png.Encode(outputFile, qrImg) if err != nil { panic(err) } return true, result.GetText(), nil } // 图像预处理 func preprocessImage(img image.Image) image.Image { // 转为灰度图 gray := imaging.Grayscale(img) // 增强对比度 enhanced := imaging.AdjustContrast(gray, 20) // 高斯模糊去噪 blurred := imaging.Blur(enhanced, 1.0) // 自适应二值化 binary := adaptiveThreshold(blurred) return binary } // 二值化处理 func adaptiveThreshold(img image.Image) image.Image { bounds := img.Bounds() dst := image.NewGray(bounds) // 局部自适应阈值 blockSize := 15 c := 2.0 for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { // 计算局部均值 sum := 0.0 count := 0 for dy := -blockSize / 2; dy <= blockSize/2; dy++ { for dx := -blockSize / 2; dx <= blockSize/2; dx++ { nx, ny := x+dx, y+dy if nx >= bounds.Min.X && nx < bounds.Max.X && ny >= bounds.Min.Y && ny < bounds.Max.Y { r, _, _, _ := img.At(nx, ny).RGBA() sum += float64(r >> 8) count++ } } } localMean := sum / float64(count) r, _, _, _ := img.At(x, y).RGBA() gray := float64(r >> 8) if gray > localMean-c { dst.SetGray(x, y, color.Gray{Y: 255}) } else { dst.SetGray(x, y, color.Gray{Y: 0}) } } } return dst } // 分析二维码错误类型 func analyzeQRCodeError(err error) error { if err == nil { return nil } // 检查不同类型的错误 switch e := err.(type) { case gozxing.ChecksumException: return fmt.Errorf("二维码校验失败(可能部分损坏或被遮挡): %v", e) case gozxing.FormatException: return fmt.Errorf("二维码格式错误(可能不是有效的二维码): %v", e) case gozxing.NotFoundException: return fmt.Errorf("二维码格式错误: %v", e) default: return fmt.Errorf("解码失败: %v", err) } } // 生成二维码 func generateQRCode(content string, width int, height int, fileName string) (string, error) { // 创建 QR 码写入器 writer := qrcode.NewQRCodeWriter() // 设置编码参数 hints := make(map[gozxing.EncodeHintType]interface{}) // 纠错级别设置 hints[gozxing.EncodeHintType_ERROR_CORRECTION] = "H" // 字符集 hints[gozxing.EncodeHintType_CHARACTER_SET] = "UTF-8" // 边距 hints[gozxing.EncodeHintType_MARGIN] = 4 // 生成二维码 bitMatrix, err := writer.Encode(content, gozxing.BarcodeFormat_QR_CODE, width, height, hints) if err != nil { return "", fmt.Errorf("生成二维码失败: %v", err) } // 创建图像 img := image.NewRGBA(image.Rect(0, 0, width, height)) // 白色背景 for y := 0; y < height; y++ { for x := 0; x < width; x++ { img.Set(x, y, color.White) } } // 黑色二维码点 for y := 0; y < height; y++ { for x := 0; x < width; x++ { if bitMatrix.Get(x, y) { img.Set(x, y, color.Black) } } } // 保存文件 file, err := os.Create(fileName) if err != nil { return "", fmt.Errorf("保存二维码失败: %v", err) } defer file.Close() png.Encode(file, img) return fmt.Sprintf("生成二维码成功: %v", fileName), nil } // =================== 辅助函数 ======================= // 辅助函数 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) } // ResizeWTToHeightQuality 图片缩放 // //export ResizeWTToHeightQuality func ResizeWTToHeightQuality(jsonConfig *C.char, dsWidth, dsHeight C.int) *C.char { configStr := C.GoString(jsonConfig) dsWidthStr := int(dsWidth) dsHeightStr := int(dsHeight) var config *Config if err := json.Unmarshal([]byte(configStr), &config); err != nil { return C.CString(fmt.Sprintf("解析 config 失败: %v", err)) } fileName, err := resizeWTToHeightQuality(config, dsWidthStr, dsHeightStr) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } return C.CString(fileName) } // CropImage 图片裁切 // //export CropImage func CropImage(jsonConfig *C.char, x, y, width, height C.int) *C.char { configStr := C.GoString(jsonConfig) xStr := int(x) yStr := int(y) widthStr := int(width) heightStr := int(height) var config *Config if err := json.Unmarshal([]byte(configStr), &config); err != nil { return C.CString(fmt.Sprintf("解析 config 失败: %v", err)) } fileName, err := cropImage(config, xStr, yStr, widthStr, heightStr) 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)) } // main 函数是必需的,即使为空 //func main() { //}