ebiten: add BlendOperationSubtract and BlendOperationReverseSubtract

Updates #2382
This commit is contained in:
Hajime Hoshi 2022-10-16 23:33:16 +09:00
parent 06c53c1445
commit e03825bf08
6 changed files with 130 additions and 3 deletions

View File

@ -104,12 +104,24 @@ const (
// BlendOperationAdd represents adding the source and destination color. // BlendOperationAdd represents adding the source and destination color.
// c_out = factor_src × c_src + factor_dst × c_dst // c_out = factor_src × c_src + factor_dst × c_dst
BlendOperationAdd BlendOperation = iota BlendOperationAdd BlendOperation = iota
// BlendOperationSubtract represents subtracting the source and destination color.
// c_out = factor_src × c_src - factor_dst × c_dst
BlendOperationSubtract
// BlendOperationReverseSubtract represents subtracting the source and destination color in a reversed order.
// c_out = factor_dst × c_dst - factor_src × c_src
BlendOperationReverseSubtract
) )
func (b BlendOperation) internalBlendOperation() graphicsdriver.BlendOperation { func (b BlendOperation) internalBlendOperation() graphicsdriver.BlendOperation {
switch b { switch b {
case BlendOperationAdd: case BlendOperationAdd:
return graphicsdriver.BlendOperationAdd return graphicsdriver.BlendOperationAdd
case BlendOperationSubtract:
return graphicsdriver.BlendOperationSubtract
case BlendOperationReverseSubtract:
return graphicsdriver.BlendOperationReverseSubtract
default: default:
panic(fmt.Sprintf("ebiten: invalid blend operation: %d", b)) panic(fmt.Sprintf("ebiten: invalid blend operation: %d", b))
} }

View File

@ -3551,3 +3551,104 @@ func TestImageColorMAndScale(t *testing.T) {
}) })
} }
} }
func TestImageBlendOperation(t *testing.T) {
const w, h = 16, 1
dst := ebiten.NewImage(w, h)
src := ebiten.NewImage(w, h)
dstColor := func(i int) (byte, byte, byte, byte) {
return byte(4 * i * 17), byte(4*i*17 + 1), byte(4*i*17 + 2), byte(4*i*17 + 3)
}
srcColor := func(i int) (byte, byte, byte, byte) {
return byte(4 * i * 13), byte(4*i*13 + 1), byte(4*i*13 + 2), byte(4*i*13 + 3)
}
clamp := func(x int) byte {
if x > 255 {
return 255
}
if x < 0 {
return 0
}
return byte(x)
}
dstPix := make([]byte, 4*w*h)
for i := 0; i < w; i++ {
r, g, b, a := dstColor(i)
dstPix[4*i] = r
dstPix[4*i+1] = g
dstPix[4*i+2] = b
dstPix[4*i+3] = a
}
srcPix := make([]byte, 4*w*h)
for i := 0; i < w; i++ {
r, g, b, a := srcColor(i)
srcPix[4*i] = r
srcPix[4*i+1] = g
srcPix[4*i+2] = b
srcPix[4*i+3] = a
}
src.WritePixels(srcPix)
colorOperations := []ebiten.BlendOperation{
ebiten.BlendOperationAdd,
ebiten.BlendOperationSubtract,
ebiten.BlendOperationReverseSubtract,
}
alphaOperations := []ebiten.BlendOperation{
ebiten.BlendOperationAdd,
ebiten.BlendOperationSubtract,
ebiten.BlendOperationReverseSubtract,
}
for _, cop := range colorOperations {
for _, aop := range alphaOperations {
// Reset the destination state.
dst.WritePixels(dstPix)
op := &ebiten.DrawImageOptions{}
op.Blend = ebiten.Blend{
BlendFactorSourceColor: ebiten.BlendFactorOne,
BlendFactorSourceAlpha: ebiten.BlendFactorOne,
BlendFactorDestinationColor: ebiten.BlendFactorOne,
BlendFactorDestinationAlpha: ebiten.BlendFactorOne,
BlendOperationColor: cop,
BlendOperationAlpha: aop,
}
dst.DrawImage(src, op)
for i := 0; i < w; i++ {
got := dst.At(i, 0).(color.RGBA)
sr, sg, sb, sa := srcColor(i)
dr, dg, db, da := dstColor(i)
var want color.RGBA
switch cop {
case ebiten.BlendOperationAdd:
want.R = clamp(int(sr) + int(dr))
want.G = clamp(int(sg) + int(dg))
want.B = clamp(int(sb) + int(db))
case ebiten.BlendOperationSubtract:
want.R = clamp(int(sr) - int(dr))
want.G = clamp(int(sg) - int(dg))
want.B = clamp(int(sb) - int(db))
case ebiten.BlendOperationReverseSubtract:
want.R = clamp(int(dr) - int(sr))
want.G = clamp(int(dg) - int(sg))
want.B = clamp(int(db) - int(sb))
}
switch aop {
case ebiten.BlendOperationAdd:
want.A = clamp(int(sa) + int(da))
case ebiten.BlendOperationSubtract:
want.A = clamp(int(sa) - int(da))
case ebiten.BlendOperationReverseSubtract:
want.A = clamp(int(da) - int(sa))
}
if !sameColors(got, want, 1) {
t.Errorf("dst.At(%d, 0): operations: %d, %d: got: %v, want: %v", i, cop, aop, got, want)
}
}
}
}
}

View File

@ -39,8 +39,8 @@ type BlendOperation byte
const ( const (
BlendOperationAdd BlendOperation = iota BlendOperationAdd BlendOperation = iota
BlendOperationSubtract
// TODO: Add more operators BlendOperationReverseSubtract
) )
var BlendSourceOver = Blend{ var BlendSourceOver = Blend{

View File

@ -55,6 +55,10 @@ func blendOperationToBlendOp(o graphicsdriver.BlendOperation) _D3D12_BLEND_OP {
switch o { switch o {
case graphicsdriver.BlendOperationAdd: case graphicsdriver.BlendOperationAdd:
return _D3D12_BLEND_OP_ADD return _D3D12_BLEND_OP_ADD
case graphicsdriver.BlendOperationSubtract:
return _D3D12_BLEND_OP_SUBTRACT
case graphicsdriver.BlendOperationReverseSubtract:
return _D3D12_BLEND_OP_REV_SUBTRACT
default: default:
panic(fmt.Sprintf("directx: invalid blend operation: %d", o)) panic(fmt.Sprintf("directx: invalid blend operation: %d", o))
} }

View File

@ -325,6 +325,10 @@ func blendOperationToMetalBlendOperation(o graphicsdriver.BlendOperation) mtl.Bl
switch o { switch o {
case graphicsdriver.BlendOperationAdd: case graphicsdriver.BlendOperationAdd:
return mtl.BlendOperationAdd return mtl.BlendOperationAdd
case graphicsdriver.BlendOperationSubtract:
return mtl.BlendOperationSubtract
case graphicsdriver.BlendOperationReverseSubtract:
return mtl.BlendOperationReverseSubtract
default: default:
panic(fmt.Sprintf("metal: invalid blend operation: %d", o)) panic(fmt.Sprintf("metal: invalid blend operation: %d", o))
} }

View File

@ -36,7 +36,9 @@ const (
type blendOperation int type blendOperation int
const ( const (
glFuncAdd blendOperation = 0x8006 glFuncAdd blendOperation = 0x8006
glFuncReverseSubtract blendOperation = 0x800b
glFuncSubtract blendOperation = 0x800a
) )
func convertBlendFactor(f graphicsdriver.BlendFactor) blendFactor { func convertBlendFactor(f graphicsdriver.BlendFactor) blendFactor {
@ -64,6 +66,10 @@ func convertBlendOperation(o graphicsdriver.BlendOperation) blendOperation {
switch o { switch o {
case graphicsdriver.BlendOperationAdd: case graphicsdriver.BlendOperationAdd:
return glFuncAdd return glFuncAdd
case graphicsdriver.BlendOperationSubtract:
return glFuncSubtract
case graphicsdriver.BlendOperationReverseSubtract:
return glFuncReverseSubtract
default: default:
panic(fmt.Sprintf("opengl: invalid blend operation %d", o)) panic(fmt.Sprintf("opengl: invalid blend operation %d", o))
} }