Skip to content

Commit c3840d9

Browse files
committed
Add "Behind" blend mode
This allows you to paint "behind" the active layer, effectively painting a background onto layers with transparency data. I've also updated the GIMP (XCF) file importer to recognize "Behind" mode in layers, allowing those "Behind"-mode layers to render correctly in PD.
1 parent 12fbaa0 commit c3840d9

File tree

6 files changed

+119
-21
lines changed

6 files changed

+119
-21
lines changed

Classes/pdDIB.cls

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,47 @@ CouldNotCreateDIB:
883883

884884
End Function
885885

886-
'Replace the entire DIB's contents with some solid color
886+
'Replace a rectangle of the DIB with a fixed RGBA value.
887+
' IMPORTANTLY: it is up to the caller to handle alpha premultiplication on the passed color + alpha value,
888+
' based on the underlying premultiplied alpha state of the target DIB.
889+
Friend Sub FillRectWithColor(ByRef dstRectF As RectF, Optional ByVal fillColor As Long = vbBlack, Optional ByVal fillAlpha As Single = 0!)
890+
891+
'Unsuspend this DIB (as necessary)
892+
If m_IsSuspended Then UnsuspendDIB
893+
894+
'If the DIB is 24bpp, apply the color using GDI
895+
If (m_dibColorDepth = 24) Then
896+
897+
GDI.FillRectToDC Me.GetDIBDC, Int(dstRectF.Left), Int(dstRectF.Top), Int(dstRectF.Width) + 1, Int(dstRectF.Height) + 1, fillColor
898+
899+
'Alpha premultiplication technically doesn't matter in 24-bpp mode, so reset it to its default value (FALSE)
900+
m_IsAlphaPremultiplied = False
901+
902+
'32-bpp requires GDI+
903+
Else
904+
905+
Dim fillAlphaL As Long
906+
fillAlphaL = Int(fillAlpha * 2.55! + 0.5!)
907+
908+
Dim tmpSurface As Long, tmpBrush As Long
909+
tmpSurface = GDI_Plus.GetGDIPlusGraphicsFromDC(Me.GetDIBDC)
910+
tmpBrush = GDI_Plus.GetGDIPlusSolidBrushHandle(fillColor, fillAlphaL)
911+
912+
GDI_Plus.GDIPlus_GraphicsSetCompositingMode tmpSurface, GP_CM_SourceCopy
913+
GDI_Plus.GDIPlus_FillRectI tmpSurface, tmpBrush, Int(dstRectF.Left), Int(dstRectF.Top), Int(dstRectF.Width + 0.5!), Int(dstRectF.Height + 0.5!)
914+
GDI_Plus.ReleaseGDIPlusBrush tmpBrush
915+
GDI_Plus.ReleaseGDIPlusGraphics tmpSurface
916+
917+
m_IsAlphaPremultiplied = (fillAlphaL = 255) Or ((fillAlphaL = 0) And (fillColor = 0))
918+
919+
End If
920+
921+
'Minimize GDI resources by freeing our DC
922+
Me.FreeFromDC
923+
924+
End Sub
925+
926+
'Replace the entire DIB's contents with a fixed RGBA value
887927
Friend Sub FillWithColor(Optional ByVal fillColor As Long = vbBlack, Optional ByVal fillAlpha As Single = 0!)
888928

889929
'Unsuspend this DIB (as necessary)
@@ -926,7 +966,7 @@ Friend Sub FillWithColor(Optional ByVal fillColor As Long = vbBlack, Optional By
926966
Dim tmpSurface As Long, tmpBrush As Long
927967
tmpSurface = GDI_Plus.GetGDIPlusGraphicsFromDC(Me.GetDIBDC)
928968
tmpBrush = GDI_Plus.GetGDIPlusSolidBrushHandle(fillColor, fillAlpha * 2.55)
929-
969+
930970
GDI_Plus.GDIPlus_GraphicsSetCompositingMode tmpSurface, GP_CM_SourceCopy
931971
GDI_Plus.GDIPlus_FillRectI tmpSurface, tmpBrush, -1, -1, m_dibWidth + 2, m_dibHeight + 2
932972
GDI_Plus.ReleaseGDIPlusBrush tmpBrush

Classes/pdPixelBlender.cls

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,23 @@ Friend Sub BlendDIBs(ByRef topDIB As pdDIB, ByRef bottomDIB As pdDIB, ByVal blen
5959

6060
'With loop bounds safely calculated, we can now proceed with blending.
6161

62-
'Because we are potentially doing a *lot* of calculations here, we need to make a temporary copy of the top DIB. (Our copy will
63-
' be a beaten, mangled mess by the end of this function, and we aren't allowed to make changes to the caller's copy.)
62+
'Because we are potentially doing a *lot* of calculations here, we need to make a temporary copy of the top DIB.
63+
' (Our copy will be a beaten, mangled mess by the end of this function, and we aren't allowed to make changes
64+
' to the caller's copy.)
6465
If (m_topDIBCopy.GetDIBWidth <> topDIB.GetDIBWidth) Or (m_topDIBCopy.GetDIBHeight <> topDIB.GetDIBHeight) Then
6566
m_topDIBCopy.CreateBlank topDIB.GetDIBWidth, topDIB.GetDIBHeight, 32, 0, 0
6667
End If
68+
6769
GDI.BitBltWrapper m_topDIBCopy.GetDIBDC, 0, 0, topDIB.GetDIBWidth, topDIB.GetDIBHeight, topDIB.GetDIBDC, 0, 0, vbSrcCopy
6870
m_topDIBCopy.SetInitialAlphaPremultiplicationState topDIB.GetAlphaPremultiplication
6971

70-
'To keep our individual blend functions as simple as possible, we apply a pre-processing alpha pass that handles several things:
72+
'To keep our individual blend functions as simple as possible, we apply a pre-processing alpha pass
73+
' that handles several things:
7174
' 1) Apply layer masks (if any)
7275
' 2) Apply alpha inheritance (if any)
76+
' 3) Overwrite blend mode on paintbrush (identified by presence of a top layer mask)
7377

7478
'Note that we can skip these steps if none of these special alpha modes are active for the top DIB.
75-
7679
If (topAlphaMode <> AM_Normal) Or (bottomAlphaMode <> AM_Normal) Or (Not topLayerMask Is Nothing) Then
7780
PreProcessAlphaEffects m_topDIBCopy, bottomDIB, bottomAlphaMode, topAlphaMode, initX, initY, finalX, finalY, xOffsetBottom, yOffsetBottom, False, ptrToTopLayerAlternateRectF, topLayerMask
7881
End If
@@ -167,6 +170,13 @@ Friend Sub BlendDIBs(ByRef topDIB As pdDIB, ByRef bottomDIB As pdDIB, ByVal blen
167170
ApplyBlendMode_Erase m_topDIBCopy, bottomDIB, initX, initY, finalX, finalY, xOffsetBottom, yOffsetBottom, alphaModifier
168171
End If
169172

173+
Case BM_Behind
174+
'"Behind" mode is handled by the final blend step (see below)
175+
176+
Case BM_Overwrite
177+
'Note that overwrite mode also requires a mask - this is so that paint strokes can be correctly composited.
178+
'TODO
179+
170180
'Some blendmodes don't have dedicated, optimized subs just yet. This catch-all function handles their code for now.
171181
Case Else
172182
PDDebug.LogAction "WARNING: UNKNOWN BLEND MODE REQUESTED IN pdPixelBlender.BlendDIBs!"
@@ -176,9 +186,45 @@ Friend Sub BlendDIBs(ByRef topDIB As pdDIB, ByRef bottomDIB As pdDIB, ByVal blen
176186
'Composited results will have been placed inside the temporary top DIB copy. Apply the final blend now!
177187
If applyFinalBlend Then
178188

179-
'Some blend modes ignore this step (Erase, at present)
180-
If (blendMode <> BM_Erase) Then m_topDIBCopy.AlphaBlendToDCEx bottomDIB.GetDIBDC, xOffsetBottom + initX, yOffsetBottom + initY, finalX - initX + 1, finalY - initY + 1, initX, initY, finalX - initX + 1, finalY - initY + 1, Int(alphaModifier * 255! + 0.5!)
181-
'Debug.Print "Blending this many pixels: " & CStr((finalX - initX + 1) * (finalY - initY + 1))
189+
'In "behind" blend mode, this step is different.
190+
If (blendMode = BM_Behind) Then
191+
192+
'In "Behind" blend mode, we actually blend the bottom DIB "atop" the top DIB. Instead of making a
193+
' temporary copy of the top DIB, we actually need to make a copy of the *bottom* one.
194+
If (m_topDIBCopy.GetDIBWidth <> bottomDIB.GetDIBWidth) Or (m_topDIBCopy.GetDIBHeight <> bottomDIB.GetDIBHeight) Then
195+
m_topDIBCopy.CreateBlank bottomDIB.GetDIBWidth, bottomDIB.GetDIBHeight, 32, 0, 0
196+
End If
197+
198+
GDI.BitBltWrapper m_topDIBCopy.GetDIBDC, 0, 0, bottomDIB.GetDIBWidth, bottomDIB.GetDIBHeight, bottomDIB.GetDIBDC, 0, 0, vbSrcCopy
199+
m_topDIBCopy.SetInitialAlphaPremultiplicationState bottomDIB.GetAlphaPremultiplication
200+
201+
'm_topDIBCopy now contains a copy of the *bottom* DIB's data. Next we want to "cut out" the
202+
' merge region in the *bottom* DIB, and replace it with the top DIB's pixels. (We also need
203+
' to blank out the target region first, so that the layer's opacity - if any - is reflected
204+
' correctly in the blend.)
205+
Dim tmpFillRectF As RectF
206+
With tmpFillRectF
207+
.Left = xOffsetBottom + initX
208+
.Top = yOffsetBottom + initY
209+
.Width = finalX - initX + 1
210+
.Height = finalY - initY + 1
211+
End With
212+
213+
bottomDIB.FillRectWithColor tmpFillRectF, 0, 0
214+
topDIB.AlphaBlendToDCEx bottomDIB.GetDIBDC, xOffsetBottom + initX, yOffsetBottom + initY, finalX - initX + 1, finalY - initY + 1, initX, initY, finalX - initX + 1, finalY - initY + 1, Int(alphaModifier * 255! + 0.5!)
215+
216+
'Now, blend the *copy* of the bottom layer onto the space where we just "chopped out"
217+
' the bottom layer's pixels and replaced them with the top layer's
218+
m_topDIBCopy.AlphaBlendToDCEx bottomDIB.GetDIBDC, xOffsetBottom + initX, yOffsetBottom + initY, finalX - initX + 1, finalY - initY + 1, xOffsetBottom + initX, yOffsetBottom + initY, finalX - initX + 1, finalY - initY + 1, 255!
219+
220+
'All other blend modes behave normally
221+
Else
222+
223+
'Some blend modes ignore this step (Erase, at present)
224+
If (blendMode <> BM_Erase) Then m_topDIBCopy.AlphaBlendToDCEx bottomDIB.GetDIBDC, xOffsetBottom + initX, yOffsetBottom + initY, finalX - initX + 1, finalY - initY + 1, initX, initY, finalX - initX + 1, finalY - initY + 1, Int(alphaModifier * 255! + 0.5!)
225+
'Debug.Print "Blending this many pixels: " & CStr((finalX - initX + 1) * (finalY - initY + 1))
226+
227+
End If
182228

183229
'Also, locked alpha requires us to restore the original bottom layer alpha values now
184230
If (bottomAlphaMode = AM_Locked) Then PostProcessAlphaEffects m_topDIBCopy, bottomDIB, bottomAlphaMode, topAlphaMode, initX, initY, finalX, finalY, xOffsetBottom, yOffsetBottom, False, ptrToTopLayerAlternateRectF
@@ -365,8 +411,9 @@ Friend Sub ApplyMaskToTopDIB(ByRef dstDIB As pdDIB, ByRef maskDIB As pdDIB, Opti
365411

366412
End Sub
367413

368-
'Prior to blending two DIBs, call this function preprocess any alpha-specific effects (masks, inheritance, etc). This greatly simplifies
369-
' the actual blending code inside each blend function, because special alpha handling is never required.
414+
'Prior to blending two DIBs, call this function preprocess any alpha-specific effects (masks, inheritance, etc).
415+
' This greatly simplifies the actual blending code inside each blend function, because special alpha handling
416+
' is never required.
370417
Private Sub PreProcessAlphaEffects(ByRef topDIB As pdDIB, ByRef bottomDIB As pdDIB, ByVal bottomAlphaMode As PD_AlphaMode, ByVal topAlphaMode As PD_AlphaMode, ByVal initX As Long, ByVal initY As Long, ByVal finalX As Long, ByVal finalY As Long, ByVal xOffset As Long, ByVal yOffset As Long, Optional ByVal returnResultsUnpremultiplied As Boolean = False, Optional ByVal ptrToTopLayerAlternateRectF As Long = 0, Optional ByRef topLayerMask As pdDIB = Nothing)
371418

372419
'Only some settings require us to use this function.
@@ -406,8 +453,8 @@ Private Sub PreProcessAlphaEffects(ByRef topDIB As pdDIB, ByRef bottomDIB As pdD
406453
If (tmpFinalX > topLayerMask.GetDIBStride - 1) Then tmpFinalX = topLayerMask.GetDIBStride - 1
407454
If (tmpFinalY > topLayerMask.GetDIBHeight - 1) Then tmpFinalY = topLayerMask.GetDIBHeight - 1
408455

409-
'2D array access is slow (because VB must apply multiplication "behind-the-scenes" on each access), so we cheat and use 1D arrays,
410-
' which we reset between scanlines.
456+
'2D array access is slow (because VB must apply multiplication "behind-the-scenes" on each access),
457+
' so we cheat and use 1D arrays, which we reset between scanlines.
411458
bottomScanlineSize = topDIB.GetDIBStride: bottomDIBPointer = topDIB.GetDIBPointer
412459
topScanlineSize = topLayerMask.GetDIBStride: topDIBPointer = topLayerMask.GetDIBPointer
413460

@@ -1199,7 +1246,7 @@ Private Sub ApplyBlendMode_Difference(ByRef topDIB As pdDIB, ByRef bottomDIB As
11991246
If (newG < 0!) Then newG = -newG
12001247
newB = (bottomB - topB)
12011248
If (newB < 0!) Then newB = -newB
1202-
1249+
12031250
'If the bottom layer contains transparency, mix the newly calculated RGB values against the original top layer
12041251
' RGB values. This reduces the strength of the blend mode result, proportional to the bottom layer's alpha.
12051252
If (bottomA <> 1!) Then

Classes/pdXCF.cls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,7 @@ Private Function GetPDBlendFromGIMPBlend(ByVal srcGIMPBlend As Long) As PD_Blend
13331333
GetPDBlendFromGIMPBlend = BM_Normal
13341334
'Behind
13351335
Case 29
1336-
GetPDBlendFromGIMPBlend = BM_Normal
1336+
GetPDBlendFromGIMPBlend = BM_Behind
13371337
'Multiply
13381338
Case 30
13391339
GetPDBlendFromGIMPBlend = BM_Multiply

Modules/Colors.bas

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ Public Enum PD_BlendMode
6262
BM_GrainExtract = 24
6363
BM_GrainMerge = 25
6464
BM_Erase = 26
65+
BM_Behind = 27
66+
BM_Overwrite = 28
6567
End Enum
6668

6769
#If False Then
@@ -70,7 +72,7 @@ End Enum
7072
Const BM_SoftLight = 10, BM_HardLight = 11, BM_VividLight = 12, BM_LinearLight = 13, BM_PinLight = 14
7173
Const BM_HardMix = 15, BM_Difference = 16, BM_Exclusion = 17, BM_Subtract = 18, BM_Divide = 19
7274
Const BM_Hue = 20, BM_Saturation = 21, BM_Color = 22, BM_Luminosity = 23, BM_GrainExtract = 24
73-
Const BM_GrainMerge = 25, BM_Erase = 26
75+
Const BM_GrainMerge = 25, BM_Erase = 26, BM_Behind = 27, BM_Overwrite = 28
7476
#End If
7577

7678
'PD supports the notion "alpha modes", including "inheritance," where a layer "inherits" the alpha of a layer beneath it,
@@ -1013,6 +1015,10 @@ Public Function GetBlendModeIDFromString(ByRef srcString As String) As PD_BlendM
10131015
GetBlendModeIDFromString = BM_GrainMerge
10141016
Case "eras"
10151017
GetBlendModeIDFromString = BM_Erase
1018+
Case "bhnd"
1019+
GetBlendModeIDFromString = BM_Behind
1020+
Case "copy"
1021+
GetBlendModeIDFromString = BM_Overwrite
10161022
Case Else
10171023
GetBlendModeIDFromString = BM_Normal
10181024
PDDebug.LogAction "WARNING! Colors.GetBlendModeStringFromID received a bad value: " & srcString
@@ -1079,10 +1085,13 @@ Public Function GetBlendModeStringFromID(ByVal srcMode As PD_BlendMode) As Strin
10791085
GetBlendModeStringFromID = "gmrg"
10801086
Case BM_Erase
10811087
GetBlendModeStringFromID = "eras"
1088+
Case BM_Behind
1089+
GetBlendModeStringFromID = "bhnd"
1090+
Case BM_Overwrite
1091+
GetBlendModeStringFromID = "copy"
10821092
Case Else
10831093
GetBlendModeStringFromID = " "
10841094
PDDebug.LogAction "WARNING! Colors.GetBlendModeStringFromID received a bad value: " & srcMode
10851095
End Select
10861096

10871097
End Function
1088-

Modules/Interface.bas

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1738,7 +1738,9 @@ Public Sub PopulateBlendModeDropDown(ByRef dstCombo As pdDropDown, Optional ByVa
17381738
dstCombo.AddItem "Luminosity", , True
17391739
dstCombo.AddItem "Grain extract"
17401740
dstCombo.AddItem "Grain merge", , True
1741-
dstCombo.AddItem "Erase"
1741+
dstCombo.AddItem "Erase", , True
1742+
dstCombo.AddItem "Behind"
1743+
dstCombo.AddItem "Overwrite"
17421744

17431745
dstCombo.ListIndex = blendIndex
17441746

PhotoDemon.vbp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Type=Exe
2-
Reference=*\G{50A7E9B0-70EF-11D1-B75A-00A0C90564FE}#1.0#0#..\..\Windows\SysWOW64\shell32.dll#Microsoft Shell Controls And Automation
3-
Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#..\..\Windows\SysWOW64\stdole2.tlb#OLE Automation
2+
Reference=*\G{50A7E9B0-70EF-11D1-B75A-00A0C90564FE}#1.0#0#\\?\C:\Windows\SysWOW64\shell32.dll#Microsoft Shell Controls And Automation
3+
Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#\\?\C:\Windows\SysWOW64\stdole2.tlb#OLE Automation
44
Module=PDMain; Modules\Main.bas
55
Module=Public_Constants; Modules\PublicConstants.bas
66
Module=Public_EnumsAndTypes; Modules\PublicEnumsAndTypes.bas
@@ -524,7 +524,7 @@ Description="PhotoDemon Photo Editor"
524524
CompatibleMode="0"
525525
MajorVer=9
526526
MinorVer=1
527-
RevisionVer=311
527+
RevisionVer=315
528528
AutoIncrementVer=1
529529
ServerSupportFiles=0
530530
VersionComments="Copyright 2000-2024 Tanner Helland - photodemon.org"

0 commit comments

Comments
 (0)