graphics: Introduce CompositionMode (#151)

This commit is contained in:
Hajime Hoshi 2016-02-29 00:25:16 +09:00
parent 25241ddc50
commit 8ae1e292ab
10 changed files with 210 additions and 11 deletions

View File

@ -32,7 +32,6 @@ var (
count int
tmpRenderTarget *ebiten.Image
ebitenImage *ebiten.Image
saved bool
)
func update(screen *ebiten.Image) error {

View 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)
}
}

View File

@ -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)
)

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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) {

View File

@ -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))
})
}

View File

@ -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() {

View File

@ -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")
}
}