diff --git a/examples/_resources/images/fiveyears.jpg b/examples/_resources/images/fiveyears.jpg new file mode 100644 index 000000000..1c7f8f587 Binary files /dev/null and b/examples/_resources/images/fiveyears.jpg differ diff --git a/examples/_resources/images/license.md b/examples/_resources/images/license.md index 6c7ad47f8..a5366241e 100644 --- a/examples/_resources/images/license.md +++ b/examples/_resources/images/license.md @@ -28,14 +28,26 @@ Email: info@9031.com http://www.sozai-page.com/02_sozai/b/b04/b04_002/b04_002.html ``` +## fiveyears.jpg + +``` +The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/) +The design is licensed under the Creative Commons 3.0 Attributions license. +Read this article for more details: https://blog.golang.org/gopher +``` + ## gophers.jpg ``` http://blog.golang.org/go-programming-language-turns-two + +Photograph by Chris Nokleberg +the Creative Commons Attribution 3.0 License ``` ## Other image files ``` -Creative Commons Attribution 3.0 License +Copyright 2014 Hajime Hoshi +the Creative Commons Attribution 3.0 License ``` diff --git a/examples/masking/main.go b/examples/masking/main.go new file mode 100644 index 000000000..2789ed676 --- /dev/null +++ b/examples/masking/main.go @@ -0,0 +1,141 @@ +// Copyright 2016 Hajime Hoshi +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "image" + "image/color" + _ "image/jpeg" + "log" + "math" + + "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/ebitenutil" +) + +const ( + screenWidth = 320 + screenHeight = 240 +) + +var ( + gophersImage *ebiten.Image + fiveyearsImage *ebiten.Image + maskImage *ebiten.Image + spotLightImage *ebiten.Image + spotLightX = 0 + spotLightY = 0 + spotLightVX = 1 + spotLightVY = 1 +) + +func update(screen *ebiten.Image) error { + spotLightX += spotLightVX + spotLightY += spotLightVY + if spotLightX < 0 { + spotLightX = -spotLightX + spotLightVX = -spotLightVX + } + if spotLightY < 0 { + spotLightY = -spotLightY + spotLightVY = -spotLightVY + } + w, h := spotLightImage.Size() + maxX, maxY := screenWidth-w, screenHeight-h + if maxX < spotLightX { + spotLightX = -spotLightX + 2*maxX + spotLightVX = -spotLightVX + } + if maxY < spotLightY { + spotLightY = -spotLightY + 2*maxY + spotLightVY = -spotLightVY + } + + if err := maskImage.Clear(); err != nil { + return err + } + + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(float64(spotLightX), float64(spotLightY)) + if err := maskImage.DrawImage(spotLightImage, op); err != nil { + return err + } + + op = &ebiten.DrawImageOptions{} + op.CompositionMode = ebiten.CompositionModeSourceOut + if err := maskImage.DrawImage(fiveyearsImage, op); err != nil { + return err + } + + if err := screen.Fill(color.RGBA{0x00, 0x00, 0x80, 0xff}); err != nil { + return err + } + if err := screen.DrawImage(gophersImage, &ebiten.DrawImageOptions{}); err != nil { + return err + } + if err := screen.DrawImage(maskImage, &ebiten.DrawImageOptions{}); err != nil { + return err + } + + return nil +} + +func max(a, b int) int { + if a < b { + return b + } + return a +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func main() { + var err error + gophersImage, _, err = ebitenutil.NewImageFromFile("_resources/images/gophers.jpg", ebiten.FilterNearest) + if err != nil { + log.Fatal(err) + } + fiveyearsImage, _, err = ebitenutil.NewImageFromFile("_resources/images/fiveyears.jpg", ebiten.FilterNearest) + if err != nil { + log.Fatal(err) + } + maskImage, err = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterNearest) + if err != nil { + log.Fatal(err) + } + + as := image.Point{128, 128} + a := image.NewAlpha(image.Rectangle{image.ZP, as}) + for j := 0; j < as.Y; j++ { + for i := 0; i < as.X; i++ { + r := as.X / 2 + d := math.Sqrt(float64((i-r)*(i-r) + (j-r)*(j-r))) + b := uint8(max(0, min(0xff, 3*0xff-int(d*3*0xff)/r))) + a.SetAlpha(i, j, color.Alpha{b}) + } + } + spotLightImage, err = ebiten.NewImageFromImage(a, ebiten.FilterNearest) + if err != nil { + log.Fatal(err) + } + if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Masking (Ebiten Demo)"); err != nil { + log.Fatal(err) + } +} diff --git a/graphics.go b/graphics.go index ea93f8a14..81aa058b4 100644 --- a/graphics.go +++ b/graphics.go @@ -39,7 +39,19 @@ func glFilter(c *opengl.Context, filter Filter) opengl.Filter { // CompositionMode represents Porter-Duff composition mode. type CompositionMode int +// Note: This name convention follow CSS compositing: https://drafts.fxtf.org/compositing-2/ + const ( - CompositionModeSourceOver CompositionMode = CompositionMode(opengl.CompositionModeSourceOver) // regular alpha blending - CompositionModeLighter = CompositionMode(opengl.CompositionModeLighter) // sum of source and destination (a.k.a. 'plus' or 'additive') + CompositionModeSourceOver CompositionMode = CompositionMode(opengl.CompositionModeSourceOver) // regular alpha blending + CompositionModeClear = CompositionMode(opengl.CompositionModeClear) + CompositionModeCopy = CompositionMode(opengl.CompositionModeCopy) + CompositionModeDestination = CompositionMode(opengl.CompositionModeDestination) + CompositionModeSourceIn = CompositionMode(opengl.CompositionModeSourceIn) + CompositionModeDestinationIn = CompositionMode(opengl.CompositionModeDestinationIn) + CompositionModeSourceOut = CompositionMode(opengl.CompositionModeSourceOut) + CompositionModeDestinationOut = CompositionMode(opengl.CompositionModeDestinationOut) + CompositionModeSourceAtop = CompositionMode(opengl.CompositionModeSourceAtop) + CompositionModeDestinationAtop = CompositionMode(opengl.CompositionModeDestinationAtop) + CompositionModeXor = CompositionMode(opengl.CompositionModeXor) + CompositionModeLighter = CompositionMode(opengl.CompositionModeLighter) // sum of source and destination (a.k.a. 'plus' or 'additive') ) diff --git a/internal/graphics/opengl/types.go b/internal/graphics/opengl/types.go index 1485efc09..d5fb7e9f8 100644 --- a/internal/graphics/opengl/types.go +++ b/internal/graphics/opengl/types.go @@ -47,8 +47,8 @@ const ( CompositionModeSourceOver CompositionMode = iota // This value must be 0 (= initial value) CompositionModeClear CompositionModeCopy - CompositionModeDesination - CompositionModeDesinationOver + CompositionModeDestination + CompositionModeDestinationOver CompositionModeSourceIn CompositionModeDestinationIn CompositionModeSourceOut @@ -68,10 +68,10 @@ func (c *Context) operations(mode CompositionMode) (src operation, dst operation return c.zero, c.zero case CompositionModeCopy: return c.one, c.zero - case CompositionModeDesination: + case CompositionModeDestination: return c.zero, c.one - case CompositionModeDesinationOver: - return c.one, c.oneMinusDstAlpha + case CompositionModeDestinationOver: + return c.oneMinusDstAlpha, c.one case CompositionModeSourceIn: return c.dstAlpha, c.zero case CompositionModeDestinationIn: