ebiten: add ColorScaleFormat to DrawTrianglesOptions

Closes #2365
This commit is contained in:
Hajime Hoshi 2022-10-02 13:44:18 +09:00
parent 5e459bbe42
commit 058b8d5635
2 changed files with 185 additions and 101 deletions

View File

@ -286,6 +286,19 @@ const (
EvenOdd EvenOdd
) )
// ColorScaleFormat is the format of color scales in vertices.
type ColorScaleFormat int
const (
// ColorScaleFormatStraightAlpha indicates color scales in vertices are
// straight-alpha encoded color multiplier.
ColorScaleFormatStraightAlpha ColorScaleFormat = iota
// ColorScaleFormatStraightAlpha indicates color scales in vertices are
// premultiplied-alpha encoded color multiplier.
ColorScaleFormatPremultipliedAlpha
)
// DrawTrianglesOptions represents options for DrawTriangles. // DrawTrianglesOptions represents options for DrawTriangles.
type DrawTrianglesOptions struct { type DrawTrianglesOptions struct {
// ColorM is a color matrix to draw. // ColorM is a color matrix to draw.
@ -293,6 +306,10 @@ type DrawTrianglesOptions struct {
// ColorM is applied before vertex color scale is applied. // ColorM is applied before vertex color scale is applied.
ColorM ColorM ColorM ColorM
// ColorScaleFormat is the format of color scales in vertices.
// The default (zero) value is ColorScaleFormatStraightAlpha.
ColorScaleFormat
// CompositeMode is a composite mode to draw. // CompositeMode is a composite mode to draw.
// The default (zero) value is regular alpha blending. // The default (zero) value is regular alpha blending.
CompositeMode CompositeMode CompositeMode CompositeMode
@ -377,6 +394,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
vs := graphics.Vertices(len(vertices)) vs := graphics.Vertices(len(vertices))
dst := i dst := i
if options.ColorScaleFormat == ColorScaleFormatStraightAlpha {
for i, v := range vertices { for i, v := range vertices {
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY) dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
vs[i*graphics.VertexFloatCount] = dx vs[i*graphics.VertexFloatCount] = dx
@ -389,6 +407,20 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
vs[i*graphics.VertexFloatCount+6] = v.ColorB * v.ColorA * cb vs[i*graphics.VertexFloatCount+6] = v.ColorB * v.ColorA * cb
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca
} }
} else {
for i, v := range vertices {
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
vs[i*graphics.VertexFloatCount] = dx
vs[i*graphics.VertexFloatCount+1] = dy
sx, sy := img.adjustPositionF32(v.SrcX, v.SrcY)
vs[i*graphics.VertexFloatCount+2] = sx
vs[i*graphics.VertexFloatCount+3] = sy
vs[i*graphics.VertexFloatCount+4] = v.ColorR * cr
vs[i*graphics.VertexFloatCount+5] = v.ColorG * cg
vs[i*graphics.VertexFloatCount+6] = v.ColorB * cb
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca
}
}
is := make([]uint16, len(indices)) is := make([]uint16, len(indices))
copy(is, indices) copy(is, indices)

View File

@ -1929,7 +1929,6 @@ func TestImageWritePixelsOnSubImage(t *testing.T) {
func TestImageDrawTrianglesWithColorM(t *testing.T) { func TestImageDrawTrianglesWithColorM(t *testing.T) {
const w, h = 16, 16 const w, h = 16, 16
dst0 := ebiten.NewImage(w, h) dst0 := ebiten.NewImage(w, h)
dst1 := ebiten.NewImage(w, h)
src := ebiten.NewImage(w, h) src := ebiten.NewImage(w, h)
src.Fill(color.White) src.Fill(color.White)
@ -1980,49 +1979,73 @@ func TestImageDrawTrianglesWithColorM(t *testing.T) {
is := []uint16{0, 1, 2, 1, 2, 3} is := []uint16{0, 1, 2, 1, 2, 3}
dst0.DrawTriangles(vs0, is, src, op) dst0.DrawTriangles(vs0, is, src, op)
for _, format := range []ebiten.ColorScaleFormat{
ebiten.ColorScaleFormatStraightAlpha,
ebiten.ColorScaleFormatPremultipliedAlpha,
} {
format := format
t.Run(fmt.Sprintf("format%d", format), func(t *testing.T) {
var cr, cg, cb, ca float32
switch format {
case ebiten.ColorScaleFormatStraightAlpha:
// The values are the same as ColorM.Scale
cr = 0.2
cg = 0.4
cb = 0.6
ca = 0.8
case ebiten.ColorScaleFormatPremultipliedAlpha:
cr = 0.2 * 0.8
cg = 0.4 * 0.8
cb = 0.6 * 0.8
ca = 0.8
}
vs1 := []ebiten.Vertex{ vs1 := []ebiten.Vertex{
{ {
DstX: 0, DstX: 0,
DstY: 0, DstY: 0,
SrcX: 0, SrcX: 0,
SrcY: 0, SrcY: 0,
ColorR: 0.2, ColorR: cr,
ColorG: 0.4, ColorG: cg,
ColorB: 0.6, ColorB: cb,
ColorA: 0.8, ColorA: ca,
}, },
{ {
DstX: w, DstX: w,
DstY: 0, DstY: 0,
SrcX: w, SrcX: w,
SrcY: 0, SrcY: 0,
ColorR: 0.2, ColorR: cr,
ColorG: 0.4, ColorG: cg,
ColorB: 0.6, ColorB: cb,
ColorA: 0.8, ColorA: ca,
}, },
{ {
DstX: 0, DstX: 0,
DstY: h, DstY: h,
SrcX: 0, SrcX: 0,
SrcY: h, SrcY: h,
ColorR: 0.2, ColorR: cr,
ColorG: 0.4, ColorG: cg,
ColorB: 0.6, ColorB: cb,
ColorA: 0.8, ColorA: ca,
}, },
{ {
DstX: w, DstX: w,
DstY: h, DstY: h,
SrcX: w, SrcX: w,
SrcY: h, SrcY: h,
ColorR: 0.2, ColorR: cr,
ColorG: 0.4, ColorG: cg,
ColorB: 0.6, ColorB: cb,
ColorA: 0.8, ColorA: ca,
}, },
} }
dst1.DrawTriangles(vs1, is, src, nil)
dst1 := ebiten.NewImage(w, h)
op := &ebiten.DrawTrianglesOptions{}
op.ColorScaleFormat = format
dst1.DrawTriangles(vs1, is, src, op)
for j := 0; j < h; j++ { for j := 0; j < h; j++ {
for i := 0; i < w; i++ { for i := 0; i < w; i++ {
@ -2033,12 +2056,13 @@ func TestImageDrawTrianglesWithColorM(t *testing.T) {
} }
} }
} }
})
}
} }
func TestImageDrawTrianglesInterpolatesColors(t *testing.T) { func TestImageDrawTrianglesInterpolatesColors(t *testing.T) {
const w, h = 3, 1 const w, h = 3, 1
src := ebiten.NewImage(w, h) src := ebiten.NewImage(w, h)
dst := ebiten.NewImage(w, h)
src.Fill(color.White) src.Fill(color.White)
vs := []ebiten.Vertex{ vs := []ebiten.Vertex{
@ -2083,26 +2107,39 @@ func TestImageDrawTrianglesInterpolatesColors(t *testing.T) {
ColorA: 1, ColorA: 1,
}, },
} }
for _, format := range []ebiten.ColorScaleFormat{
ebiten.ColorScaleFormatStraightAlpha,
ebiten.ColorScaleFormatPremultipliedAlpha,
} {
format := format
t.Run(fmt.Sprintf("format%d", format), func(t *testing.T) {
dst := ebiten.NewImage(w, h)
dst.Fill(color.RGBA{0x00, 0x00, 0xff, 0xff}) dst.Fill(color.RGBA{0x00, 0x00, 0xff, 0xff})
op := &ebiten.DrawTrianglesOptions{} op := &ebiten.DrawTrianglesOptions{}
op.ColorScaleFormat = format
is := []uint16{0, 1, 2, 1, 2, 3} is := []uint16{0, 1, 2, 1, 2, 3}
dst.DrawTriangles(vs, is, src, op) dst.DrawTriangles(vs, is, src, op)
got := dst.At(1, 0).(color.RGBA) got := dst.At(1, 0).(color.RGBA)
// Correct color interpolation uses the alpha channel and notices that colors on the left side of the texture are fully transparent. // Correct color interpolation uses the alpha channel
want := color.RGBA{0x00, 0x80, 0x80, 0xff} // and notices that colors on the left side of the texture are fully transparent.
var want color.RGBA
switch format {
case ebiten.ColorScaleFormatStraightAlpha:
want = color.RGBA{0x00, 0x80, 0x80, 0xff}
case ebiten.ColorScaleFormatPremultipliedAlpha:
want = color.RGBA{0x80, 0x80, 0x80, 0xff}
}
// Interpolation isn't exactly specified, so a range is accepable. if !sameColors(got, want, 2) {
diff := math.Max(math.Max(math.Max(
math.Abs(float64(got.R)-float64(want.R)),
math.Abs(float64(got.G)-float64(want.G))),
math.Abs(float64(got.B)-float64(want.B))),
math.Abs(float64(got.A)-float64(want.A)))
if diff > 5 {
t.Errorf("At(1, 0): got: %v, want: %v", got, want) t.Errorf("At(1, 0): got: %v, want: %v", got, want)
} }
})
}
} }
func TestImageDrawTrianglesShaderInterpolatesValues(t *testing.T) { func TestImageDrawTrianglesShaderInterpolatesValues(t *testing.T) {
@ -2174,14 +2211,7 @@ func TestImageDrawTrianglesShaderInterpolatesValues(t *testing.T) {
// Shaders get each color value interpolated independently. // Shaders get each color value interpolated independently.
want := color.RGBA{0x80, 0x80, 0x80, 0xff} want := color.RGBA{0x80, 0x80, 0x80, 0xff}
// Interpolation isn't exactly specified, so a range is accepable. if !sameColors(got, want, 2) {
diff := math.Max(math.Max(math.Max(
math.Abs(float64(got.R)-float64(want.R)),
math.Abs(float64(got.G)-float64(want.G))),
math.Abs(float64(got.B)-float64(want.B))),
math.Abs(float64(got.A)-float64(want.A)))
if diff > 5 {
t.Errorf("At(1, 0): got: %v, want: %v", got, want) t.Errorf("At(1, 0): got: %v, want: %v", got, want)
} }
} }
@ -3436,7 +3466,6 @@ func TestImageTooManyConstantBuffersInDirectX(t *testing.T) {
func TestImageColorMAndScale(t *testing.T) { func TestImageColorMAndScale(t *testing.T) {
const w, h = 16, 16 const w, h = 16, 16
dst := ebiten.NewImage(w, h)
src := ebiten.NewImage(w, h) src := ebiten.NewImage(w, h)
src.Fill(color.RGBA{0x80, 0x80, 0x80, 0x80}) src.Fill(color.RGBA{0x80, 0x80, 0x80, 0x80})
@ -3483,19 +3512,42 @@ func TestImageColorMAndScale(t *testing.T) {
}, },
} }
is := []uint16{0, 1, 2, 1, 2, 3} is := []uint16{0, 1, 2, 1, 2, 3}
for _, format := range []ebiten.ColorScaleFormat{
ebiten.ColorScaleFormatStraightAlpha,
ebiten.ColorScaleFormatPremultipliedAlpha,
} {
format := format
t.Run(fmt.Sprintf("format%d", format), func(t *testing.T) {
dst := ebiten.NewImage(w, h)
op := &ebiten.DrawTrianglesOptions{} op := &ebiten.DrawTrianglesOptions{}
op.ColorM.Translate(0.25, 0.25, 0.25, 0) op.ColorM.Translate(0.25, 0.25, 0.25, 0)
op.ColorScaleFormat = format
dst.DrawTriangles(vs, is, src, op) dst.DrawTriangles(vs, is, src, op)
got := dst.At(0, 0).(color.RGBA) got := dst.At(0, 0).(color.RGBA)
alphaBeforeScale := 0.5 alphaBeforeScale := 0.5
want := color.RGBA{ var want color.RGBA
switch format {
case ebiten.ColorScaleFormatStraightAlpha:
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.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.25 * 0.75)),
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.5 * 0.75)),
byte(math.Floor(0xff * alphaBeforeScale * 0.75)), byte(math.Floor(0xff * alphaBeforeScale * 0.75)),
} }
case ebiten.ColorScaleFormatPremultipliedAlpha:
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) { if !sameColors(got, want, 2) {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
})
}
} }