daShangDao_kfzgw-info/image/image.go
2025-12-31 17:56:22 +08:00

1235 lines
30 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
// #include <stdlib.h>
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() {
//}