ebiten: separate a package for ColorM: colorm

Closes #2171
This commit is contained in:
Hajime Hoshi 2022-11-02 02:43:42 +09:00
parent 8c5f525ac2
commit 156c34a316
28 changed files with 645 additions and 78 deletions

80
colorm/colorm.go Normal file
View File

@ -0,0 +1,80 @@
// Copyright 2022 The Ebitengine Authors
//
// 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 colorm
import (
"fmt"
"sync"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/internal/builtinshader"
)
// Dim is a dimension of a ColorM.
const Dim = ebiten.ColorMDim
// ColorM represents a matrix to transform coloring when rendering an image.
//
// ColorM is applied to the straight alpha color
// while an Image's pixels' format is alpha premultiplied.
// Before applying a matrix, a color is un-multiplied, and after applying the matrix,
// the color is multiplied again.
//
// The initial value is identity.
type ColorM = ebiten.ColorM
func uniforms(c ColorM) map[string]interface{} {
var body [16]float32
var translation [4]float32
c.ReadElements(body[:], translation[:])
uniforms := map[string]interface{}{}
uniforms[builtinshader.UniformColorMBody] = body[:]
uniforms[builtinshader.UniformColorMTranslation] = translation[:]
return uniforms
}
type builtinShaderKey struct {
filter builtinshader.Filter
address builtinshader.Address
}
var (
builtinShaders = map[builtinShaderKey]*ebiten.Shader{}
builtinShadersM sync.Mutex
)
func builtinShader(filter builtinshader.Filter, address builtinshader.Address) *ebiten.Shader {
builtinShadersM.Lock()
defer builtinShadersM.Unlock()
key := builtinShaderKey{
filter: filter,
address: address,
}
if s, ok := builtinShaders[key]; ok {
return s
}
src := builtinshader.Shader(filter, address, true)
s, err := ebiten.NewShader(src)
if err != nil {
panic(fmt.Sprintf("colorm: NewShader for a built-in shader failed: %v", err))
}
shader := s
builtinShaders[key] = shader
return shader
}

View File

@ -12,20 +12,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package ebiten_test package colorm_test
import ( import (
"image/color" "image/color"
"math" "math"
"testing" "testing"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/colorm"
) )
func TestColorMInit(t *testing.T) { func TestColorMInit(t *testing.T) {
var m ebiten.ColorM var m colorm.ColorM
for i := 0; i < ebiten.ColorMDim-1; i++ { for i := 0; i < colorm.Dim-1; i++ {
for j := 0; j < ebiten.ColorMDim; j++ { for j := 0; j < colorm.Dim; j++ {
got := m.Element(i, j) got := m.Element(i, j)
want := 0.0 want := 0.0
if i == j { if i == j {
@ -38,8 +38,8 @@ func TestColorMInit(t *testing.T) {
} }
m.SetElement(0, 0, 1) m.SetElement(0, 0, 1)
for i := 0; i < ebiten.ColorMDim-1; i++ { for i := 0; i < colorm.Dim-1; i++ {
for j := 0; j < ebiten.ColorMDim; j++ { for j := 0; j < colorm.Dim; j++ {
got := m.Element(i, j) got := m.Element(i, j)
want := 0.0 want := 0.0
if i == j { if i == j {
@ -53,7 +53,7 @@ func TestColorMInit(t *testing.T) {
} }
func TestColorMAssign(t *testing.T) { func TestColorMAssign(t *testing.T) {
m := ebiten.ColorM{} m := colorm.ColorM{}
m.SetElement(0, 0, 1) m.SetElement(0, 0, 1)
m2 := m m2 := m
m.SetElement(0, 0, 0) m.SetElement(0, 0, 0)
@ -71,7 +71,7 @@ func TestColorMTranslate(t *testing.T) {
{0, 0, 1, 0, 2.5}, {0, 0, 1, 0, 2.5},
{0, 0, 0, 1, 3.5}, {0, 0, 0, 1, 3.5},
} }
m := ebiten.ColorM{} m := colorm.ColorM{}
m.Translate(0.5, 1.5, 2.5, 3.5) m.Translate(0.5, 1.5, 2.5, 3.5)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
for j := 0; j < 5; j++ { for j := 0; j < 5; j++ {
@ -91,7 +91,7 @@ func TestColorMScale(t *testing.T) {
{0, 0, 2.5, 0, 0}, {0, 0, 2.5, 0, 0},
{0, 0, 0, 3.5, 0}, {0, 0, 0, 3.5, 0},
} }
m := ebiten.ColorM{} m := colorm.ColorM{}
m.Scale(0.5, 1.5, 2.5, 3.5) m.Scale(0.5, 1.5, 2.5, 3.5)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
for j := 0; j < 5; j++ { for j := 0; j < 5; j++ {
@ -111,7 +111,7 @@ func TestColorMTranslateAndScale(t *testing.T) {
{0, 0, 1, 0, 0}, {0, 0, 1, 0, 0},
{0, 0, 0, 0.5, 0.5}, {0, 0, 0, 0.5, 0.5},
} }
m := ebiten.ColorM{} m := colorm.ColorM{}
m.Translate(0, 0, 0, 1) m.Translate(0, 0, 0, 1)
m.Scale(1, 1, 1, 0.5) m.Scale(1, 1, 1, 0.5)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
@ -132,7 +132,7 @@ func TestColorMMonochrome(t *testing.T) {
{0.2990, 0.5870, 0.1140, 0, 0}, {0.2990, 0.5870, 0.1140, 0, 0},
{0, 0, 0, 1, 0}, {0, 0, 0, 1, 0},
} }
m := ebiten.ColorM{} m := colorm.ColorM{}
m.ChangeHSV(0, 0, 1) m.ChangeHSV(0, 0, 1)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
for j := 0; j < 5; j++ { for j := 0; j < 5; j++ {
@ -152,7 +152,7 @@ func TestColorMConcatSelf(t *testing.T) {
{30, 43, 51, 39, 34}, {30, 43, 51, 39, 34},
{25, 37, 39, 46, 36}, {25, 37, 39, 46, 36},
} }
m := ebiten.ColorM{} m := colorm.ColorM{}
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
for j := 0; j < 5; j++ { for j := 0; j < 5; j++ {
m.SetElement(i, j, float64((i+j)%5+1)) m.SetElement(i, j, float64((i+j)%5+1))
@ -178,23 +178,23 @@ func absDiffU32(x, y uint32) uint32 {
} }
func TestColorMApply(t *testing.T) { func TestColorMApply(t *testing.T) {
mono := ebiten.ColorM{} mono := colorm.ColorM{}
mono.ChangeHSV(0, 0, 1) mono.ChangeHSV(0, 0, 1)
shiny := ebiten.ColorM{} shiny := colorm.ColorM{}
shiny.Translate(1, 1, 1, 0) shiny.Translate(1, 1, 1, 0)
shift := ebiten.ColorM{} shift := colorm.ColorM{}
shift.Translate(0.5, 0.5, 0.5, 0.5) shift.Translate(0.5, 0.5, 0.5, 0.5)
cases := []struct { cases := []struct {
ColorM ebiten.ColorM ColorM colorm.ColorM
In color.Color In color.Color
Out color.Color Out color.Color
Delta uint32 Delta uint32
}{ }{
{ {
ColorM: ebiten.ColorM{}, ColorM: colorm.ColorM{},
In: color.RGBA{1, 2, 3, 4}, In: color.RGBA{1, 2, 3, 4},
Out: color.RGBA{1, 2, 3, 4}, Out: color.RGBA{1, 2, 3, 4},
Delta: 0x101, Delta: 0x101,
@ -237,7 +237,7 @@ func TestColorMApply(t *testing.T) {
// #1765 // #1765
func TestColorMConcat(t *testing.T) { func TestColorMConcat(t *testing.T) {
var a, b ebiten.ColorM var a, b colorm.ColorM
a.SetElement(1, 2, -1) a.SetElement(1, 2, -1)
a.Concat(b) a.Concat(b)
if got, want := a.Element(1, 2), -1.0; got != want { if got, want := a.Element(1, 2), -1.0; got != want {

121
colorm/draw.go Normal file
View File

@ -0,0 +1,121 @@
// Copyright 2022 The Ebitengine Authors
//
// 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 colorm
import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/internal/builtinshader"
)
// DrawImageOptions represents options for DrawImage.
type DrawImageOptions struct {
// GeoM is a geometry matrix to draw.
// The default (zero) value is identity, which draws the image at (0, 0).
GeoM ebiten.GeoM
// Blend is a blending way of the source color and the destination color.
// The default (zero) value is the regular alpha blending.
Blend ebiten.Blend
// Filter is a type of texture filter.
// The default (zero) value is ebiten.FilterNearest.
Filter ebiten.Filter
}
// DrawImage draws src onto dst.
//
// DrawImage is basically the same as ebiten.DrawImage, but with a color matrix.
func DrawImage(dst, src *ebiten.Image, colorM ColorM, op *DrawImageOptions) {
if op == nil {
op = &DrawImageOptions{}
}
w, h := src.Size()
opShader := &ebiten.DrawRectShaderOptions{}
opShader.GeoM = op.GeoM
opShader.CompositeMode = ebiten.CompositeModeCustom
opShader.Blend = op.Blend
opShader.Uniforms = uniforms(colorM)
opShader.Images[0] = src
s := builtinShader(builtinshader.Filter(op.Filter), builtinshader.AddressUnsafe)
dst.DrawRectShader(w, h, s, opShader)
}
// DrawTrianglesOptions represents options for DrawTriangles.
type DrawTrianglesOptions struct {
// ColorScaleMode is the mode of color scales in vertices.
// The default (zero) value is ebiten.ColorScaleModeStraightAlpha.
ColorScaleMode ebiten.ColorScaleMode
// Blend is a blending way of the source color and the destination color.
// The default (zero) value is the regular alpha blending.
Blend ebiten.Blend
// Filter is a type of texture filter.
// The default (zero) value is ebiten.FilterNearest.
Filter ebiten.Filter
// Address is a sampler address mode.
// The default (zero) value is ebiten.AddressUnsafe.
Address ebiten.Address
// FillRule indicates the rule how an overlapped region is rendered.
//
// The rule EvenOdd is useful when you want to render a complex polygon.
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
// See examples/vector for actual usages.
//
// The default (zero) value is ebiten.FillAll.
FillRule ebiten.FillRule
// AntiAlias indicates whether the rendering uses anti-alias or not.
// AntiAlias is useful especially when you pass vertices from the vector package.
//
// AntiAlias increases internal draw calls and might affect performance.
// Use the build tag `ebitenginedebug` to check the number of draw calls if you care.
//
// The default (zero) value is false.
AntiAlias bool
}
// DrawTriangles draws triangles onto dst.
//
// DrawTriangles is basically the same as ebiten.DrawTriangles, but with a color matrix.
func DrawTriangles(dst *ebiten.Image, vertices []ebiten.Vertex, indices []uint16, img *ebiten.Image, colorM ColorM, op *DrawTrianglesOptions) {
if op == nil {
op = &DrawTrianglesOptions{}
}
if op.ColorScaleMode == ebiten.ColorScaleModeStraightAlpha {
vs := make([]ebiten.Vertex, len(vertices))
copy(vs, vertices)
for i := range vertices {
vs[i].ColorR *= vs[i].ColorA
vs[i].ColorG *= vs[i].ColorA
vs[i].ColorB *= vs[i].ColorA
}
vertices = vs
}
opShader := &ebiten.DrawTrianglesShaderOptions{}
opShader.CompositeMode = ebiten.CompositeModeCustom
opShader.Blend = op.Blend
opShader.FillRule = op.FillRule
opShader.AntiAlias = op.AntiAlias
opShader.Uniforms = uniforms(colorM)
opShader.Images[0] = img
s := builtinShader(builtinshader.Filter(op.Filter), builtinshader.Address(op.Address))
dst.DrawTrianglesShader(vertices, indices, s, opShader)
}

301
colorm/draw_test.go Normal file
View File

@ -0,0 +1,301 @@
// Copyright 2022 The Ebitengine Authors
//
// 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 colorm_test
import (
"fmt"
"image/color"
"math"
"testing"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/colorm"
t "github.com/hajimehoshi/ebiten/v2/internal/testing"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
// sameColors compares c1 and c2 and returns a boolean value indicating
// if the two colors are (almost) same.
//
// Pixels read from GPU might include errors (#492), and
// sameColors considers such errors as delta.
func sameColors(c1, c2 color.RGBA, delta int) bool {
return abs(int(c1.R)-int(c2.R)) <= delta &&
abs(int(c1.G)-int(c2.G)) <= delta &&
abs(int(c1.B)-int(c2.B)) <= delta &&
abs(int(c1.A)-int(c2.A)) <= delta
}
func TestMain(m *testing.M) {
ui.SetPanicOnErrorOnReadingPixelsForTesting(true)
t.MainWithRunLoop(m)
}
func TestDrawTrianglesWithColorM(t *testing.T) {
const w, h = 16, 16
dst0 := ebiten.NewImage(w, h)
src := ebiten.NewImage(w, h)
src.Fill(color.White)
vs0 := []ebiten.Vertex{
{
DstX: 0,
DstY: 0,
SrcX: 0,
SrcY: 0,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
{
DstX: w,
DstY: 0,
SrcX: w,
SrcY: 0,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
{
DstX: 0,
DstY: h,
SrcX: 0,
SrcY: h,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
{
DstX: w,
DstY: h,
SrcX: w,
SrcY: h,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
}
var cm colorm.ColorM
cm.Scale(0.2, 0.4, 0.6, 0.8)
op := &colorm.DrawTrianglesOptions{}
is := []uint16{0, 1, 2, 1, 2, 3}
colorm.DrawTriangles(dst0, vs0, is, src, cm, op)
for _, format := range []ebiten.ColorScaleMode{
ebiten.ColorScaleModeStraightAlpha,
ebiten.ColorScaleModePremultipliedAlpha,
} {
format := format
t.Run(fmt.Sprintf("format%d", format), func(t *testing.T) {
var cr, cg, cb, ca float32
switch format {
case ebiten.ColorScaleModeStraightAlpha:
// The values are the same as ColorM.Scale
cr = 0.2
cg = 0.4
cb = 0.6
ca = 0.8
case ebiten.ColorScaleModePremultipliedAlpha:
cr = 0.2 * 0.8
cg = 0.4 * 0.8
cb = 0.6 * 0.8
ca = 0.8
}
vs1 := []ebiten.Vertex{
{
DstX: 0,
DstY: 0,
SrcX: 0,
SrcY: 0,
ColorR: cr,
ColorG: cg,
ColorB: cb,
ColorA: ca,
},
{
DstX: w,
DstY: 0,
SrcX: w,
SrcY: 0,
ColorR: cr,
ColorG: cg,
ColorB: cb,
ColorA: ca,
},
{
DstX: 0,
DstY: h,
SrcX: 0,
SrcY: h,
ColorR: cr,
ColorG: cg,
ColorB: cb,
ColorA: ca,
},
{
DstX: w,
DstY: h,
SrcX: w,
SrcY: h,
ColorR: cr,
ColorG: cg,
ColorB: cb,
ColorA: ca,
},
}
dst1 := ebiten.NewImage(w, h)
op := &ebiten.DrawTrianglesOptions{}
op.ColorScaleMode = format
dst1.DrawTriangles(vs1, is, src, op)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst0.At(i, j)
want := dst1.At(i, j)
if got != want {
t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
})
}
}
func TestColorMAndScale(t *testing.T) {
const w, h = 16, 16
src := ebiten.NewImage(w, h)
src.Fill(color.RGBA{0x80, 0x80, 0x80, 0x80})
vs := []ebiten.Vertex{
{
SrcX: 0,
SrcY: 0,
DstX: 0,
DstY: 0,
ColorR: 0.5,
ColorG: 0.25,
ColorB: 0.5,
ColorA: 0.75,
},
{
SrcX: w,
SrcY: 0,
DstX: w,
DstY: 0,
ColorR: 0.5,
ColorG: 0.25,
ColorB: 0.5,
ColorA: 0.75,
},
{
SrcX: 0,
SrcY: h,
DstX: 0,
DstY: h,
ColorR: 0.5,
ColorG: 0.25,
ColorB: 0.5,
ColorA: 0.75,
},
{
SrcX: w,
SrcY: h,
DstX: w,
DstY: h,
ColorR: 0.5,
ColorG: 0.25,
ColorB: 0.5,
ColorA: 0.75,
},
}
is := []uint16{0, 1, 2, 1, 2, 3}
for _, format := range []ebiten.ColorScaleMode{
ebiten.ColorScaleModeStraightAlpha,
ebiten.ColorScaleModePremultipliedAlpha,
} {
format := format
t.Run(fmt.Sprintf("format%d", format), func(t *testing.T) {
dst := ebiten.NewImage(w, h)
var cm colorm.ColorM
cm.Translate(0.25, 0.25, 0.25, 0)
op := &colorm.DrawTrianglesOptions{}
op.ColorScaleMode = format
colorm.DrawTriangles(dst, vs, is, src, cm, op)
got := dst.At(0, 0).(color.RGBA)
alphaBeforeScale := 0.5
var want color.RGBA
switch format {
case ebiten.ColorScaleModeStraightAlpha:
want = color.RGBA{
byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.5 * 0.75)),
byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.25 * 0.75)),
byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.5 * 0.75)),
byte(math.Floor(0xff * alphaBeforeScale * 0.75)),
}
case ebiten.ColorScaleModePremultipliedAlpha:
want = color.RGBA{
byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.5)),
byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.25)),
byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.5)),
byte(math.Floor(0xff * alphaBeforeScale * 0.75)),
}
}
if !sameColors(got, want, 2) {
t.Errorf("got: %v, want: %v", got, want)
}
})
}
}
// Issue #1213
func TestColorMCopy(t *testing.T) {
const w, h = 16, 16
dst := ebiten.NewImage(w, h)
src := ebiten.NewImage(w, h)
for k := 0; k < 256; k++ {
var cm colorm.ColorM
cm.Translate(1, 1, 1, float64(k)/0xff)
op := &colorm.DrawImageOptions{}
op.Blend = ebiten.BlendCopy
colorm.DrawImage(dst, src, cm, op)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
want := color.RGBA{byte(k), byte(k), byte(k), byte(k)}
if !sameColors(got, want, 1) {
t.Fatalf("dst.At(%d, %d), k: %d: got %v, want %v", i, j, k, got, want)
}
}
}
}
}

View File

@ -20,6 +20,7 @@ import (
) )
// ColorScale represents a scale of RGBA color. // ColorScale represents a scale of RGBA color.
// ColorScale is intended to be applied to a premultiplied-alpha color value.
// //
// The initial (zero) value of ColorScale is an identity scale (1, 1, 1, 1). // The initial (zero) value of ColorScale is an identity scale (1, 1, 1, 1).
type ColorScale struct { type ColorScale struct {
@ -33,6 +34,14 @@ func (c *ColorScale) String() string {
return fmt.Sprintf("(%f,%f,%f,%f)", c.r_1+1, c.g_1+1, c.b_1+1, c.a_1+1) return fmt.Sprintf("(%f,%f,%f,%f)", c.r_1+1, c.g_1+1, c.b_1+1, c.a_1+1)
} }
// Reset resets the ColorScale as identity.
func (c *ColorScale) Reset() {
c.r_1 = 0
c.g_1 = 0
c.b_1 = 0
c.a_1 = 0
}
// R returns the red scale. // R returns the red scale.
func (c *ColorScale) R() float32 { func (c *ColorScale) R() float32 {
return c.r_1 + 1 return c.r_1 + 1
@ -85,8 +94,20 @@ func (c *ColorScale) Scale(r, g, b, a float32) {
c.a_1 = (c.a_1+1)*a - 1 c.a_1 = (c.a_1+1)*a - 1
} }
// ScaleAlpha multiplies the given alpha value to the current scale.
func (c *ColorScale) ScaleAlpha(a float32) {
c.r_1 = (c.r_1+1)*a - 1
c.g_1 = (c.g_1+1)*a - 1
c.b_1 = (c.b_1+1)*a - 1
c.a_1 = (c.a_1+1)*a - 1
}
// ScaleWithColor multiplies the given color values to the current scale. // ScaleWithColor multiplies the given color values to the current scale.
func (c *ColorScale) ScaleWithColor(clr color.Color) { func (c *ColorScale) ScaleWithColor(clr color.Color) {
cr, cg, cb, ca := clr.RGBA() cr, cg, cb, ca := clr.RGBA()
c.Scale(float32(cr)/0xffff, float32(cg)/0xffff, float32(cb)/0xffff, float32(ca)/0xffff) c.Scale(float32(cr)/0xffff, float32(cg)/0xffff, float32(cb)/0xffff, float32(ca)/0xffff)
} }
func (c *ColorScale) apply(r, g, b, a float32) (float32, float32, float32, float32) {
return (c.r_1 + 1) * r, (c.g_1 + 1) * g, (c.b_1 + 1) * b, (c.a_1 + 1) * a
}

View File

@ -129,7 +129,7 @@ func (b *Board) Draw(boardImage *ebiten.Image) {
x := i*tileSize + (i+1)*tileMargin x := i*tileSize + (i+1)*tileMargin
y := j*tileSize + (j+1)*tileMargin y := j*tileSize + (j+1)*tileMargin
op.GeoM.Translate(float64(x), float64(y)) op.GeoM.Translate(float64(x), float64(y))
op.ColorM.ScaleWithColor(tileBackgroundColor(v)) op.ColorScale.ScaleWithColor(tileBackgroundColor(v))
boardImage.DrawImage(tileImage, op) boardImage.DrawImage(tileImage, op)
} }
} }

View File

@ -383,7 +383,7 @@ func (t *Tile) Draw(boardImage *ebiten.Image) {
op.GeoM.Translate(float64(tileSize/2), float64(tileSize/2)) op.GeoM.Translate(float64(tileSize/2), float64(tileSize/2))
} }
op.GeoM.Translate(float64(x), float64(y)) op.GeoM.Translate(float64(x), float64(y))
op.ColorM.ScaleWithColor(tileBackgroundColor(v)) op.ColorScale.ScaleWithColor(tileBackgroundColor(v))
boardImage.DrawImage(tileImage, op) boardImage.DrawImage(tileImage, op)
str := strconv.Itoa(v) str := strconv.Itoa(v)

View File

@ -61,7 +61,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
// Draw 100 Ebitens // Draw 100 Ebitens
v := g.offset() v := g.offset()
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.ColorM.Scale(1.0, 1.0, 1.0, 0.5) op.ColorScale.ScaleAlpha(0.5)
for i := 0; i < 10*10; i++ { for i := 0; i < 10*10; i++ {
op.GeoM.Reset() op.GeoM.Reset()
x := float64(i%10)*v + 15 x := float64(i%10)*v + 15

View File

@ -16,6 +16,7 @@ package blocks
import ( import (
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/colorm"
) )
const maxFlushCount = 20 const maxFlushCount = 20
@ -193,8 +194,8 @@ func min(a, b float64) float64 {
return a return a
} }
func flushingColor(rate float64) ebiten.ColorM { func flushingColor(rate float64) colorm.ColorM {
clr := ebiten.ColorM{} var clr colorm.ColorM
alpha := min(1, rate*2) alpha := min(1, rate*2)
clr.Scale(1, 1, 1, alpha) clr.Scale(1, 1, 1, alpha)
r := min(1, (1-rate)*2) r := min(1, (1-rate)*2)
@ -211,7 +212,7 @@ func (f *Field) Draw(r *ebiten.Image, x, y int) {
} }
} else { } else {
for i := 0; i < fieldBlockCountX; i++ { for i := 0; i < fieldBlockCountX; i++ {
drawBlock(r, f.blocks[i][j], i*blockWidth+x, j*blockHeight+y, ebiten.ColorM{}) drawBlock(r, f.blocks[i][j], i*blockWidth+x, j*blockHeight+y, colorm.ColorM{})
} }
} }
} }

View File

@ -25,6 +25,7 @@ import (
"time" "time"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/colorm"
"github.com/hajimehoshi/ebiten/v2/examples/resources/images" "github.com/hajimehoshi/ebiten/v2/examples/resources/images"
"github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/vector" "github.com/hajimehoshi/ebiten/v2/vector"
@ -148,17 +149,17 @@ func NewGameScene() *GameScene {
} }
var ( var (
lightGray ebiten.ColorM lightGray colorm.ColorM
) )
func init() { func init() {
id := ebiten.ColorM{} var id colorm.ColorM
mono := ebiten.ColorM{} var mono colorm.ColorM
mono.ChangeHSV(0, 0, 1) mono.ChangeHSV(0, 0, 1)
for j := 0; j < ebiten.ColorMDim-1; j++ { for j := 0; j < colorm.Dim-1; j++ {
for i := 0; i < ebiten.ColorMDim-1; i++ { for i := 0; i < colorm.Dim-1; i++ {
lightGray.SetElement(i, j, mono.Element(i, j)*0.7+id.Element(i, j)*0.3) lightGray.SetElement(i, j, mono.Element(i, j)*0.7+id.Element(i, j)*0.3)
} }
} }
@ -177,13 +178,12 @@ func (s *GameScene) drawBackground(r *ebiten.Image) {
scale = scaleH scale = scaleH
} }
op := &ebiten.DrawImageOptions{} op := &colorm.DrawImageOptions{}
op.GeoM.Translate(-float64(w)/2, -float64(h)/2) op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
op.GeoM.Scale(scale, scale) op.GeoM.Scale(scale, scale)
op.GeoM.Translate(ScreenWidth/2, ScreenHeight/2) op.GeoM.Translate(ScreenWidth/2, ScreenHeight/2)
op.ColorM = lightGray
op.Filter = ebiten.FilterLinear op.Filter = ebiten.FilterLinear
r.DrawImage(imageGameBG, op) colorm.DrawImage(r, imageGameBG, lightGray, op)
} }
const ( const (

View File

@ -20,6 +20,7 @@ import (
_ "image/png" _ "image/png"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/colorm"
rblocks "github.com/hajimehoshi/ebiten/v2/examples/resources/images/blocks" rblocks "github.com/hajimehoshi/ebiten/v2/examples/resources/images/blocks"
) )
@ -165,17 +166,16 @@ const (
fieldBlockCountY = 20 fieldBlockCountY = 20
) )
func drawBlock(r *ebiten.Image, block BlockType, x, y int, clr ebiten.ColorM) { func drawBlock(r *ebiten.Image, block BlockType, x, y int, clr colorm.ColorM) {
if block == BlockTypeNone { if block == BlockTypeNone {
return return
} }
op := &ebiten.DrawImageOptions{} op := &colorm.DrawImageOptions{}
op.ColorM = clr
op.GeoM.Translate(float64(x), float64(y)) op.GeoM.Translate(float64(x), float64(y))
srcX := (int(block) - 1) * blockWidth srcX := (int(block) - 1) * blockWidth
r.DrawImage(imageBlocks.SubImage(image.Rect(srcX, 0, srcX+blockWidth, blockHeight)).(*ebiten.Image), op) colorm.DrawImage(r, imageBlocks.SubImage(image.Rect(srcX, 0, srcX+blockWidth, blockHeight)).(*ebiten.Image), clr, op)
} }
func (p *Piece) InitialPosition() (int, int) { func (p *Piece) InitialPosition() (int, int) {
@ -249,7 +249,7 @@ func (p *Piece) Draw(r *ebiten.Image, x, y int, angle Angle) {
for i := range p.blocks { for i := range p.blocks {
for j := range p.blocks[i] { for j := range p.blocks[i] {
if p.isBlocked(i, j, angle) { if p.isBlocked(i, j, angle) {
drawBlock(r, p.blockType, i*blockWidth+x, j*blockHeight+y, ebiten.ColorM{}) drawBlock(r, p.blockType, i*blockWidth+x, j*blockHeight+y, colorm.ColorM{})
} }
} }
} }

View File

@ -73,9 +73,9 @@ func (s *SceneManager) Draw(r *ebiten.Image) {
r.DrawImage(transitionFrom, nil) r.DrawImage(transitionFrom, nil)
alpha := 1 - float64(s.transitionCount)/float64(transitionMaxCount) alpha := 1 - float32(s.transitionCount)/float32(transitionMaxCount)
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.ColorM.Scale(1, 1, 1, alpha) op.ColorScale.ScaleAlpha(alpha)
r.DrawImage(transitionTo, op) r.DrawImage(transitionTo, op)
} }

View File

@ -78,7 +78,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
// A_{n+1} = A_n * (1 - 1/(n+1)) + a_{n+1} * 1/(n+1) // A_{n+1} = A_n * (1 - 1/(n+1)) + a_{n+1} * 1/(n+1)
// which is precisely what an alpha blend with alpha 1/(n+1) does. // which is precisely what an alpha blend with alpha 1/(n+1) does.
layers++ layers++
op.ColorM.Scale(1, 1, 1, 1.0/float64(layers)) op.ColorScale.ScaleAlpha(1 / float32(layers))
screen.DrawImage(gophersImage, op) screen.DrawImage(gophersImage, op)
} }
} }

View File

@ -96,7 +96,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
screen.Fill(color.Black) screen.Fill(color.Black)
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.ColorM.Scale(200.0/255.0, 200.0/255.0, 200.0/255.0, 1) op.ColorScale.Scale(200.0/255.0, 200.0/255.0, 200.0/255.0, 1)
g.space.EachBody(func(body *cp.Body) { g.space.EachBody(func(body *cp.Body) {
op.GeoM.Reset() op.GeoM.Reset()

View File

@ -77,10 +77,10 @@ func (s *Sprite) MoveBy(x, y int) {
} }
// Draw draws the sprite. // Draw draws the sprite.
func (s *Sprite) Draw(screen *ebiten.Image, dx, dy int, alpha float64) { func (s *Sprite) Draw(screen *ebiten.Image, dx, dy int, alpha float32) {
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(s.x+dx), float64(s.y+dy)) op.GeoM.Translate(float64(s.x+dx), float64(s.y+dy))
op.ColorM.Scale(1, 1, 1, alpha) op.ColorScale.ScaleAlpha(alpha)
screen.DrawImage(s.image, op) screen.DrawImage(s.image, op)
screen.DrawImage(s.image, op) screen.DrawImage(s.image, op)
} }

View File

@ -22,6 +22,7 @@ import (
"log" "log"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/colorm"
"github.com/hajimehoshi/ebiten/v2/examples/resources/images" "github.com/hajimehoshi/ebiten/v2/examples/resources/images"
) )
@ -66,20 +67,21 @@ func (g *Game) Draw(screen *ebiten.Image) {
// Fill with solid colors // Fill with solid colors
for i, c := range colors { for i, c := range colors {
op := &ebiten.DrawImageOptions{} op := &colorm.DrawImageOptions{}
x := i % 4 x := i % 4
y := i/4 + 1 y := i/4 + 1
op.GeoM.Translate(ox+float64(dx*x), oy+float64(dy*y)) op.GeoM.Translate(ox+float64(dx*x), oy+float64(dy*y))
// Reset RGB (not Alpha) 0 forcibly // Reset RGB (not Alpha) 0 forcibly
op.ColorM.Scale(0, 0, 0, 1) var cm colorm.ColorM
cm.Scale(0, 0, 0, 1)
// Set color // Set color
r := float64(c.R) / 0xff r := float64(c.R) / 0xff
g := float64(c.G) / 0xff g := float64(c.G) / 0xff
b := float64(c.B) / 0xff b := float64(c.B) / 0xff
op.ColorM.Translate(r, g, b, 0) cm.Translate(r, g, b, 0)
screen.DrawImage(ebitenImage, op) colorm.DrawImage(screen, ebitenImage, cm, op)
} }
} }

View File

@ -23,6 +23,7 @@ import (
"math" "math"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/colorm"
"github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/examples/resources/images" "github.com/hajimehoshi/ebiten/v2/examples/resources/images"
"github.com/hajimehoshi/ebiten/v2/inpututil" "github.com/hajimehoshi/ebiten/v2/inpututil"
@ -100,7 +101,7 @@ func (g *Game) Update() error {
func (g *Game) Draw(screen *ebiten.Image) { func (g *Game) Draw(screen *ebiten.Image) {
// Center the image on the screen. // Center the image on the screen.
w, h := gophersImage.Size() w, h := gophersImage.Size()
op := &ebiten.DrawImageOptions{} op := &colorm.DrawImageOptions{}
op.GeoM.Translate(-float64(w)/2, -float64(h)/2) op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
op.GeoM.Scale(2, 2) op.GeoM.Scale(2, 2)
op.GeoM.Translate(float64(screenWidth)/2, float64(screenHeight)/2) op.GeoM.Translate(float64(screenWidth)/2, float64(screenHeight)/2)
@ -109,15 +110,16 @@ func (g *Game) Draw(screen *ebiten.Image) {
hue := float64(g.hue128) * 2 * math.Pi / 128 hue := float64(g.hue128) * 2 * math.Pi / 128
saturation := float64(g.saturation128) / 128 saturation := float64(g.saturation128) / 128
value := float64(g.value128) / 128 value := float64(g.value128) / 128
op.ColorM.ChangeHSV(hue, saturation, value) var c colorm.ColorM
c.ChangeHSV(hue, saturation, value)
// Invert the color. // Invert the color.
if g.inverted { if g.inverted {
op.ColorM.Scale(-1, -1, -1, 1) c.Scale(-1, -1, -1, 1)
op.ColorM.Translate(1, 1, 1, 0) c.Translate(1, 1, 1, 0)
} }
screen.DrawImage(gophersImage, op) colorm.DrawImage(screen, gophersImage, c, op)
// Draw the text of the current status. // Draw the text of the current status.
msgInverted := "false" msgInverted := "false"

View File

@ -22,6 +22,7 @@ import (
"math" "math"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/colorm"
"github.com/hajimehoshi/ebiten/v2/examples/resources/images" "github.com/hajimehoshi/ebiten/v2/examples/resources/images"
) )
@ -46,15 +47,15 @@ func (g *Game) Update() error {
func (g *Game) Draw(screen *ebiten.Image) { func (g *Game) Draw(screen *ebiten.Image) {
// Center the image on the screen. // Center the image on the screen.
w, h := gophersImage.Size() w, h := gophersImage.Size()
op := &ebiten.DrawImageOptions{} op := &colorm.DrawImageOptions{}
op.GeoM.Translate(-float64(w)/2, -float64(h)/2) op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
op.GeoM.Scale(2, 2) op.GeoM.Scale(2, 2)
op.GeoM.Translate(float64(screenWidth)/2, float64(screenHeight)/2) op.GeoM.Translate(float64(screenWidth)/2, float64(screenHeight)/2)
// Rotate the hue. // Rotate the hue.
op.ColorM.RotateHue(float64(g.count%360) * 2 * math.Pi / 360) var c colorm.ColorM
c.RotateHue(float64(g.count%360) * 2 * math.Pi / 360)
screen.DrawImage(gophersImage, op) colorm.DrawImage(screen, gophersImage, c, op)
} }
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {

View File

@ -62,7 +62,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
// Draw the base (grayed) keyboard image. // Draw the base (grayed) keyboard image.
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(offsetX, offsetY) op.GeoM.Translate(offsetX, offsetY)
op.ColorM.Scale(0.5, 0.5, 0.5, 1) op.ColorScale.Scale(0.5, 0.5, 0.5, 1)
screen.DrawImage(keyboardImage, op) screen.DrawImage(keyboardImage, op)
// Draw the highlighted keys. // Draw the highlighted keys.

View File

@ -22,6 +22,7 @@ import (
"math" "math"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/colorm"
"github.com/hajimehoshi/ebiten/v2/ebitenutil" "github.com/hajimehoshi/ebiten/v2/ebitenutil"
) )
@ -115,14 +116,15 @@ func (g *Game) Update() error {
// paint draws the brush on the given canvas image at the position (x, y). // paint draws the brush on the given canvas image at the position (x, y).
func (g *Game) paint(canvas *ebiten.Image, x, y int) { func (g *Game) paint(canvas *ebiten.Image, x, y int) {
op := &ebiten.DrawImageOptions{} op := &colorm.DrawImageOptions{}
op.GeoM.Translate(float64(x), float64(y)) op.GeoM.Translate(float64(x), float64(y))
var cm colorm.ColorM
// Scale the color and rotate the hue so that colors vary on each frame. // Scale the color and rotate the hue so that colors vary on each frame.
op.ColorM.Scale(1.0, 0.50, 0.125, 1.0) cm.Scale(1.0, 0.50, 0.125, 1.0)
tps := ebiten.TPS() tps := ebiten.TPS()
theta := 2.0 * math.Pi * float64(g.count%tps) / float64(tps) theta := 2.0 * math.Pi * float64(g.count%tps) / float64(tps)
op.ColorM.RotateHue(theta) cm.RotateHue(theta)
canvas.DrawImage(brushImage, op) colorm.DrawImage(canvas, brushImage, cm, op)
} }
func (g *Game) Draw(screen *ebiten.Image) { func (g *Game) Draw(screen *ebiten.Image) {

View File

@ -59,7 +59,7 @@ type sprite struct {
img *ebiten.Image img *ebiten.Image
scale float64 scale float64
angle float64 angle float64
alpha float64 alpha float32
} }
func (s *sprite) update() { func (s *sprite) update() {
@ -94,8 +94,8 @@ func (s *sprite) draw(screen *ebiten.Image) {
op.GeoM.Translate(x, y) op.GeoM.Translate(x, y)
op.GeoM.Translate(ox, oy) op.GeoM.Translate(ox, oy)
rate := float64(s.count) / float64(s.maxCount) rate := float32(s.count) / float32(s.maxCount)
alpha := 0.0 var alpha float32
if rate < 0.2 { if rate < 0.2 {
alpha = rate / 0.2 alpha = rate / 0.2
} else if rate > 0.8 { } else if rate > 0.8 {
@ -104,7 +104,7 @@ func (s *sprite) draw(screen *ebiten.Image) {
alpha = 1 alpha = 1
} }
alpha *= s.alpha alpha *= s.alpha
op.ColorM.Scale(1, 1, 1, alpha) op.ColorScale.ScaleAlpha(alpha)
screen.DrawImage(s.img, op) screen.DrawImage(s.img, op)
} }

View File

@ -253,7 +253,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
// Draw shadow // Draw shadow
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.ColorM.Scale(1, 1, 1, 0.7) op.ColorScale.ScaleAlpha(0.7)
screen.DrawImage(shadowImage, op) screen.DrawImage(shadowImage, op)
// Draw walls // Draw walls

View File

@ -50,7 +50,7 @@ func init() {
ebitenImage = ebiten.NewImage(w, h) ebitenImage = ebiten.NewImage(w, h)
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.ColorM.Scale(1, 1, 1, 0.5) op.ColorScale.ScaleAlpha(0.5)
ebitenImage.DrawImage(origEbitenImage, op) ebitenImage.DrawImage(origEbitenImage, op)
} }

View File

@ -51,7 +51,7 @@ func init() {
ebitenImage = ebiten.NewImage(w, h) ebitenImage = ebiten.NewImage(w, h)
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.ColorM.Scale(1, 1, 1, 0.5) op.ColorScale.ScaleAlpha(0.5)
ebitenImage.DrawImage(origEbitenImage, op) ebitenImage.DrawImage(origEbitenImage, op)
} }

View File

@ -127,20 +127,20 @@ func (g *Game) Draw(screen *ebiten.Image) {
op.GeoM.Reset() op.GeoM.Reset()
op.GeoM.Translate(x, y) op.GeoM.Translate(x, y)
op.GeoM.Translate(gl.X, gl.Y) op.GeoM.Translate(gl.X, gl.Y)
op.ColorM.Reset() op.ColorScale.Reset()
r := 1.0 r := float32(1)
if i%3 == 0 { if i%3 == 0 {
r = 0.5 r = 0.5
} }
g := 1.0 g := float32(1)
if i%3 == 1 { if i%3 == 1 {
g = 0.5 g = 0.5
} }
b := 1.0 b := float32(1)
if i%3 == 2 { if i%3 == 2 {
b = 0.5 b = 0.5
} }
op.ColorM.Scale(r, g, b, 1) op.ColorScale.Scale(r, g, b, 1)
screen.DrawImage(gl.Image, op) screen.DrawImage(gl.Image, op)
} }
} }

View File

@ -102,8 +102,16 @@ type DrawImageOptions struct {
// The default (zero) value is identity, which draws the image at (0, 0). // The default (zero) value is identity, which draws the image at (0, 0).
GeoM GeoM GeoM GeoM
// ColorScale is a scale of color.
// ColorScale is slightly different from ColorM's Scale in terms of alphas:
// ColorScale is applied to premultiplied-alpha colors, while ColorM is applied to straight-alpha colors.
// The default (zero) value is identity, which is (1, 1, 1, 1).
ColorScale ColorScale
// ColorM is a color matrix to draw. // ColorM is a color matrix to draw.
// The default (zero) value is identity, which doesn't change any color. // The default (zero) value is identity, which doesn't change any color.
//
// Deprecated: as of v2.5. Use ColorScale or the package colorm instead.
ColorM ColorM ColorM ColorM
// CompositeMode is a composite mode to draw. // CompositeMode is a composite mode to draw.
@ -231,6 +239,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
sx0, sy0 := img.adjustPosition(bounds.Min.X, bounds.Min.Y) sx0, sy0 := img.adjustPosition(bounds.Min.X, bounds.Min.Y)
sx1, sy1 := img.adjustPosition(bounds.Max.X, bounds.Max.Y) sx1, sy1 := img.adjustPosition(bounds.Max.X, bounds.Max.Y)
colorm, cr, cg, cb, ca := colorMToScale(options.ColorM.affineColorM()) colorm, cr, cg, cb, ca := colorMToScale(options.ColorM.affineColorM())
cr, cg, cb, ca = options.ColorScale.apply(cr, cg, cb, ca)
vs := graphics.QuadVertices(float32(sx0), float32(sy0), float32(sx1), float32(sy1), a, b, c, d, tx, ty, cr, cg, cb, ca) vs := graphics.QuadVertices(float32(sx0), float32(sy0), float32(sx1), float32(sy1), a, b, c, d, tx, ty, cr, cg, cb, ca)
is := graphics.QuadIndices() is := graphics.QuadIndices()
@ -324,9 +333,12 @@ type DrawTrianglesOptions struct {
// ColorM is a color matrix to draw. // ColorM is a color matrix to draw.
// The default (zero) value is identity, which doesn't change any color. // The default (zero) value is identity, which doesn't change any color.
// ColorM is applied before vertex color scale is applied. // ColorM is applied before vertex color scale is applied.
//
// Deprecated: as of v2.5. Use the package colorm instead.
ColorM ColorM ColorM ColorM
// ColorScaleMode is the mode of color scales in vertices. // ColorScaleMode is the mode of color scales in vertices.
// ColorScaleMode affects the color calculation with vertex colors, but doesn't affect with a color matrix.
// The default (zero) value is ColorScaleModeStraightAlpha. // The default (zero) value is ColorScaleModeStraightAlpha.
ColorScaleMode ColorScaleMode ColorScaleMode ColorScaleMode

View File

@ -1009,12 +1009,12 @@ func TestImageMipmapColor(t *testing.T) {
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.Filter = ebiten.FilterLinear op.Filter = ebiten.FilterLinear
op.GeoM.Scale(s, s) op.GeoM.Scale(s, s)
op.ColorM.Scale(1, 1, 0, 1) op.ColorScale.Scale(1, 1, 0, 1)
img0.DrawImage(img1, op) img0.DrawImage(img1, op)
op.GeoM.Translate(128, 0) op.GeoM.Translate(128, 0)
op.ColorM.Reset() op.ColorScale.Reset()
op.ColorM.Scale(0, 1, 1, 1) op.ColorScale.Scale(0, 1, 1, 1)
img0.DrawImage(img1, op) img0.DrawImage(img1, op)
want := color.RGBA{0, 0xff, 0xff, 0xff} want := color.RGBA{0, 0xff, 0xff, 0xff}
@ -3967,6 +3967,30 @@ func TestImageColorMScale(t *testing.T) {
} }
} }
func TestImageColorScaleAndColorM(t *testing.T) {
const w, h = 16, 16
dst0 := ebiten.NewImage(w, h)
dst1 := ebiten.NewImage(w, h)
src := ebiten.NewImage(w, h)
src.Fill(color.RGBA{0x24, 0x3f, 0x6a, 0x88})
// ColorScale is applied to premultiplied-alpha colors.
op := &ebiten.DrawImageOptions{}
op.ColorScale.Scale(0.3*0.6, 0.4*0.6, 0.5*0.6, 0.6)
dst0.DrawImage(src, op)
// ColorM.Scale is applied to straight-alpha colors.
op = &ebiten.DrawImageOptions{}
op.ColorM.Scale(0.3, 0.4, 0.5, 0.6)
dst1.DrawImage(src, op)
got := dst0.At(0, 0)
want := dst1.At(0, 0)
if got != want {
t.Errorf("got: %v, want: %v", got, want)
}
}
// Issue #2428 // Issue #2428
func TestImageSetAndSubImage(t *testing.T) { func TestImageSetAndSubImage(t *testing.T) {
const w, h = 16, 16 const w, h = 16, 16

View File

@ -181,7 +181,7 @@ var textM sync.Mutex
func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) { func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(x), float64(y)) op.GeoM.Translate(float64(x), float64(y))
op.ColorM.ScaleWithColor(clr) op.ColorScale.ScaleWithColor(clr)
DrawWithOptions(dst, text, face, op) DrawWithOptions(dst, text, face, op)
} }