package platform import ( "bytes" "context" "encoding/binary" "image" "image/color" "image/jpeg" "image/png" "testing" ) func TestAssetPreviewDownscalesWithoutChangingOriginal(t *testing.T) { source := image.NewNRGBA(image.Rect(0, 0, 1600, 900)) for y := 0; y < 900; y++ { for x := 0; x < 1600; x++ { source.SetNRGBA(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), 255}) } } var encoded bytes.Buffer if err := png.Encode(&encoded, source); err != nil { t.Fatal(err) } original := append([]byte(nil), encoded.Bytes()...) preview, err := makeAssetPreview(encoded.Bytes(), "image/png", 512) if err != nil { t.Fatal(err) } config, format, err := image.DecodeConfig(bytes.NewReader(preview.data)) if err != nil || config.Width != 512 || config.Height != 288 || format != "jpeg" { t.Fatalf("bad preview %s %dx%d %v", format, config.Width, config.Height, err) } if len(preview.data) >= len(original) || !bytes.Equal(original, encoded.Bytes()) { t.Fatal("preview must shrink without changing original") } cached, err := cachedAssetPreview(context.Background(), original, "image/png", 512) if err != nil || !bytes.Equal(cached.data, preview.data) { t.Fatal("cache result mismatch") } } func TestAssetPreviewPreservesTransparencyAndAnimation(t *testing.T) { source := image.NewNRGBA(image.Rect(0, 0, 800, 400)) source.SetNRGBA(1, 1, color.NRGBA{255, 0, 0, 255}) var encoded bytes.Buffer _ = png.Encode(&encoded, source) preview, err := makeAssetPreview(encoded.Bytes(), "image/png", 256) if err != nil { t.Fatal(err) } decoded, format, err := image.Decode(bytes.NewReader(preview.data)) if err != nil || format != "png" { t.Fatal("transparent image must stay PNG") } _, _, _, alpha := decoded.At(200, 100).RGBA() if alpha != 0 { t.Fatal("transparent background was lost") } original := []byte("GIF original animation bytes") gif, err := makeAssetPreview(original, "image/gif", 512) if err != nil || !bytes.Equal(gif.data, original) { t.Fatal("GIF animation changed") } } func TestAssetPreviewRejectsUnboundedWidths(t *testing.T) { for _, value := range []string{"0", "1", "256", "512", "2048", "999999", "-1", "abc"} { if _, err := previewWidth("preview", value); err == nil { t.Errorf("accepted width %s", value) } } for _, value := range []string{"", "1024"} { if _, err := previewWidth("preview", value); err != nil { t.Fatal(err) } } if n, err := previewWidth("", ""); err != nil || n != 0 { t.Fatal("original request changed") } if _, err := previewWidth("other", ""); err == nil { t.Fatal("unknown variant accepted") } if _, err := makeAssetPreview([]byte("invalid"), "image/png", 512); err == nil { t.Fatal("malformed image accepted") } } func TestAssetPreviewAppliesJPEGOrientation(t *testing.T) { source := image.NewNRGBA(image.Rect(0, 0, 800, 400)) for y := 0; y < 400; y++ { for x := 0; x < 800; x++ { source.SetNRGBA(x, y, color.NRGBA{255, 0, 0, 255}) } } var original bytes.Buffer _ = jpeg.Encode(&original, source, nil) tiff := make([]byte, 26) copy(tiff, "II") binary.LittleEndian.PutUint16(tiff[2:], 42) binary.LittleEndian.PutUint32(tiff[4:], 8) binary.LittleEndian.PutUint16(tiff[8:], 1) binary.LittleEndian.PutUint16(tiff[10:], 0x112) binary.LittleEndian.PutUint16(tiff[12:], 3) binary.LittleEndian.PutUint32(tiff[14:], 1) binary.LittleEndian.PutUint16(tiff[18:], 6) exif := append([]byte("Exif\x00\x00"), tiff...) segment := append([]byte{0xff, 0xe1, 0, byte(len(exif) + 2)}, exif...) encoded := append(append(append([]byte{}, original.Bytes()[:2]...), segment...), original.Bytes()[2:]...) if jpegOrientation(encoded) != 6 { t.Fatal("EXIF orientation not decoded") } preview, err := makeAssetPreview(encoded, "image/jpeg", 256) if err != nil { t.Fatal(err) } config, _, err := image.DecodeConfig(bytes.NewReader(preview.data)) if err != nil || config.Width != 128 || config.Height != 256 { t.Fatalf("rotated dimensions %dx%d %v", config.Width, config.Height, err) } } func TestAllAssetOrientationMappings(t *testing.T) { source := image.NewNRGBA(image.Rect(0, 0, 3, 2)) source.SetNRGBA(0, 0, color.NRGBA{255, 0, 0, 255}) for orientation := 1; orientation <= 8; orientation++ { oriented := orientedAsset{source, orientation} red := 0 for y := 0; y < oriented.Bounds().Dy(); y++ { for x := 0; x < oriented.Bounds().Dx(); x++ { r, _, _, _ := oriented.At(x, y).RGBA() if r > 0 { red++ } } } if red != 1 { t.Errorf("orientation %d loses/repeats pixels", orientation) } } }