From e03825bf089b611f28ed266e552c633bd1db84bf Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sun, 16 Oct 2022 23:33:16 +0900 Subject: [PATCH] ebiten: add BlendOperationSubtract and BlendOperationReverseSubtract Updates #2382 --- blend.go | 12 +++ image_test.go | 101 ++++++++++++++++++ internal/graphicsdriver/blend.go | 4 +- .../directx/pipeline_windows.go | 4 + .../graphicsdriver/metal/graphics_darwin.go | 4 + internal/graphicsdriver/opengl/context.go | 8 +- 6 files changed, 130 insertions(+), 3 deletions(-) diff --git a/blend.go b/blend.go index e7a00fb4b..db84e4af0 100644 --- a/blend.go +++ b/blend.go @@ -104,12 +104,24 @@ const ( // BlendOperationAdd represents adding the source and destination color. // c_out = factor_src × c_src + factor_dst × c_dst 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 { switch b { case BlendOperationAdd: return graphicsdriver.BlendOperationAdd + case BlendOperationSubtract: + return graphicsdriver.BlendOperationSubtract + case BlendOperationReverseSubtract: + return graphicsdriver.BlendOperationReverseSubtract default: panic(fmt.Sprintf("ebiten: invalid blend operation: %d", b)) } diff --git a/image_test.go b/image_test.go index 94ad1141d..a22891143 100644 --- a/image_test.go +++ b/image_test.go @@ -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) + } + } + } + } +} diff --git a/internal/graphicsdriver/blend.go b/internal/graphicsdriver/blend.go index 526dac8d1..3f62e2350 100644 --- a/internal/graphicsdriver/blend.go +++ b/internal/graphicsdriver/blend.go @@ -39,8 +39,8 @@ type BlendOperation byte const ( BlendOperationAdd BlendOperation = iota - - // TODO: Add more operators + BlendOperationSubtract + BlendOperationReverseSubtract ) var BlendSourceOver = Blend{ diff --git a/internal/graphicsdriver/directx/pipeline_windows.go b/internal/graphicsdriver/directx/pipeline_windows.go index d75927363..9cda3192c 100644 --- a/internal/graphicsdriver/directx/pipeline_windows.go +++ b/internal/graphicsdriver/directx/pipeline_windows.go @@ -55,6 +55,10 @@ func blendOperationToBlendOp(o graphicsdriver.BlendOperation) _D3D12_BLEND_OP { switch o { case graphicsdriver.BlendOperationAdd: return _D3D12_BLEND_OP_ADD + case graphicsdriver.BlendOperationSubtract: + return _D3D12_BLEND_OP_SUBTRACT + case graphicsdriver.BlendOperationReverseSubtract: + return _D3D12_BLEND_OP_REV_SUBTRACT default: panic(fmt.Sprintf("directx: invalid blend operation: %d", o)) } diff --git a/internal/graphicsdriver/metal/graphics_darwin.go b/internal/graphicsdriver/metal/graphics_darwin.go index 7ea1ded59..c73de0630 100644 --- a/internal/graphicsdriver/metal/graphics_darwin.go +++ b/internal/graphicsdriver/metal/graphics_darwin.go @@ -325,6 +325,10 @@ func blendOperationToMetalBlendOperation(o graphicsdriver.BlendOperation) mtl.Bl switch o { case graphicsdriver.BlendOperationAdd: return mtl.BlendOperationAdd + case graphicsdriver.BlendOperationSubtract: + return mtl.BlendOperationSubtract + case graphicsdriver.BlendOperationReverseSubtract: + return mtl.BlendOperationReverseSubtract default: panic(fmt.Sprintf("metal: invalid blend operation: %d", o)) } diff --git a/internal/graphicsdriver/opengl/context.go b/internal/graphicsdriver/opengl/context.go index 7179f3e9e..bd50e4da0 100644 --- a/internal/graphicsdriver/opengl/context.go +++ b/internal/graphicsdriver/opengl/context.go @@ -36,7 +36,9 @@ const ( type blendOperation int const ( - glFuncAdd blendOperation = 0x8006 + glFuncAdd blendOperation = 0x8006 + glFuncReverseSubtract blendOperation = 0x800b + glFuncSubtract blendOperation = 0x800a ) func convertBlendFactor(f graphicsdriver.BlendFactor) blendFactor { @@ -64,6 +66,10 @@ func convertBlendOperation(o graphicsdriver.BlendOperation) blendOperation { switch o { case graphicsdriver.BlendOperationAdd: return glFuncAdd + case graphicsdriver.BlendOperationSubtract: + return glFuncSubtract + case graphicsdriver.BlendOperationReverseSubtract: + return glFuncReverseSubtract default: panic(fmt.Sprintf("opengl: invalid blend operation %d", o)) }