package generator import ( "encoding/binary" "path/filepath" "testing" "golang.org/x/image/font" ) func testFace(t *testing.T, size float64) font.Face { t.Helper() face, err := LoadFace(filepath.Join("..", "..", "assets", "fonts", "JetBrainsMonoNLNerdFontMono-Regular.ttf"), size) if err != nil { t.Fatalf("LoadFace: %v", err) } t.Cleanup(func() { face.Close() }) return face } func TestNewTextOverlay(t *testing.T) { const frameW, frameH = 1920, 1080 o, err := NewTextOverlay("AB", frameW, frameH, testFace(t, 48)) if err != nil { t.Fatalf("NewTextOverlay: %v", err) } if o.w%6 != 0 { t.Fatalf("w=%d not a multiple of 6", o.w) } if o.x%6 != 0 { t.Fatalf("x=%d not a multiple of 6", o.x) } if center := o.x + o.w/2; center < frameW/2-3 || center > frameW/2+3 { t.Fatalf("box center %d not near frame center %d", center, frameW/2) } if o.y != 48 { t.Fatalf("y=%d, want 48", o.y) } if o.blocks == nil || len(o.blocks) != o.h*(o.w/6)*16 { t.Fatalf("pre-packed tile has wrong size") } if o.cov[2*o.w+2] != 0 { t.Fatalf("padding pixel (2,2) covered: %d", o.cov[2*o.w+2]) } nonzero, full := 0, 0 for _, c := range o.cov { if c > 0 { nonzero++ } if c == 255 { full++ } } if nonzero < 100 { t.Fatalf("too few covered pixels: %d", nonzero) } if full == 0 { t.Fatalf("no fully covered (255) pixels") } // Text without descenders ("AB") must leave the bottom quarter empty; // text with a descender ("Ag") must have coverage there. for _, tc := range []struct { text string wantAny bool }{ {"AB", false}, {"Ag", true}, } { o, err := NewTextOverlay(tc.text, frameW, frameH, testFace(t, 48)) if err != nil { t.Fatalf("NewTextOverlay(%q): %v", tc.text, err) } any := false for row := o.h * 3 / 4; row < o.h; row++ { for col := 0; col < o.w; col++ { if o.cov[row*o.w+col] > 0 { any = true } } } if any != tc.wantAny { t.Fatalf("%q: coverage in bottom quarter = %v, want %v", tc.text, any, tc.wantAny) } } } func TestTextOverlayApplyV210(t *testing.T) { const frameW, frameH = 1920, 1080 o, err := NewTextOverlay("MXL", frameW, frameH, testFace(t, 48)) if err != nil { t.Fatalf("NewTextOverlay: %v", err) } frame := make([]byte, frameW*frameH*8/3) for i := range frame { frame[i] = 0x5A // marker: untouched regions must survive } if err := o.ApplyV210(frame); err != nil { t.Fatalf("ApplyV210: %v", err) } sample := func(x, y int) (yc, cb, cr uint32) { p := y*frameW + x off := (p / 6) * 16 w0 := binary.LittleEndian.Uint32(frame[off:]) w1 := binary.LittleEndian.Uint32(frame[off+4:]) w2 := binary.LittleEndian.Uint32(frame[off+8:]) w3 := binary.LittleEndian.Uint32(frame[off+12:]) switch p % 6 { case 0: return (w0 >> 10) & 0x3FF, w0 & 0x3FF, (w0 >> 20) & 0x3FF case 1: return w1 & 0x3FF, w0 & 0x3FF, (w0 >> 20) & 0x3FF case 2: return (w1 >> 20) & 0x3FF, (w1 >> 10) & 0x3FF, w2 & 0x3FF case 3: return (w2 >> 10) & 0x3FF, (w1 >> 10) & 0x3FF, w2 & 0x3FF case 4: return w3 & 0x3FF, (w2 >> 20) & 0x3FF, (w3 >> 10) & 0x3FF default: return (w3 >> 20) & 0x3FF, (w2 >> 20) & 0x3FF, (w3 >> 10) & 0x3FF } } // Every box pixel: Y from coverage, neutral chroma at even columns. for row := 0; row < o.h; row++ { for col := 0; col < o.w; col++ { wantY := uint32(64 + (int(o.cov[row*o.w+col])*876+127)/255) yc, cb, cr := sample(o.x+col, o.y+row) if yc != wantY { t.Fatalf("box pixel (%d,%d): Y=%d, want %d", o.x+col, o.y+row, yc, wantY) } if (o.x+col)%2 == 0 && (cb != 512 || cr != 512) { t.Fatalf("box pixel (%d,%d): Cb=%d Cr=%d, want 512/512", o.x+col, o.y+row, cb, cr) } } } // Blocks just outside the box (right edge and below) must be untouched. for _, p := range [][2]int{ {o.x + o.w, o.y}, // right of box, block-aligned {o.x, o.y + o.h}, // below box {o.x + o.w, o.y + o.h}, // corner {100, 1000}, // far away } { off := ((p[1]*frameW + p[0]) / 6) * 16 for i := 0; i < 16; i++ { if frame[off+i] != 0x5A { t.Fatalf("block at (%d,%d) modified outside the box", p[0], p[1]) } } } }