package main // #include import "C" import ( "encoding/json" "fmt" "github.com/boombuler/barcode" "github.com/boombuler/barcode/code128" "github.com/boombuler/barcode/code39" "github.com/boombuler/barcode/ean" "github.com/disintegration/imaging" "github.com/fogleman/gg" "github.com/golang/freetype" "github.com/golang/freetype/truetype" "github.com/makiuchi-d/gozxing" "github.com/makiuchi-d/gozxing/qrcode" "github.com/nfnt/resize" "golang.org/x/image/draw" "golang.org/x/image/math/fixed" "image" "image/color" "image/jpeg" _ "image/jpeg" "image/png" _ "image/png" "io/ioutil" "math" "os" "path/filepath" "runtime" "strconv" "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 } // 保存图片到指定目录下 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 } // 识别二维码 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 simpleAutoWrap(text string, maxCharsPerLine int) ([]string, bool) { var lines []string var currentLine strings.Builder charCount := 0 for _, r := range text { // 换行符处理 if r == '\n' { if currentLine.Len() > 0 { lines = append(lines, currentLine.String()) currentLine.Reset() charCount = 0 } continue } // 计算字符宽度(中文算2个字符,英文算1个) charWidth := 1 if r >= 0x4E00 && r <= 0x9FFF { // 中文范围 charWidth = 2 } else if r >= 0x3000 && r <= 0x303F { // 中文标点 charWidth = 2 } // 检查是否需要换行 if charCount+charWidth > maxCharsPerLine && currentLine.Len() > 0 { lines = append(lines, currentLine.String()) currentLine.Reset() charCount = 0 } currentLine.WriteRune(r) charCount += charWidth } if currentLine.Len() > 0 { lines = append(lines, currentLine.String()) } return lines, false // 返回false表示所有内容都已处理 } // 创建带中文字体的文本图片,支持超出部分显示... // text 文本, width, height 宽度高度, fontSize 文字大小, outputPath 输入路径 func createChineseTextImage(text string, width, height int, fontSize float64, outputPath string) (string, error) { // 获取字体路径 fontPath := getDefaultFontPath() if fontPath == "" { return "", fmt.Errorf("未找到系统字体文件,请手动指定字体路径") } // 读取字体文件 fontBytes, err := ioutil.ReadFile(fontPath) if err != nil { return "", fmt.Errorf("读取字体文件失败: %v", err) } // 解析字体 f, err := truetype.Parse(fontBytes) if err != nil { return "", fmt.Errorf("解析字体失败: %v", err) } // 创建图片 img := image.NewRGBA(image.Rect(0, 0, width, height)) // 白色背景 white := color.RGBA{255, 255, 255, 255} for y := 0; y < height; y++ { for x := 0; x < width; x++ { img.Set(x, y, white) } } // 创建freetype上下文 c := freetype.NewContext() c.SetDPI(72) c.SetFont(f) c.SetFontSize(fontSize) c.SetClip(img.Bounds()) c.SetDst(img) c.SetSrc(image.NewUniform(color.Black)) // 计算可用的文本宽度(左右各留50像素边距) availableWidth := width - 100 // 根据字体大小计算每行大约可以显示多少个字符 charsPerLine := int(float64(availableWidth) / fontSize * 1.7) if charsPerLine < 10 { charsPerLine = 10 } // 计算最大可显示行数 // X坐标:从左侧20像素开始 // Y坐标:从顶部20像素 + 字体高度 lineSpacing := int(c.PointToFixed(fontSize*1.5) >> 6) topMargin := 50 + int(c.PointToFixed(fontSize)>>6) bottomMargin := 50 maxLines := (height - topMargin - bottomMargin) / lineSpacing if maxLines <= 0 { maxLines = 1 } // 使用简单的自动换行,获取所有行 allLines, _ := simpleAutoWrap(text, charsPerLine) // 检查是否有超出图片的内容 hasMore := len(allLines) > maxLines displayLines := allLines if hasMore { // 只显示前 maxLines-1 行,最后一行添加 "..." displayLines = allLines[:maxLines-1] // 获取最后一行文本,并确保能显示 "..." lastLine := allLines[maxLines-1] lastLineChars := 0 var truncatedLine strings.Builder for _, r := range lastLine { // 计算字符宽度 charWidth := 1 if r >= 0x4E00 && r <= 0x9FFF { charWidth = 2 } else if r >= 0x3000 && r <= 0x303F { charWidth = 2 } // 检查是否还能添加字符(留出3个字符给"...") if lastLineChars+charWidth > charsPerLine-3 { break } truncatedLine.WriteRune(r) lastLineChars += charWidth } // 添加 "..." truncatedText := truncatedLine.String() + " ..." displayLines = append(displayLines, truncatedText) } else { // 如果内容不多,直接显示所有行 displayLines = allLines if len(displayLines) > maxLines { displayLines = displayLines[:maxLines] } } // 设置起始位置 startX := 50 startY := topMargin // 绘制每行文字 pt := freetype.Pt(startX, startY) for i, line := range displayLines { // 确保不会超出图片底部 if i*lineSpacing > height-bottomMargin { break } // 绘制文字 _, err = c.DrawString(line, pt) if err != nil { return "", fmt.Errorf("绘制文字失败: %v", err) } // 移动到下一行 pt.Y += c.PointToFixed(fontSize * 1.5) } // 保存图片 file, err := os.Create(outputPath) if err != nil { return "", fmt.Errorf("创建文件失败: %v", err) } defer file.Close() err = png.Encode(file, img) if err != nil { return "", fmt.Errorf("编码并写入失败: %v", err) } return outputPath, nil } // 获取系统字体路径 func getDefaultFontPath() string { switch runtime.GOOS { case "windows": paths := []string{ "C:/Windows/Fonts/simhei.ttf", // 黑体 "C:/Windows/Fonts/simsun.ttc", // 宋体 "C:/Windows/Fonts/msyh.ttc", // 微软雅黑 } for _, path := range paths { if _, err := os.Stat(path); err == nil { return path } } case "darwin": return "/System/Library/Fonts/PingFang.ttc" case "linux": paths := []string{ "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", } for _, path := range paths { if _, err := os.Stat(path); err == nil { return path } } } return "" } // 绘制中文文本(支持自动换行) func drawChineseText(img *image.RGBA, title, author, publisher string) error { // 获取字体路径 fontPath := getDefaultFontPath() if fontPath == "" { return fmt.Errorf("未找到系统字体文件,请手动指定字体路径") } // 读取字体文件 fontBytes, err := ioutil.ReadFile(fontPath) if err != nil { return fmt.Errorf("读取字体文件失败: %v", err) } // 解析字体 fontObj, err := truetype.Parse(fontBytes) if err != nil { return fmt.Errorf("解析字体失败: %v", err) } // 创建freetype上下文 c := freetype.NewContext() c.SetDPI(72) c.SetFont(fontObj) c.SetSrc(image.NewUniform(color.Black)) c.SetClip(img.Bounds()) c.SetDst(img) // 定义绘制区域的宽度 maxWidth := 400 // 可根据需要调整 // 1. 绘制书名(使用较大字体,支持多行) c.SetFontSize(45) titleLines := wrapText(title, fontObj, 45, maxWidth) // 限制书名最多显示3行 if len(titleLines) > 3 { titleLines = titleLines[:3] } titleY := 250 // 起始Y坐标 titleLineHeight := 60 // 行高 for i, line := range titleLines { lineWidth := calculateStringWidth(line, fontObj, 45) pt := freetype.Pt((800-int(lineWidth))/2+30, titleY+i*titleLineHeight) if _, err := c.DrawString(line, pt); err != nil { return fmt.Errorf("绘制书名失败: %v", err) } } // 2. 绘制作者(使用中等字体,支持多行) c.SetFontSize(28) authorLines := wrapText(author, fontObj, 28, maxWidth) // 限制作者最多显示2行 if len(authorLines) > 2 { authorLines = authorLines[:2] } authorY := 420 // 起始Y坐标 authorLineHeight := 40 // 行高 for i, line := range authorLines { lineWidth := calculateStringWidth(line, fontObj, 28) pt := freetype.Pt((800-int(lineWidth))/2+30, authorY+i*authorLineHeight) if _, err := c.DrawString(line, pt); err != nil { return fmt.Errorf("绘制作者失败: %v", err) } } // 3. 绘制出版社(使用中等字体,支持多行) c.SetFontSize(18) publisherLines := wrapText(publisher, fontObj, 18, maxWidth) // 限制出版社最多显示2行 if len(publisherLines) > 1 { publisherLines = publisherLines[:1] } publisherY := 700 // 起始Y坐标 publisherLineHeight := 30 // 行高 for i, line := range publisherLines { lineWidth := calculateStringWidth(line, fontObj, 18) pt := freetype.Pt((800-int(lineWidth))/2+30, publisherY+i*publisherLineHeight) if _, err := c.DrawString(line, pt); err != nil { return fmt.Errorf("绘制出版社失败: %v", err) } } // 4. 绘制右下角文字 err = drawBottomRightText(img, fontObj) if err != nil { return fmt.Errorf("绘制右下角文字失败: %v", err) } return nil } // 绘制右下角文字 func drawBottomRightText(img *image.RGBA, fontObj *truetype.Font) error { // 设置文字内容 line1 := "此为实例图片" line2 := "联系客服获取实图" // 设置字体大小 fontSize := 8.0 // 计算右边界距 rightMargin := 10 // 距离右边界的像素 bottomMargin := 10 // 距离底部的像素 // 计算行高 lineHeight := int(fontSize * 1.5) // 创建freetype上下文 c := freetype.NewContext() c.SetDPI(72) c.SetFont(fontObj) c.SetClip(img.Bounds()) c.SetDst(img) // 计算第二行(最后一行)的位置 line2Width := calculateStringWidth(line2, fontObj, fontSize) x2 := img.Bounds().Dx() - line2Width - rightMargin y2 := img.Bounds().Dy() - bottomMargin // 计算第一行的位置 line1Width := calculateStringWidth(line1, fontObj, fontSize) x1 := img.Bounds().Dx() - line1Width - rightMargin y1 := y2 - lineHeight // 设置字体大小 c.SetFontSize(fontSize) // 方法1:多层绘制实现描边效果 strokeRadius := 1.0 // 描边半径 // 绘制描边(灰色,8个方向) strokeColor := color.RGBA{128, 128, 128, 150} // 灰色 c.SetSrc(image.NewUniform(strokeColor)) // 为第二行绘制描边 offsets := []struct{ dx, dy float64 }{ {-strokeRadius, -strokeRadius}, {0, -strokeRadius}, {strokeRadius, -strokeRadius}, {-strokeRadius, 0}, {strokeRadius, 0}, {-strokeRadius, strokeRadius}, {0, strokeRadius}, {strokeRadius, strokeRadius}, } for _, offset := range offsets { pt2 := freetype.Pt(int(float64(x2)+offset.dx), int(float64(y2)+offset.dy)) if _, err := c.DrawString(line2, pt2); err != nil { return fmt.Errorf("绘制第二行描边失败: %v", err) } pt1 := freetype.Pt(int(float64(x1)+offset.dx), int(float64(y1)+offset.dy)) if _, err := c.DrawString(line1, pt1); err != nil { return fmt.Errorf("绘制第一行描边失败: %v", err) } } // 绘制白色文字(覆盖在中间) textColor := color.RGBA{255, 255, 255, 255} // 白色 c.SetSrc(image.NewUniform(textColor)) // 绘制第二行文字 pt2 := freetype.Pt(x2, y2) if _, err := c.DrawString(line2, pt2); err != nil { return fmt.Errorf("绘制第二行文字失败: %v", err) } // 绘制第一行文字 pt1 := freetype.Pt(x1, y1) if _, err := c.DrawString(line1, pt1); err != nil { return fmt.Errorf("绘制第一行文字失败: %v", err) } return nil } // 文本换行函数 func wrapText(text string, fontObj *truetype.Font, fontSize float64, maxWidth int) []string { var lines []string var currentLine string var currentWidth int for _, ch := range text { char := string(ch) charWidth := calculateStringWidth(char, fontObj, fontSize) // 如果当前字符是换行符,直接换行 if char == "\n" { if currentLine != "" { lines = append(lines, currentLine) } currentLine = "" currentWidth = 0 continue } // 如果加上当前字符会超出宽度,开始新行 if currentWidth+charWidth > maxWidth && currentLine != "" { lines = append(lines, currentLine) currentLine = char currentWidth = charWidth } else { currentLine += char currentWidth += charWidth } } // 添加最后一行 if currentLine != "" { lines = append(lines, currentLine) } return lines } // 计算字符串宽度 func calculateStringWidth(text string, fontObj *truetype.Font, fontSize float64) int { width := 0 for _, ch := range text { idx := fontObj.Index(ch) horizAdvance := fontObj.HMetric(fixed.Int26_6(fontSize), idx).AdvanceWidth width += int(horizAdvance) } return width } // 加载PNG图片 func loadPNG(filename string) (*image.RGBA, error) { file, err := os.Open(filename) if err != nil { return nil, err } defer file.Close() img, err := png.Decode(file) if err != nil { return nil, err } // 转换为RGBA rgba := image.NewRGBA(img.Bounds()) for y := 0; y < img.Bounds().Dy(); y++ { for x := 0; x < img.Bounds().Dx(); x++ { rgba.Set(x, y, img.At(x, y)) } } return rgba, nil } // 保存为PNG文件 func savePNG(img image.Image, filename string) error { file, err := os.Create(filename) if err != nil { return err } defer file.Close() return png.Encode(file, img) } const ( CODE128 = "code128" EAN13 = "ean13" CODE39 = "code39" ) // 根据类型生成条形码 func generateBarcode(barcodeType, content, filename string) (string, error) { switch barcodeType { case CODE128: return generateCode128(content, filename) case EAN13: return generateEAN13(content, filename) case CODE39: return generateCode39(content, filename) } return "", fmt.Errorf("条形码类型不存在: %s", barcodeType) } // 生成Code128条形码 func generateCode128(content, filename string) (string, error) { // 创建条形码 code, err := code128.Encode(content) if err != nil { return "", fmt.Errorf("创建条形码失败: %v", err) } // 缩放条形码尺寸 scaledCode, err := barcode.Scale(code, 250, 85) if err != nil { return "", fmt.Errorf("缩放条形码尺寸失败: %v", err) } // 创建带文字的图像 imgWidth := 250 imgHeight := 110 // 条形码高度 + 文字区域高度 dc := gg.NewContext(imgWidth, imgHeight) // 设置白色背景 dc.SetColor(color.White) dc.Clear() dc.DrawImage(scaledCode, 0, 10) fontPath := getDefaultFontPath() if fontPath == "" { return "", fmt.Errorf("未找到系统字体文件,请手动指定字体路径") } // 设置文字属性 if err := dc.LoadFontFace("/System/Library/Fonts/Helvetica.ttc", 14); err != nil { // 如果字体文件不存在,使用默认字体 dc.LoadFontFace("Arial.ttf", 14) } dc.SetColor(color.Black) // 在条形码下方居中绘制文字0 textY := float64(85 + 20) // 条形码高度100 + 20像素间距 textWidth, _ := dc.MeasureString(content) textX := (float64(imgWidth) - textWidth) / 2 dc.DrawString(content, textX, textY) // 保存图像 err = dc.SavePNG(filename) if err != nil { return "", fmt.Errorf("保存图像失败: %v", err) } return filename, nil } // 生成EAN13条形码 func generateEAN13(content, filename string) (string, error) { eanCode, err := ean.Encode(content) if err != nil { return "", fmt.Errorf("创建条形码失败: %v", err) } scaledEanCode, err := barcode.Scale(eanCode, 250, 85) if err != nil { return "", fmt.Errorf("缩放条形码尺寸失败: %v", err) } // 创建带文字的图像 imgWidth := 250 imgHeight := 110 // 条形码高度 + 文字区域高度 dc := gg.NewContext(imgWidth, imgHeight) // 设置白色背景 dc.SetColor(color.White) dc.Clear() dc.DrawImage(scaledEanCode, 0, 10) fontPath := getDefaultFontPath() if fontPath == "" { return "", fmt.Errorf("未找到系统字体文件,请手动指定字体路径") } // 设置文字属性 if err := dc.LoadFontFace("/System/Library/Fonts/Helvetica.ttc", 14); err != nil { // 如果字体文件不存在,使用默认字体 dc.LoadFontFace("Arial.ttf", 14) } dc.SetColor(color.Black) // 在条形码下方居中绘制文字0 textY := float64(85 + 20) // 条形码高度100 + 20像素间距 textWidth, _ := dc.MeasureString(content) textX := (float64(imgWidth) - textWidth) / 2 dc.DrawString(content, textX, textY) // 保存图像 err = dc.SavePNG(filename) if err != nil { return "", fmt.Errorf("保存图像失败: %v", err) } return filename, nil } // 生成Code39 func generateCode39(content, filename string) (string, error) { code, err := code39.Encode(content, true, true) if err != nil { return "", fmt.Errorf("创建条形码失败: %v", err) } scaled, err := barcode.Scale(code, 250, 85) if err != nil { return "", fmt.Errorf("缩放条形码尺寸失败: %v", err) } // 创建带文字的图像 imgWidth := 250 imgHeight := 110 // 条形码高度 + 文字区域高度 dc := gg.NewContext(imgWidth, imgHeight) // 设置白色背景 dc.SetColor(color.White) dc.Clear() dc.DrawImage(scaled, 0, 10) fontPath := getDefaultFontPath() if fontPath == "" { return "", fmt.Errorf("未找到系统字体文件,请手动指定字体路径") } // 设置文字属性 if err := dc.LoadFontFace("/System/Library/Fonts/Helvetica.ttc", 14); err != nil { // 如果字体文件不存在,使用默认字体 dc.LoadFontFace("Arial.ttf", 14) } dc.SetColor(color.Black) // 在条形码下方居中绘制文字0 textY := float64(85 + 20) // 条形码高度100 + 20像素间距 textWidth, _ := dc.MeasureString(content) textX := (float64(imgWidth) - textWidth) / 2 dc.DrawString(content, textX, textY) // 保存图像 err = dc.SavePNG(filename) if err != nil { return "", fmt.Errorf("保存图像失败: %v", err) } return 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) xInt := int(x) yInt := int(y) 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 := cropImage(config, xInt, yInt, widthInt, heightInt) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } return C.CString(fileName) } // CreateChineseTextImage 创建带中文字体的文本图片,支持超出部分显示... // //export CreateChineseTextImage func CreateChineseTextImage(text *C.char, width, height C.int, fontSize *C.char, outputPath *C.char) *C.char { textStr := C.GoString(text) widthInt := int(width) heightInt := int(height) fontSizeStr := C.GoString(fontSize) float, err := strconv.ParseFloat(fontSizeStr, 64) if err != nil { return C.CString(fmt.Sprintf("转换float64类型失败: %v", err)) } outputPathStr := C.GoString(outputPath) textImage, err := createChineseTextImage(textStr, widthInt, heightInt, float, outputPathStr) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } return C.CString(textImage) } // DrawChineseInfo 绘制书名,作者,出版社信息 // //export DrawChineseInfo func DrawChineseInfo(filePath, title, author, publisher, outputPath *C.char) *C.char { filePathStr := C.GoString(filePath) titleStr := C.GoString(title) authorStr := C.GoString(author) publishertr := C.GoString(publisher) outputPathStr := C.GoString(outputPath) img, err := loadPNG(filePathStr) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } err = drawChineseText(img, titleStr, authorStr, publishertr) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } err = savePNG(img, outputPathStr) if err != nil { return C.CString(fmt.Sprintf("%v", err)) } return C.CString(fmt.Sprintf("图片保存成功,路径: %s", outputPath)) } // GenerateBarcode 根据类型生成条形码 // //export GenerateBarcode func GenerateBarcode(barcodeType, content, filename *C.char) *C.char { barcodeTypeStr := C.GoString(barcodeType) contentStr := C.GoString(content) filenameStr := C.GoString(filename) filename, err := generateBarcode(barcodeTypeStr, contentStr, filenameStr) 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() { //}