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  }