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

591 lines
15 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/nfnt/resize"
"golang.org/x/image/draw"
"image"
"image/color"
"image/jpeg"
_ "image/jpeg"
"image/png"
_ "image/png"
"os"
"path/filepath"
"strings"
"unsafe"
)
// Config 配置结构体
type Config struct {
OutputDir string // 输出目录路径
FileName string // 文件名
MatchDir string // 满足条件的图片目录名
UnmatchDir string // 不满足条件的图片目录名
EqualHeightDir string // 等高的图片目录名
WhiteDir string // 白色底图的图片目录名
WhiteBorderPngDir string // 去白边转PNG的图片目录名
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
}
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 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)
}
// 导出函数释放C字符串内存
//
//export FreeCString
func FreeCString(str *C.char) {
C.free(unsafe.Pointer(str))
}
//func main() {
//
//}