mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 10:48:53 +01:00
graphics: Introduce CompositionMode (#151)
This commit is contained in:
parent
25241ddc50
commit
8ae1e292ab
@ -32,7 +32,6 @@ var (
|
||||
count int
|
||||
tmpRenderTarget *ebiten.Image
|
||||
ebitenImage *ebiten.Image
|
||||
saved bool
|
||||
)
|
||||
|
||||
func update(screen *ebiten.Image) error {
|
||||
|
60
examples/composition/main.go
Normal file
60
examples/composition/main.go
Normal file
@ -0,0 +1,60 @@
|
||||
// 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/color"
|
||||
_ "image/png"
|
||||
"log"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
)
|
||||
|
||||
const (
|
||||
screenWidth = 320
|
||||
screenHeight = 240
|
||||
)
|
||||
|
||||
var (
|
||||
ebitenImage *ebiten.Image
|
||||
)
|
||||
|
||||
func update(screen *ebiten.Image) error {
|
||||
w, _ := ebitenImage.Size()
|
||||
const ox = 10
|
||||
const oy = 10
|
||||
|
||||
screen.Fill(color.NRGBA{0x00, 0x40, 0x80, 0xff})
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Translate(ox, oy)
|
||||
screen.DrawImage(ebitenImage, op)
|
||||
op = &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Translate(ox+float64(w), oy)
|
||||
op.CompositionMode = ebiten.CompositionModeLighter
|
||||
screen.DrawImage(ebitenImage, op)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
ebitenImage, _, err = ebitenutil.NewImageFromFile("_resources/images/ebiten.png", ebiten.FilterNearest)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Composition Mode (Ebiten Demo)"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
@ -35,3 +35,11 @@ func glFilter(c *opengl.Context, filter Filter) opengl.Filter {
|
||||
}
|
||||
panic("not reach")
|
||||
}
|
||||
|
||||
// CompositionMode represents Porter-Duff composition mode.
|
||||
type CompositionMode int
|
||||
|
||||
const (
|
||||
CompositionModeSourceOver CompositionMode = CompositionMode(opengl.CompositionModeSourceOver)
|
||||
CompositionModeLighter = CompositionMode(opengl.CompositionModeLighter)
|
||||
)
|
||||
|
11
image.go
11
image.go
@ -22,6 +22,7 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/internal/graphics/opengl"
|
||||
)
|
||||
|
||||
// Image represents an image.
|
||||
@ -98,7 +99,8 @@ func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) {
|
||||
}
|
||||
w, h := image.Size()
|
||||
quads := &textureQuads{parts: parts, width: w, height: h}
|
||||
return i.framebuffer.DrawTexture(glContext, image.texture, quads, &options.GeoM, &options.ColorM)
|
||||
m := opengl.CompositionMode(options.CompositionMode)
|
||||
return i.framebuffer.DrawTexture(glContext, image.texture, quads, &options.GeoM, &options.ColorM, m)
|
||||
}
|
||||
|
||||
// Bounds returns the bounds of the image.
|
||||
@ -182,9 +184,10 @@ func (i *Image) ReplacePixels(p []uint8) error {
|
||||
|
||||
// A DrawImageOptions represents options to render an image on an image.
|
||||
type DrawImageOptions struct {
|
||||
ImageParts ImageParts
|
||||
GeoM GeoM
|
||||
ColorM ColorM
|
||||
ImageParts ImageParts
|
||||
GeoM GeoM
|
||||
ColorM ColorM
|
||||
CompositionMode CompositionMode
|
||||
|
||||
// Deprecated (as of 1.1.0-alpha): Use ImageParts instead.
|
||||
Parts []ImagePart
|
||||
|
@ -85,6 +85,7 @@ func TestImageComposition(t *testing.T) {
|
||||
img2Color := color.NRGBA{0x24, 0x3f, 0x6a, 0x88}
|
||||
img3Color := color.NRGBA{0x85, 0xa3, 0x08, 0xd3}
|
||||
|
||||
// TODO: Rename this to img0
|
||||
img1, _, err := openEbitenImage("testdata/ebiten.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -238,4 +239,43 @@ func TestImageDispose(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestImageCompositionModeLighter(t *testing.T) {
|
||||
img0, _, err := openEbitenImage("testdata/ebiten.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
w, h := img0.Size()
|
||||
img1, err := NewImage(w, h, FilterNearest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
img1.Fill(color.RGBA{0x01, 0x02, 0x03, 0x04})
|
||||
op := &DrawImageOptions{}
|
||||
op.CompositionMode = CompositionModeLighter
|
||||
img1.DrawImage(img0, op)
|
||||
for j := 0; j < img1.Bounds().Size().Y; j++ {
|
||||
for i := 0; i < img1.Bounds().Size().X; i++ {
|
||||
got := img1.At(i, j).(color.RGBA)
|
||||
want := img0.At(i, j).(color.RGBA)
|
||||
want.R = uint8(min(0xff, int(want.R)+1))
|
||||
want.G = uint8(min(0xff, int(want.G)+2))
|
||||
want.B = uint8(min(0xff, int(want.B)+3))
|
||||
want.A = uint8(min(0xff, int(want.A)+4))
|
||||
if got != want {
|
||||
t.Errorf("img1 At(%d, %d): got %#v; want %#v", i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add more tests (e.g. DrawImage with color matrix)
|
||||
|
@ -37,7 +37,9 @@ var vertices = make([]int16, 16*quadsMaxNum)
|
||||
|
||||
var shadersInitialized = false
|
||||
|
||||
func drawTexture(c *opengl.Context, texture opengl.Texture, projectionMatrix *[4][4]float64, quads TextureQuads, geo Matrix, color Matrix) error {
|
||||
func drawTexture(c *opengl.Context, texture opengl.Texture, projectionMatrix *[4][4]float64, quads TextureQuads, geo Matrix, color Matrix, mode opengl.CompositionMode) error {
|
||||
c.BlendFunc(mode)
|
||||
|
||||
// NOTE: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
|
||||
// Let's use them to compare to len(quads) in the future.
|
||||
|
||||
|
@ -15,9 +15,10 @@
|
||||
package graphics
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/internal/graphics/opengl"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/internal/graphics/opengl"
|
||||
)
|
||||
|
||||
type TextureQuads interface {
|
||||
@ -115,12 +116,12 @@ func (f *Framebuffer) Fill(c *opengl.Context, clr color.Color) error {
|
||||
return c.FillFramebuffer(r, g, b, a)
|
||||
}
|
||||
|
||||
func (f *Framebuffer) DrawTexture(c *opengl.Context, t *Texture, quads TextureQuads, geo, clr Matrix) error {
|
||||
func (f *Framebuffer) DrawTexture(c *opengl.Context, t *Texture, quads TextureQuads, geo, clr Matrix, mode opengl.CompositionMode) error {
|
||||
if err := f.setAsViewport(c); err != nil {
|
||||
return err
|
||||
}
|
||||
p := f.projectionMatrix()
|
||||
return drawTexture(c, t.native, p, quads, geo, clr)
|
||||
return drawTexture(c, t.native, p, quads, geo, clr, mode)
|
||||
}
|
||||
|
||||
func (f *Framebuffer) Pixels(c *opengl.Context) ([]uint8, error) {
|
||||
|
@ -63,6 +63,12 @@ func NewContext() *Context {
|
||||
StaticDraw: gl.STATIC_DRAW,
|
||||
Triangles: gl.TRIANGLES,
|
||||
Lines: gl.LINES,
|
||||
zero: gl.ZERO,
|
||||
one: gl.ONE,
|
||||
srcAlpha: gl.SRC_ALPHA,
|
||||
dstAlpha: gl.DST_ALPHA,
|
||||
oneMinusSrcAlpha: gl.ONE_MINUS_SRC_ALPHA,
|
||||
oneMinusDstAlpha: gl.ONE_MINUS_DST_ALPHA,
|
||||
}
|
||||
c.locationCache = newLocationCache()
|
||||
c.funcs = make(chan func())
|
||||
@ -98,7 +104,15 @@ func (c *Context) Init() {
|
||||
}
|
||||
// Textures' pixel formats are alpha premultiplied.
|
||||
gl.Enable(gl.BLEND)
|
||||
gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
||||
})
|
||||
c.BlendFunc(CompositionModeSourceOver)
|
||||
}
|
||||
|
||||
func (c *Context) BlendFunc(mode CompositionMode) {
|
||||
c.RunOnContextThread(func() {
|
||||
s, d := c.operations(mode)
|
||||
// TODO: Cache and don't call this often
|
||||
gl.BlendFunc(uint32(s), uint32(d))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,12 @@ func NewContext() *Context {
|
||||
StaticDraw: BufferUsage(gl.STATIC_DRAW),
|
||||
Triangles: Mode(gl.TRIANGLES),
|
||||
Lines: Mode(gl.LINES),
|
||||
zero: operation(gl.ZERO),
|
||||
one: operation(gl.ONE),
|
||||
srcAlpha: operation(gl.SRC_ALPHA),
|
||||
dstAlpha: operation(gl.DST_ALPHA),
|
||||
oneMinusSrcAlpha: operation(gl.ONE_MINUS_SRC_ALPHA),
|
||||
oneMinusDstAlpha: operation(gl.ONE_MINUS_DST_ALPHA),
|
||||
}
|
||||
c.gl = gl
|
||||
c.locationCache = newLocationCache()
|
||||
@ -116,7 +122,15 @@ func (c *Context) init() {
|
||||
gl := c.gl
|
||||
// Textures' pixel formats are alpha premultiplied.
|
||||
gl.Enable(gl.BLEND)
|
||||
gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
||||
//gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
||||
c.BlendFunc(CompositionModeSourceOver)
|
||||
}
|
||||
|
||||
func (c *Context) BlendFunc(mode CompositionMode) {
|
||||
s, d := c.operations(mode)
|
||||
// TODO: Cache and don't call this often
|
||||
gl := c.gl
|
||||
gl.BlendFunc(int(s), int(d))
|
||||
}
|
||||
|
||||
func (c *Context) Check() {
|
||||
|
@ -19,6 +19,7 @@ type ShaderType int
|
||||
type BufferType int
|
||||
type BufferUsage int
|
||||
type Mode int
|
||||
type operation int
|
||||
|
||||
type Context struct {
|
||||
Nearest Filter
|
||||
@ -31,5 +32,62 @@ type Context struct {
|
||||
StaticDraw BufferUsage
|
||||
Triangles Mode
|
||||
Lines Mode
|
||||
zero operation
|
||||
one operation
|
||||
srcAlpha operation
|
||||
dstAlpha operation
|
||||
oneMinusSrcAlpha operation
|
||||
oneMinusDstAlpha operation
|
||||
context
|
||||
}
|
||||
|
||||
type CompositionMode int
|
||||
|
||||
const (
|
||||
CompositionModeSourceOver CompositionMode = iota
|
||||
CompositionModeClear
|
||||
CompositionModeCopy
|
||||
CompositionModeDesination
|
||||
CompositionModeDesinationOver
|
||||
CompositionModeSourceIn
|
||||
CompositionModeDestinationIn
|
||||
CompositionModeSourceOut
|
||||
CompositionModeDestinationOut
|
||||
CompositionModeSourceAtop
|
||||
CompositionModeDestinationAtop
|
||||
CompositionModeXor
|
||||
CompositionModeLighter
|
||||
)
|
||||
|
||||
func (c *Context) operations(mode CompositionMode) (src operation, dst operation) {
|
||||
switch mode {
|
||||
case CompositionModeSourceOver:
|
||||
return c.one, c.oneMinusSrcAlpha
|
||||
case CompositionModeClear:
|
||||
return c.zero, c.zero
|
||||
case CompositionModeCopy:
|
||||
return c.one, c.zero
|
||||
case CompositionModeDesination:
|
||||
return c.zero, c.one
|
||||
case CompositionModeDesinationOver:
|
||||
return c.one, c.oneMinusDstAlpha
|
||||
case CompositionModeSourceIn:
|
||||
return c.dstAlpha, c.zero
|
||||
case CompositionModeDestinationIn:
|
||||
return c.zero, c.srcAlpha
|
||||
case CompositionModeSourceOut:
|
||||
return c.oneMinusDstAlpha, c.zero
|
||||
case CompositionModeDestinationOut:
|
||||
return c.zero, c.oneMinusSrcAlpha
|
||||
case CompositionModeSourceAtop:
|
||||
return c.dstAlpha, c.oneMinusSrcAlpha
|
||||
case CompositionModeDestinationAtop:
|
||||
return c.oneMinusDstAlpha, c.srcAlpha
|
||||
case CompositionModeXor:
|
||||
return c.oneMinusDstAlpha, c.oneMinusSrcAlpha
|
||||
case CompositionModeLighter:
|
||||
return c.one, c.one
|
||||
default:
|
||||
panic("not reach")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user