package webp_test import ( "bytes" "context" "errors" "fmt" "image" "image/color" "image/jpeg" "io" "testing" "git.golem15.com/golem15/summercms/modules/lagoon/attach" "git.golem15.com/golem15/summercms/modules/lagoon/attach/webp" "gocloud.dev/blob" "gocloud.dev/blob/memblob" ) // gradient is a w by h test picture. func gradient(w, h int) *image.RGBA { img := image.NewRGBA(image.Rect(0, 0, w, h)) for y := 0; y < h; y++ { for x := 0; x < w; x++ { img.Set(x, y, color.RGBA{uint8(x * 255 / w), uint8(y * 255 / h), 128, 255}) } } return img } // isWebP reports whether raw starts with a RIFF/WEBP header. func isWebP(raw []byte) bool { return len(raw) > 12 && string(raw[0:4]) == "RIFF" && string(raw[8:12]) == "WEBP" } // putOriginal stores a JPEG original for f in bucket. func putOriginal(t *testing.T, bucket *blob.Bucket, f *attach.File, w, h int) { t.Helper() var buf bytes.Buffer if err := jpeg.Encode(&buf, gradient(w, h), nil); err != nil { t.Fatal(err) } if err := bucket.WriteAll(t.Context(), attach.BlobKey(f.DiskName), buf.Bytes(), nil); err != nil { t.Fatal(err) } } func TestEncodeWritesWebP(t *testing.T) { var buf bytes.Buffer if err := webp.Encode(&buf, gradient(64, 40)); err != nil { t.Fatal(err) } if !isWebP(buf.Bytes()) { t.Fatalf("Encode wrote % x, want a RIFF/WEBP header", buf.Bytes()[:16]) } cfg, format, err := image.DecodeConfig(bytes.NewReader(buf.Bytes())) if err != nil || format != "webp" || cfg.Width != 64 || cfg.Height != 40 { t.Fatalf("decoded config %+v %q err=%v, want a 64x40 webp", cfg, format, err) } if err := webp.Encode(&buf, nil); err == nil { t.Fatal("webp.Encode(nil) must fail") } } func TestVariantKey(t *testing.T) { for _, tc := range []struct{ thumb, format, want string }{ {"abc/123/xyz/thumb_42_480_0_0_0_auto.jpg", "webp", "abc/123/xyz/thumb_42_480_0_0_0_auto.webp"}, {"abc/123/xyz/thumb_42_480_0_0_0_auto.png", "WEBP", "abc/123/xyz/thumb_42_480_0_0_0_auto.webp"}, {"abc/123/xyz/thumb_42_480_0_0_0_auto.webp", "webp", "abc/123/xyz/thumb_42_480_0_0_0_auto.webp.webp"}, {"abc/123/xyz/thumb_42_480_0_0_0_auto.jpg", "../x", ""}, {"abc/123/xyz/thumb_42_480_0_0_0_auto.jpg", "", ""}, {"", "webp", ""}, } { if got := attach.VariantKey(tc.thumb, tc.format); got != tc.want { t.Errorf("VariantKey(%q, %q) = %q, want %q", tc.thumb, tc.format, got, tc.want) } } } func TestVariantWritesOnceNextToTheThumbnail(t *testing.T) { ctx := t.Context() bucket := memblob.OpenBucket(nil) defer bucket.Close() f := &attach.File{ID: 5, DiskName: "abc123xyz.jpg", FileName: "cover.jpg"} putOriginal(t, bucket, f, 400, 300) key, err := webp.Variant(ctx, bucket, f, 120, 90, "auto") if err != nil { t.Fatal(err) } if want := "abc/123/xyz/thumb_5_120_90_0_0_auto.webp"; key != want { t.Fatalf("variant key = %q, want %q", key, want) } if ok, err := bucket.Exists(ctx, "abc/123/xyz/thumb_5_120_90_0_0_auto.jpg"); err != nil || !ok { t.Fatalf("the thumbnail was not made first: %v %v", ok, err) } raw, err := bucket.ReadAll(ctx, key) if err != nil || !isWebP(raw) { t.Fatalf("variant bytes = % x err=%v, want WebP", raw[:min(16, len(raw))], err) } attrs, err := bucket.Attributes(ctx, key) if err != nil || attrs.ContentType != webp.ContentType { t.Fatalf("variant content type = %v err=%v", attrs, err) } cfg, _, err := image.DecodeConfig(bytes.NewReader(raw)) if err != nil || cfg.Width != 120 || cfg.Height != 90 { t.Fatalf("variant is %dx%d err=%v, want the thumbnail's 120x90", cfg.Width, cfg.Height, err) } // A second call finds the variant and writes nothing. if err := bucket.WriteAll(ctx, key, []byte("kept"), nil); err != nil { t.Fatal(err) } again, err := webp.Variant(ctx, bucket, f, 120, 90, "auto") if err != nil || again != key { t.Fatalf("second Variant = %q err=%v", again, err) } if raw, _ := bucket.ReadAll(ctx, key); string(raw) != "kept" { t.Fatalf("second Variant rewrote the variant: % x", raw[:min(16, len(raw))]) } } func TestVariantOfAWebPOriginalKeepsItsThumbnail(t *testing.T) { ctx := t.Context() bucket := memblob.OpenBucket(nil) defer bucket.Close() f := &attach.File{ID: 9, DiskName: "def456uvw.webp"} var buf bytes.Buffer if err := webp.Encode(&buf, gradient(200, 100)); err != nil { t.Fatal(err) } if err := bucket.WriteAll(ctx, attach.BlobKey(f.DiskName), buf.Bytes(), nil); err != nil { t.Fatal(err) } key, err := webp.Variant(ctx, bucket, f, 100, 50, "auto") if err != nil { t.Fatal(err) } thumb := "def/456/uvw/thumb_9_100_50_0_0_auto.webp" if key != thumb+".webp" { t.Fatalf("variant key = %q, want %q", key, thumb+".webp") } if raw, _ := bucket.ReadAll(ctx, thumb); isWebP(raw) { t.Fatal("the thumbnail of a webp original was overwritten with the variant") } if raw, _ := bucket.ReadAll(ctx, key); !isWebP(raw) { t.Fatal("the variant is not WebP") } } func TestVariantEncodeFailureLeavesNoFile(t *testing.T) { ctx := t.Context() bucket := memblob.OpenBucket(nil) defer bucket.Close() f := &attach.File{ID: 7, DiskName: "ghi789rst.jpg"} putOriginal(t, bucket, f, 300, 200) boom := errors.New("boom") restore := webp.SetEncoderForTest(func(w io.Writer, _ image.Image) error { _, _ = w.Write([]byte("RIFF partial")) return boom }) t.Cleanup(restore) if _, err := webp.Variant(ctx, bucket, f, 60, 40, "auto"); !errors.Is(err, boom) { t.Fatalf("Variant err = %v, want the encode failure", err) } if ok, err := bucket.Exists(ctx, "ghi/789/rst/thumb_7_60_40_0_0_auto.webp"); err != nil || ok { t.Fatalf("a failed encode left a variant: %v %v", ok, err) } restore() if _, err := webp.Variant(ctx, bucket, f, 60, 40, "auto"); err != nil { t.Fatalf("the next run after a failure: %v", err) } } func ExampleVariant() { ctx := context.Background() bucket := memblob.OpenBucket(nil) defer bucket.Close() // The system_files row of a cover image and its stored original. f := &attach.File{ID: 42, DiskName: "5f1d0c2e9a7b4c3d8e6f.jpg", FileName: "cover.jpg"} var img bytes.Buffer if err := jpeg.Encode(&img, image.NewRGBA(image.Rect(0, 0, 1600, 1200)), nil); err != nil { fmt.Println(err) return } if err := bucket.WriteAll(ctx, attach.BlobKey(f.DiskName), img.Bytes(), nil); err != nil { fmt.Println(err) return } // In a background job: the 800 px thumbnail and its WebP copy. key, err := webp.Variant(ctx, bucket, f, 800, 800, "auto") if err != nil { fmt.Println(err) return } fmt.Println(key) fmt.Println(attach.PublicURL(key)) // Output: // 5f1/d0c/2e9/thumb_42_800_800_0_0_auto.webp // /storage/uploads/5f1/d0c/2e9/thumb_42_800_800_0_0_auto.webp }