ebiten: add BlendOperationMin and BlendOperationMax

Closes #2395
This commit is contained in:
Hajime Hoshi 2023-10-12 23:55:31 +09:00
parent 7018e7dfb1
commit 567e2fa04c
8 changed files with 133 additions and 1 deletions

View File

@ -200,6 +200,20 @@ const (
// c_out = (BlendFactorDestinationRGB) × c_dst - (BlendFactorSourceRGB) × c_src
// α_out = (BlendFactorDestinationAlpha) × α_dst - (BlendFactorSourceAlpha) × α_src
BlendOperationReverseSubtract
// BlendOperationMin represents a minimum function for the source and destination color.
// If BlendOperationMin is specified, blend factors are not used.
//
// c_out = min(c_dst, c_src)
// α_out = min(α_dst, α_src)
BlendOperationMin
// BlendOperationMax represents a maximum function for the source and destination color.
// If BlendOperationMax is specified, blend factors are not used.
//
// c_out = max(c_dst, c_src)
// α_out = max(α_dst, α_src)
BlendOperationMax
)
func (b BlendOperation) internalBlendOperation() graphicsdriver.BlendOperation {
@ -210,6 +224,10 @@ func (b BlendOperation) internalBlendOperation() graphicsdriver.BlendOperation {
return graphicsdriver.BlendOperationSubtract
case BlendOperationReverseSubtract:
return graphicsdriver.BlendOperationReverseSubtract
case BlendOperationMin:
return graphicsdriver.BlendOperationMin
case BlendOperationMax:
return graphicsdriver.BlendOperationMax
default:
panic(fmt.Sprintf("ebiten: invalid blend operation: %d", b))
}

View File

@ -335,13 +335,26 @@ func TestImageDispose(t *testing.T) {
}
}
func min(a, b int) int {
type ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string
}
// TODO: Use the built-in function min from Go 1.21.
func min[T ordered](a, b T) T {
if a < b {
return a
}
return b
}
// TODO: Use the built-in function max from Go 1.21.
func max[T ordered](a, b T) T {
if a < b {
return b
}
return a
}
func TestImageBlendLighter(t *testing.T) {
img0, _, err := openEbitenImage()
if err != nil {
@ -3687,6 +3700,87 @@ func TestImageBlendOperation(t *testing.T) {
}
}
func TestImageBlendOperationMinAndMax(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)
}
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)
operations := []ebiten.BlendOperation{
ebiten.BlendOperationMin,
ebiten.BlendOperationMax,
}
for _, rgbOp := range operations {
for _, alphaOp := range operations {
// Reset the destination state.
dst.WritePixels(dstPix)
op := &ebiten.DrawImageOptions{}
// Use the default blend factors, and confirm that the factors are ignored.
op.Blend = ebiten.Blend{
BlendFactorSourceRGB: ebiten.BlendFactorDefault,
BlendFactorSourceAlpha: ebiten.BlendFactorDefault,
BlendFactorDestinationRGB: ebiten.BlendFactorDefault,
BlendFactorDestinationAlpha: ebiten.BlendFactorDefault,
BlendOperationRGB: rgbOp,
BlendOperationAlpha: alphaOp,
}
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 rgbOp {
case ebiten.BlendOperationMin:
want.R = min(sr, dr)
want.G = min(sg, dg)
want.B = min(sb, db)
case ebiten.BlendOperationMax:
want.R = max(sr, dr)
want.G = max(sg, dg)
want.B = max(sb, db)
}
switch alphaOp {
case ebiten.BlendOperationMin:
want.A = min(sa, da)
case ebiten.BlendOperationMax:
want.A = max(sa, da)
}
if !sameColors(got, want, 1) {
t.Errorf("dst.At(%d, 0): operations: %d, %d: got: %v, want: %v", i, rgbOp, alphaOp, got, want)
}
}
}
}
}
func TestImageBlendFactor(t *testing.T) {
if skipTooSlowTests(t) {
return

View File

@ -45,6 +45,8 @@ const (
BlendOperationAdd BlendOperation = iota
BlendOperationSubtract
BlendOperationReverseSubtract
BlendOperationMin
BlendOperationMax
)
var BlendSourceOver = Blend{

View File

@ -106,6 +106,10 @@ func blendOperationToBlendOp11(o graphicsdriver.BlendOperation) _D3D11_BLEND_OP
return _D3D11_BLEND_OP_SUBTRACT
case graphicsdriver.BlendOperationReverseSubtract:
return _D3D11_BLEND_OP_REV_SUBTRACT
case graphicsdriver.BlendOperationMin:
return _D3D11_BLEND_OP_MIN
case graphicsdriver.BlendOperationMax:
return _D3D11_BLEND_OP_MAX
default:
panic(fmt.Sprintf("directx: invalid blend operation: %d", o))
}

View File

@ -107,6 +107,10 @@ func blendOperationToBlendOp12(o graphicsdriver.BlendOperation) _D3D12_BLEND_OP
return _D3D12_BLEND_OP_SUBTRACT
case graphicsdriver.BlendOperationReverseSubtract:
return _D3D12_BLEND_OP_REV_SUBTRACT
case graphicsdriver.BlendOperationMin:
return _D3D12_BLEND_OP_MIN
case graphicsdriver.BlendOperationMax:
return _D3D12_BLEND_OP_MAX
default:
panic(fmt.Sprintf("directx: invalid blend operation: %d", o))
}

View File

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

View File

@ -67,6 +67,10 @@ func convertBlendOperation(o graphicsdriver.BlendOperation) blendOperation {
return gl.FUNC_SUBTRACT
case graphicsdriver.BlendOperationReverseSubtract:
return gl.FUNC_REVERSE_SUBTRACT
case graphicsdriver.BlendOperationMin:
return gl.MIN
case graphicsdriver.BlendOperationMax:
return gl.MAX
default:
panic(fmt.Sprintf("opengl: invalid blend operation %d", o))
}

View File

@ -40,7 +40,9 @@ const (
INVERT = 0x150A
KEEP = 0x1E00
LINK_STATUS = 0x8B82
MAX = 0x8008
MAX_TEXTURE_SIZE = 0x0D33
MIN = 0x8007
NEAREST = 0x2600
NO_ERROR = 0
NOTEQUAL = 0x0205