77 lines
2.1 KiB
Go
77 lines
2.1 KiB
Go
// Logo plane construction for texture-sampling kernels (v210_dvd_logo.wgsl).
|
|
package generator
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"math"
|
|
"os"
|
|
|
|
// PNG decoder registration for LoadLogoPNG.
|
|
_ "image/png"
|
|
)
|
|
|
|
// BuildLogoPlane converts an image into the packed logo plane consumed by
|
|
// v210_dvd_logo.wgsl as read-only storage binding 2:
|
|
//
|
|
// logo[0] = width | height<<16
|
|
// logo[1+dy*w+dx] = opaque<<30 | Cr<<20 | Cb<<10 | Y
|
|
//
|
|
// Rec.709 full-range RGB -> 10-bit studio swing, the same transform behind
|
|
// the color-bar tables in the kernels (verified against all 21 bar values).
|
|
// Semi-transparent pixels (alpha >= 50%) count as opaque; the rest show the
|
|
// kernel background through.
|
|
func BuildLogoPlane(img image.Image) ([]uint32, error) {
|
|
b := img.Bounds()
|
|
w, h := b.Dx(), b.Dy()
|
|
if w <= 0 || h <= 0 || w > 0xFFFF || h > 0xFFFF {
|
|
return nil, fmt.Errorf("logo: bad dimensions %dx%d", w, h)
|
|
}
|
|
plane := make([]uint32, 1+w*h)
|
|
plane[0] = uint32(w) | uint32(h)<<16
|
|
for yy := 0; yy < h; yy++ {
|
|
for xx := 0; xx < w; xx++ {
|
|
r, g, bl, a := img.At(b.Min.X+xx, b.Min.Y+yy).RGBA()
|
|
if a < 1<<15 {
|
|
continue // transparent: shader falls back to background
|
|
}
|
|
// At().RGBA() is premultiplied; unpremultiply to full-range RGB.
|
|
rf := float64(r) / float64(a)
|
|
gf := float64(g) / float64(a)
|
|
bf := float64(bl) / float64(a)
|
|
luma := 0.2126*rf + 0.7152*gf + 0.0722*bf
|
|
yc := clamp10(64 + math.Round(876*luma))
|
|
cb := clamp10(512 + math.Round(448*(bf-luma)/(1-0.0722)))
|
|
cr := clamp10(512 + math.Round(448*(rf-luma)/(1-0.2126)))
|
|
plane[1+yy*w+xx] = 1<<30 | cr<<20 | cb<<10 | yc
|
|
}
|
|
}
|
|
return plane, nil
|
|
}
|
|
|
|
// clamp10 keeps a 10-bit code inside the legal 4..1019 range.
|
|
func clamp10(v float64) uint32 {
|
|
switch {
|
|
case v < 4:
|
|
return 4
|
|
case v > 1019:
|
|
return 1019
|
|
default:
|
|
return uint32(v)
|
|
}
|
|
}
|
|
|
|
// LoadLogoPNG reads a PNG file and builds the logo plane.
|
|
func LoadLogoPNG(path string) ([]uint32, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("logo: %w", err)
|
|
}
|
|
defer f.Close()
|
|
img, _, err := image.Decode(f)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("logo: decode %s: %w", path, err)
|
|
}
|
|
return BuildLogoPlane(img)
|
|
}
|