daShangDao_utils/imglib/imglib_test.go
Cai1Cai1 a92f8e2c10 feat: 初始版本 v1.0.0
- 新增 imglib 包,支持 FilePath/URL/Base64 三种图片输入方式
- 纯白占比检测、白底居中合成、等比缩放、去白边、裁切
- 二维码生成与识别、条形码生成(Code128/EAN13/Code39)
- 中文文字图片、书籍信息水印、通用水印叠加
- 输出辅助:EncodeToBytes/EncodeToBase64/SaveToFile/SaveJPEG/SavePNG
- 字体缓存,避免重复加载
- 完整测试覆盖(23个测试用例)
2026-06-30 11:56:28 +08:00

446 lines
11 KiB
Go
Raw Permalink 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 imglib
import (
"image"
"image/color"
"image/png"
"os"
"path/filepath"
"strings"
"testing"
)
// createTestPNG 创建一个纯色测试 PNG 图片,返回文件路径
func createTestPNG(t *testing.T, width, height int, fillColor color.RGBA) string {
t.Helper()
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, fillColor)
}
}
dir := t.TempDir()
path := filepath.Join(dir, "test.png")
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
if err := png.Encode(f, img); err != nil {
t.Fatal(err)
}
return path
}
// createTestPNGWithPattern 创建一个带内容的测试图片
func createTestPNGWithPattern(t *testing.T) string {
t.Helper()
img := image.NewRGBA(image.Rect(0, 0, 100, 50))
// 白色背景
for y := 0; y < 50; y++ {
for x := 0; x < 100; x++ {
img.Set(x, y, color.White)
}
}
// 黑色方块(非白色像素)
for y := 10; y < 30; y++ {
for x := 20; x < 40; x++ {
img.Set(x, y, color.Black)
}
}
dir := t.TempDir()
path := filepath.Join(dir, "pattern.png")
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
if err := png.Encode(f, img); err != nil {
t.Fatal(err)
}
return path
}
// ===================== ImageInput 测试 =====================
func TestImageInput_FromFile(t *testing.T) {
path := createTestPNG(t, 10, 10, color.RGBA{255, 0, 0, 255})
input := NewImageInputFromFile(path)
img, format, err := input.Load()
if err != nil {
t.Fatalf("从文件加载图片失败: %v", err)
}
if img == nil {
t.Fatal("返回的图片为 nil")
}
if format != "png" {
t.Errorf("期望格式 png得到 %s", format)
}
}
func TestImageInput_FromBase64(t *testing.T) {
path := createTestPNG(t, 5, 5, color.RGBA{0, 255, 0, 255})
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
b64 := base64Encode(data)
input := NewImageInputFromBase64(b64)
img, format, err := input.Load()
if err != nil {
t.Fatalf("从 base64 加载图片失败: %v", err)
}
if img == nil {
t.Fatal("返回的图片为 nil")
}
if format != "png" {
t.Errorf("期望格式 png得到 %s", format)
}
}
func TestImageInput_FromBase64_WithPrefix(t *testing.T) {
path := createTestPNG(t, 5, 5, color.RGBA{0, 0, 255, 255})
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
b64WithPrefix := "data:image/png;base64," + base64Encode(data)
input := NewImageInputFromBase64(b64WithPrefix)
img, format, err := input.Load()
if err != nil {
t.Fatalf("从带前缀的 base64 加载图片失败: %v", err)
}
if img == nil {
t.Fatal("返回的图片为 nil")
}
_ = format
}
func TestImageInput_Empty(t *testing.T) {
input := ImageInput{}
_, _, err := input.Load()
if err == nil {
t.Fatal("空的 ImageInput 应该返回错误")
}
}
// ===================== 纯白占比检测 =====================
func TestCalculateWhitePercentage_FullWhite(t *testing.T) {
path := createTestPNG(t, 50, 50, color.RGBA{255, 255, 255, 255})
pct, err := CalculateWhitePercentage(NewImageInputFromFile(path))
if err != nil {
t.Fatalf("CalculateWhitePercentage 失败: %v", err)
}
if pct != 1.0 {
t.Errorf("全白图片期望占比 1.0,得到 %f", pct)
}
}
func TestCalculateWhitePercentage_PartialWhite(t *testing.T) {
path := createTestPNGWithPattern(t)
pct, err := CalculateWhitePercentage(NewImageInputFromFile(path))
if err != nil {
t.Fatalf("CalculateWhitePercentage 失败: %v", err)
}
if pct >= 1.0 || pct <= 0 {
t.Errorf("部分白色图片期望占比在 0~1 之间,得到 %f", pct)
}
}
func TestCalculateWhitePercentage_AllBlack(t *testing.T) {
path := createTestPNG(t, 20, 20, color.RGBA{0, 0, 0, 255})
pct, err := CalculateWhitePercentage(NewImageInputFromFile(path))
if err != nil {
t.Fatalf("CalculateWhitePercentage 失败: %v", err)
}
if pct != 0.0 {
t.Errorf("全黑图片期望占比 0.0,得到 %f", pct)
}
}
// ===================== 白底居中合成 =====================
func TestCreateWhiteBottomCenteredImage(t *testing.T) {
path := createTestPNG(t, 20, 20, color.RGBA{255, 0, 0, 255})
result, err := CreateWhiteBottomCenteredImage(NewImageInputFromFile(path), 100, 100)
if err != nil {
t.Fatalf("CreateWhiteBottomCenteredImage 失败: %v", err)
}
bounds := result.Bounds()
if bounds.Dx() != 100 || bounds.Dy() != 100 {
t.Errorf("期望尺寸 100x100得到 %dx%d", bounds.Dx(), bounds.Dy())
}
}
// ===================== 缩放 =====================
func TestResizeToHeight(t *testing.T) {
path := createTestPNG(t, 100, 50, color.RGBA{255, 255, 255, 255})
result, err := ResizeToHeight(NewImageInputFromFile(path), 100)
if err != nil {
t.Fatalf("ResizeToHeight 失败: %v", err)
}
bounds := result.Bounds()
if bounds.Dy() != 100 {
t.Errorf("目标高度 100得到 %d", bounds.Dy())
}
// 宽度应按比例缩放: 100 * (100/50) = 200
if bounds.Dx() != 200 {
t.Errorf("期望宽度 200等比例得到 %d", bounds.Dx())
}
}
func TestResizeToDimensions(t *testing.T) {
path := createTestPNG(t, 80, 60, color.RGBA{255, 255, 255, 255})
result, err := ResizeToDimensions(NewImageInputFromFile(path), 40, 30)
if err != nil {
t.Fatalf("ResizeToDimensions 失败: %v", err)
}
bounds := result.Bounds()
if bounds.Dx() != 40 || bounds.Dy() != 30 {
t.Errorf("期望尺寸 40x30得到 %dx%d", bounds.Dx(), bounds.Dy())
}
}
// ===================== 裁切 =====================
func TestCrop(t *testing.T) {
path := createTestPNG(t, 100, 100, color.RGBA{255, 255, 255, 255})
result, err := Crop(NewImageInputFromFile(path), 10, 10, 50, 50)
if err != nil {
t.Fatalf("Crop 失败: %v", err)
}
bounds := result.Bounds()
if bounds.Dx() != 50 || bounds.Dy() != 50 {
t.Errorf("期望裁切尺寸 50x50得到 %dx%d", bounds.Dx(), bounds.Dy())
}
}
func TestCrop_OutOfBounds(t *testing.T) {
path := createTestPNG(t, 30, 30, color.RGBA{255, 255, 255, 255})
_, err := Crop(NewImageInputFromFile(path), 100, 0, 10, 10)
if err == nil {
t.Fatal("越界裁切应该返回错误")
}
}
// ===================== 去白边 =====================
func TestRemoveWhiteBorder(t *testing.T) {
path := createTestPNGWithPattern(t)
result, err := RemoveWhiteBorder(NewImageInputFromFile(path))
if err != nil {
t.Fatalf("RemoveWhiteBorder 失败: %v", err)
}
if result == nil {
t.Fatal("返回的图片为 nil")
}
}
// ===================== 二维码生成 =====================
func TestGenerateQRCode(t *testing.T) {
qr, err := GenerateQRCode("https://example.com", 100, 100)
if err != nil {
t.Fatalf("GenerateQRCode 失败: %v", err)
}
bounds := qr.Bounds()
if bounds.Dx() != 100 || bounds.Dy() != 100 {
t.Errorf("期望尺寸 100x100得到 %dx%d", bounds.Dx(), bounds.Dy())
}
}
// ===================== 条形码生成 =====================
func TestGenerateBarcode_Code128(t *testing.T) {
img, err := GenerateBarcode(Code128, "1234567890")
if err != nil {
t.Fatalf("GenerateBarcode Code128 失败: %v", err)
}
if img == nil {
t.Fatal("返回的图片为 nil")
}
}
func TestGenerateBarcode_EAN13(t *testing.T) {
img, err := GenerateBarcode(EAN13, "5901234123457")
if err != nil {
t.Fatalf("GenerateBarcode EAN13 失败: %v", err)
}
if img == nil {
t.Fatal("返回的图片为 nil")
}
}
func TestGenerateBarcode_Code39(t *testing.T) {
img, err := GenerateBarcode(Code39, "ABC-123")
if err != nil {
t.Fatalf("GenerateBarcode Code39 失败: %v", err)
}
if img == nil {
t.Fatal("返回的图片为 nil")
}
}
func TestGenerateBarcode_InvalidType(t *testing.T) {
_, err := GenerateBarcode("invalid", "test")
if err == nil {
t.Fatal("无效的条形码类型应该返回错误")
}
}
// ===================== 中文文字图片 =====================
func TestCreateChineseTextImage(t *testing.T) {
// 这个测试在无中文字体的环境中可能跳过
img, err := CreateChineseTextImage("测试中文文本", 300, 200, 24)
if err != nil {
t.Skipf("跳过中文文字图片测试(可能无中文字体): %v", err)
}
if img == nil {
t.Fatal("返回的图片为 nil")
}
bounds := img.Bounds()
if bounds.Dx() != 300 || bounds.Dy() != 200 {
t.Errorf("期望尺寸 300x200得到 %dx%d", bounds.Dx(), bounds.Dy())
}
}
// ===================== 输出辅助 =====================
func TestEncodeToBytes_PNG(t *testing.T) {
img, err := GenerateQRCode("test", 50, 50)
if err != nil {
t.Fatal(err)
}
data, err := EncodeToBytes(img, "png", 0)
if err != nil {
t.Fatalf("EncodeToBytes PNG 失败: %v", err)
}
if len(data) == 0 {
t.Fatal("编码后的数据为空")
}
}
func TestEncodeToBytes_JPEG(t *testing.T) {
img, err := GenerateQRCode("test", 50, 50)
if err != nil {
t.Fatal(err)
}
data, err := EncodeToBytes(img, "jpeg", 85)
if err != nil {
t.Fatalf("EncodeToBytes JPEG 失败: %v", err)
}
if len(data) == 0 {
t.Fatal("编码后的数据为空")
}
}
func TestEncodeToBase64_PNG(t *testing.T) {
img, err := GenerateQRCode("test", 20, 20)
if err != nil {
t.Fatal(err)
}
b64, err := EncodeToBase64(img, "png", 0)
if err != nil {
t.Fatalf("EncodeToBase64 失败: %v", err)
}
if !strings.HasPrefix(b64, "data:image/png;base64,") {
t.Errorf("期望以 'data:image/png;base64,' 开头,得到 %s", b64[:30])
}
}
func TestSaveToFile(t *testing.T) {
img, err := GenerateQRCode("test", 20, 20)
if err != nil {
t.Fatal(err)
}
dir := t.TempDir()
path := filepath.Join(dir, "output.png")
if err := SaveToFile(img, path); err != nil {
t.Fatalf("SaveToFile 失败: %v", err)
}
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Fatal("保存的文件不存在")
}
}
func TestSaveJPEG(t *testing.T) {
img, _ := GenerateQRCode("test", 20, 20)
dir := t.TempDir()
path := filepath.Join(dir, "test.jpg")
if err := SaveJPEG(img, path, 90); err != nil {
t.Fatalf("SaveJPEG 失败: %v", err)
}
}
func TestSavePNG(t *testing.T) {
img, _ := GenerateQRCode("test", 20, 20)
dir := t.TempDir()
path := filepath.Join(dir, "test.png")
if err := SavePNG(img, path); err != nil {
t.Fatalf("SavePNG 失败: %v", err)
}
}
// ===================== 综合ImageInput 三种方式 =====================
func TestImageInput_ThreeInputMethods(t *testing.T) {
// 1. 从文件加载
path := createTestPNGWithPattern(t)
pctFile, err := CalculateWhitePercentage(NewImageInputFromFile(path))
if err != nil {
t.Fatalf("文件方式加载失败: %v", err)
}
// 2. 从 Base64 加载(和文件加载的结果应一致)
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
pctBase64, err := CalculateWhitePercentage(NewImageInputFromBase64(base64Encode(data)))
if err != nil {
t.Fatalf("Base64方式加载失败: %v", err)
}
if pctFile != pctBase64 {
t.Errorf("文件加载(%f) 和 Base64 加载(%f) 结果不一致", pctFile, pctBase64)
}
}
// base64Encode 辅助函数
func base64Encode(data []byte) string {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var result strings.Builder
result.Grow((len(data) + 2) / 3 * 4)
for i := 0; i < len(data); i += 3 {
var b [3]byte
for j := 0; j < 3 && i+j < len(data); j++ {
b[j] = data[i+j]
}
n := 3
if i+3 > len(data) {
n = len(data) - i
}
val := uint(b[0])<<16 | uint(b[1])<<8 | uint(b[2])
result.WriteByte(alphabet[(val>>18)&0x3F])
result.WriteByte(alphabet[(val>>12)&0x3F])
if n >= 2 {
result.WriteByte(alphabet[(val>>6)&0x3F])
} else {
result.WriteByte('=')
}
if n >= 3 {
result.WriteByte(alphabet[val&0x3F])
} else {
result.WriteByte('=')
}
}
return result.String()
}