main.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // Copyright 2016 Hajime Hoshi
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "bytes"
  17. "image"
  18. "image/color"
  19. _ "image/jpeg"
  20. "log"
  21. "math"
  22. "github.com/hajimehoshi/ebiten/v2"
  23. "github.com/hajimehoshi/ebiten/v2/examples/resources/images"
  24. )
  25. const (
  26. screenWidth = 320
  27. screenHeight = 240
  28. )
  29. var (
  30. bgImage *ebiten.Image
  31. fgImage *ebiten.Image
  32. maskedFgImage = ebiten.NewImage(screenWidth, screenHeight)
  33. spotLightImage *ebiten.Image
  34. )
  35. func init() {
  36. // Decode an image from the image file's byte slice.
  37. img, _, err := image.Decode(bytes.NewReader(images.Gophers_jpg))
  38. if err != nil {
  39. log.Fatal(err)
  40. }
  41. bgImage = ebiten.NewImageFromImage(img)
  42. img, _, err = image.Decode(bytes.NewReader(images.FiveYears_jpg))
  43. if err != nil {
  44. log.Fatal(err)
  45. }
  46. fgImage = ebiten.NewImageFromImage(img)
  47. // Initialize the spot light image.
  48. const r = 64
  49. alphas := image.Point{r * 2, r * 2}
  50. a := image.NewAlpha(image.Rectangle{image.ZP, alphas})
  51. for j := 0; j < alphas.Y; j++ {
  52. for i := 0; i < alphas.X; i++ {
  53. // d is the distance between (i, j) and the (circle) center.
  54. d := math.Sqrt(float64((i-r)*(i-r) + (j-r)*(j-r)))
  55. // Alphas around the center are 0 and values outside of the circle are 0xff.
  56. b := uint8(max(0, min(0xff, int(3*d*0xff/r)-2*0xff)))
  57. a.SetAlpha(i, j, color.Alpha{b})
  58. }
  59. }
  60. spotLightImage = ebiten.NewImageFromImage(a)
  61. }
  62. type Game struct {
  63. spotLightX int
  64. spotLightY int
  65. spotLightVX int
  66. spotLightVY int
  67. }
  68. func NewGame() *Game {
  69. return &Game{
  70. spotLightX: 0,
  71. spotLightY: 0,
  72. spotLightVX: 1,
  73. spotLightVY: 1,
  74. }
  75. }
  76. func (g *Game) Update() error {
  77. if g.spotLightVX == 0 {
  78. g.spotLightVX = 1
  79. }
  80. if g.spotLightVY == 0 {
  81. g.spotLightVY = 1
  82. }
  83. g.spotLightX += g.spotLightVX
  84. g.spotLightY += g.spotLightVY
  85. if g.spotLightX < 0 {
  86. g.spotLightX = -g.spotLightX
  87. g.spotLightVX = -g.spotLightVX
  88. }
  89. if g.spotLightY < 0 {
  90. g.spotLightY = -g.spotLightY
  91. g.spotLightVY = -g.spotLightVY
  92. }
  93. s := spotLightImage.Bounds().Size()
  94. maxX, maxY := screenWidth-s.X, screenHeight-s.Y
  95. if maxX < g.spotLightX {
  96. g.spotLightX = -g.spotLightX + 2*maxX
  97. g.spotLightVX = -g.spotLightVX
  98. }
  99. if maxY < g.spotLightY {
  100. g.spotLightY = -g.spotLightY + 2*maxY
  101. g.spotLightVY = -g.spotLightVY
  102. }
  103. return nil
  104. }
  105. func (g *Game) Draw(screen *ebiten.Image) {
  106. // Reset the maskedFgImage.
  107. maskedFgImage.Fill(color.White)
  108. op := &ebiten.DrawImageOptions{}
  109. op.Blend = ebiten.BlendCopy
  110. op.GeoM.Translate(float64(g.spotLightX), float64(g.spotLightY))
  111. maskedFgImage.DrawImage(spotLightImage, op)
  112. // Use 'source-in' blend mode so that the source image (fgImage) is used but the alpha
  113. // is determined by the destination image (maskedFgImage).
  114. //
  115. // The result image is the source image with the destination alpha. In maskedFgImage, alpha
  116. // values in the hole is 0 and alpha values in other places are 0xff. As a result, the
  117. // maskedFgImage draws the source image with a hole that shape is spotLightImage. Note that
  118. // RGB values in the destination image are ignored.
  119. //
  120. // See also https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcin.
  121. op = &ebiten.DrawImageOptions{}
  122. op.Blend = ebiten.BlendSourceIn
  123. maskedFgImage.DrawImage(fgImage, op)
  124. screen.Fill(color.RGBA{0x00, 0x00, 0x80, 0xff})
  125. screen.DrawImage(bgImage, &ebiten.DrawImageOptions{})
  126. screen.DrawImage(maskedFgImage, &ebiten.DrawImageOptions{})
  127. }
  128. func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
  129. return screenWidth, screenHeight
  130. }
  131. func main() {
  132. ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
  133. ebiten.SetWindowTitle("Masking (Ebitengine Demo)")
  134. if err := ebiten.RunGame(&Game{}); err != nil {
  135. log.Fatal(err)
  136. }
  137. }