github.com/kintar/etxt@v0.0.9/examples/ebiten/cutout/main.go (about) 1 //go:build !gtxt 2 3 package main 4 5 import "os" 6 import "log" 7 import "fmt" 8 import "time" 9 import "math/rand" 10 import "image/color" 11 12 import "github.com/hajimehoshi/ebiten/v2" 13 import "golang.org/x/image/math/fixed" 14 15 import "github.com/kintar/etxt" 16 17 // NOTICE: this program looks very different with thick and slim 18 // 19 // fonts. Artsy with the slim ones, nerdy with the thick 20 // ones. Try out different fonts! 21 const MainText = "COMPLETE\nSYSTEM\nFAILURE" 22 const MainFontSize = 94 23 24 var runePool = []rune{ 25 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 26 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 27 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 28 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 29 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 30 '?', '!', '#', '=', '+', '/', '&', 31 } 32 33 type Game struct { 34 backRenderer *etxt.Renderer 35 frontRenderer *etxt.Renderer 36 backLines [][]rune 37 offscreen *ebiten.Image 38 } 39 40 func (self *Game) Layout(w int, h int) (int, int) { return w, h } 41 func (self *Game) Update() error { 42 // update background text 43 randMaxOpen := len(runePool) 44 for _, line := range self.backLines { 45 for i, _ := range line { 46 if rand.Int63n(1024) < 64 { // change runes arbitrarily 47 line[i] = runePool[rand.Intn(randMaxOpen)] 48 } 49 } 50 } 51 52 return nil 53 } 54 55 func (self *Game) Draw(screen *ebiten.Image) { 56 // dark background 57 screen.Fill(color.RGBA{0, 0, 0, 255}) 58 59 // draw background text 60 // ... the main idea is to draw line by line while positioning 61 // the glyphs manually, more or less centered. 62 self.backRenderer.SetTarget(screen) 63 for i, line := range self.backLines { 64 y := fixed.Int26_6((22 + i*16) * 64) 65 x := fixed.Int26_6(12 * 64) 66 self.backRenderer.Traverse(string(line), fixed.P(0, 0), 67 func(dot fixed.Point26_6, _ rune, index etxt.GlyphIndex) { 68 mask := self.backRenderer.LoadGlyphMask(index, dot) 69 glyphWidth, glyphHeight := mask.Image.Size() 70 dot.X = x - fixed.Int26_6(glyphWidth<<5) 71 dot.Y = y - fixed.Int26_6(glyphHeight<<5) 72 self.backRenderer.DefaultDrawFunc(dot, mask, index) 73 x += 16 * 64 74 }) 75 } 76 77 // draw front text to offscreen image 78 w, h := screen.Size() 79 if self.offscreen == nil || sizeMismatch(self.offscreen, w, h) { 80 self.offscreen = ebiten.NewImage(w, h) 81 self.frontRenderer.SetTarget(self.offscreen) 82 } 83 self.offscreen.Fill(self.frontRenderer.GetColor()) 84 self.frontRenderer.Draw(MainText, w/2, h/2) // mix mode was set in main() 85 86 // draw offscreen over screen 87 screen.DrawImage(self.offscreen, nil) 88 } 89 90 func sizeMismatch(img *ebiten.Image, expectedWidth, expectedHeight int) bool { 91 width, height := img.Size() 92 return (width != expectedWidth) || (height != expectedHeight) 93 } 94 95 func main() { 96 rand.Seed(time.Now().UnixNano()) 97 98 // get font path 99 if len(os.Args) != 2 { 100 msg := "Usage: expects one argument with the path to the font to be used\n" 101 fmt.Fprint(os.Stderr, msg) 102 os.Exit(1) 103 } 104 105 // parse font 106 font, fontName, err := etxt.ParseFontFrom(os.Args[1]) 107 if err != nil { 108 log.Fatal(err) 109 } 110 fmt.Printf("Font loaded: %s\n", fontName) 111 112 // create cache 113 cache := etxt.NewDefaultCache(1024 * 1024 * 1024) // 1GB cache 114 115 // create and configure renderer 116 renderer := etxt.NewStdRenderer() 117 renderer.SetCacheHandler(cache.NewHandler()) 118 renderer.SetSizePx(16) 119 renderer.SetFont(font) 120 renderer.SetAlign(etxt.Baseline, etxt.Left) 121 renderer.SetColor(color.RGBA{0, 255, 0, 255}) 122 123 // mmmm... actually, two renderers will make life easier here 124 frontRend := etxt.NewRenderer(renderer.GetRasterizer()) // share rasterizer, no problem 125 frontRend.SetCacheHandler(cache.NewHandler()) // share cache, no problem 126 frontRend.SetSizePx(MainFontSize) 127 frontRend.SetFont(font) 128 frontRend.SetAlign(etxt.YCenter, etxt.XCenter) 129 frontRend.SetColor(color.RGBA{0, 255, 0, 244}) // [1] 130 frontRend.SetMixMode(ebiten.CompositeModeXor) // **the critical part** 131 // [1] I generally like the textures created by slight translucency, 132 // but you can also use 255 for the solid color (or 0 to see the 133 // background weirdness in all its glory). 134 135 // run the game 136 ebiten.SetWindowTitle("etxt/examples/ebiten/cutout") 137 ebiten.SetWindowSize(640, 480) 138 backLines := make([][]rune, 480/16) 139 for i, _ := range backLines { 140 runes := make([]rune, 640/16) 141 for i, _ := range runes { 142 runes[i] = '0' 143 } 144 backLines[i] = runes 145 } 146 err = ebiten.RunGame(&Game{renderer, frontRend, backLines, nil}) 147 if err != nil { 148 log.Fatal(err) 149 } 150 }