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() }