Live video on the AT Protocol
at eli/docker-deployment-docs 111 lines 2.7 kB view raw
1package media 2 3import ( 4 "bytes" 5 "fmt" 6 "image" 7 "image/color" 8 "image/draw" 9 "image/png" 10 "io" 11 "strings" 12 13 "github.com/golang/freetype" 14 "github.com/golang/freetype/truetype" 15 "golang.org/x/image/font" 16 "stream.place/streamplace/js/app" 17) 18 19type TextRenderer struct { 20 font *truetype.Font 21} 22 23func NewTextRenderer() (*TextRenderer, error) { 24 font, err := loadFont() 25 if err != nil { 26 return nil, err 27 } 28 return &TextRenderer{font: font}, nil 29} 30 31func loadFont() (*truetype.Font, error) { 32 entries, err := app.AllFiles.ReadDir("dist/assets/assets/fonts") 33 if err != nil { 34 return nil, err 35 } 36 var fontFile string 37 for _, entry := range entries { 38 if strings.HasPrefix(entry.Name(), "AtkinsonHyperlegibleM") { 39 fontFile = fmt.Sprintf("dist/assets/assets/fonts/%s", entry.Name()) 40 break 41 } 42 } 43 if fontFile == "" { 44 return nil, fmt.Errorf("could not find AtkinsonHyperlegibleMono-Regular font") 45 } 46 fd, err := app.AllFiles.Open(fontFile) 47 if err != nil { 48 return nil, err 49 } 50 fontBytes, err := io.ReadAll(fd) 51 if err != nil { 52 return nil, err 53 } 54 f, err := freetype.ParseFont(fontBytes) 55 if err != nil { 56 return nil, err 57 } 58 return f, nil 59} 60 61func (tr *TextRenderer) GenerateImage(textContent string, fgColorHex string, bgColorHex string, fontSize float64) ([]byte, error) { 62 fgColor := color.RGBA{0xff, 0xff, 0xff, 0xff} 63 if len(fgColorHex) == 7 { 64 _, err := fmt.Sscanf(fgColorHex, "#%02x%02x%02x", &fgColor.R, &fgColor.G, &fgColor.B) 65 if err != nil { 66 return nil, err 67 } 68 } 69 70 bgColor := color.RGBA{0x30, 0x0a, 0x24, 0xcc} 71 if len(bgColorHex) == 7 { 72 _, err := fmt.Sscanf(bgColorHex, "#%02x%02x%02x", &bgColor.R, &bgColor.G, &bgColor.B) 73 if err != nil { 74 return nil, err 75 } 76 } 77 78 code := strings.ReplaceAll(textContent, "\t", " ") // convert tabs into spaces 79 text := strings.Split(code, "\n") // split newlines into arrays 80 81 fg := image.NewUniform(fgColor) 82 bg := image.NewUniform(bgColor) 83 rgba := image.NewRGBA(image.Rect(0, 0, 540, 55)) 84 draw.Draw(rgba, rgba.Bounds(), bg, image.Pt(0, 0), draw.Src) 85 c := freetype.NewContext() 86 c.SetDPI(72) 87 c.SetFont(tr.font) 88 c.SetFontSize(fontSize) 89 c.SetClip(rgba.Bounds()) 90 c.SetDst(rgba) 91 c.SetSrc(fg) 92 c.SetHinting(font.HintingNone) 93 94 textXOffset := 10 95 textYOffset := 5 + int(c.PointToFixed(fontSize)>>6) // Note shift/truncate 6 bits first 96 97 pt := freetype.Pt(textXOffset, textYOffset) 98 for _, s := range text { 99 _, err := c.DrawString(strings.ReplaceAll(s, "\r", ""), pt) 100 if err != nil { 101 return nil, err 102 } 103 pt.Y += c.PointToFixed(fontSize * 1.5) 104 } 105 106 b := new(bytes.Buffer) 107 if err := png.Encode(b, rgba); err != nil { 108 return nil, err 109 } 110 return b.Bytes(), nil 111}