Re: subtractive painting



Using getpixel and setpixel I have made a crude kind of painting
program for my kids and they are quite happy about it. But I want to
make the colors on the screen behave in a similar way to the way paint
pigments do on paper. In other words I want a shade of green to appear
when blue is painted over an existing yellow color, or orange to come
from painting red over yellow.

Sounds simple but I have spent days Googling around this and have not
found the formula I need.

At the simplest level, you can get GDI to.. kind of do what you want to using a mask pen:

'***
Dim LastX As Single, LastY As Single
Dim PenCol As Long

Private Const ColOff As Long = 9
Private Const NumCol As Long = 7

Private Sub Form_Load()
Me.DrawMode = vbMaskPen
Me.DrawWidth = 20
PenCol = ColOff
End Sub

Private Sub Form_MouseDown(ByRef Button As Integer, _
ByRef Shift As Integer, ByRef X As Single, ByRef Y As Single)
LastX = X
LastY = Y
Me.ForeColor = QBColor(PenCol)
End Sub

Private Sub Form_MouseMove(ByRef Button As Integer, _
ByRef Shift As Integer, ByRef X As Single, ByRef Y As Single)
If (Button) Then
Me.Line (LastX, LastY)-(X, Y)
LastX = X
LastY = Y
End If
End Sub

Private Sub Form_MouseUp(ByRef Button As Integer, _
ByRef Shift As Integer, ByRef X As Single, ByRef Y As Single)
PenCol = (((PenCol - ColOff) + 1) Mod NumCol) + ColOff
End Sub
'***

It's cheap and cheesy but the results however are a bit rubbish from an artistic point of view so to get anything better
you'd need to roll your own blending algorithm. Assuming you're trying to accomplish the effect of watercolour ink on
paper which had these subtractive properties, and ignoring for the time being such factors as wet edges, bleed, paper
texture and so on here's how I'd go about it.
Use to buffers, one for the current 'painting' and one for the current brush stroke. Use GDI to render the brush stroke
normally onto the brush stroke buffer using the normal drawing mode (you can also overlay this onto the screen to give
the artist an indication of what they're doing.) One important thing here though is that the brush stroke image is a
negative of what you want to draw, so it should be filled black and the brush stroke should be rendered in the inverse
of the desired colour for reasons we'll see later.
Once the brush stork is complete (the mouse button/stylus is lifted) you then composite the brush stroke onto the main
image buffer by using the GDI buffer as a subtractive tint over the existing pixels. You can choose the opacity of the
pigment to be applied by the brush stroke, say 10%, then multiply the RGB values of the brush stroke image by this value
and subtract the result from the master image. Because the brush stroke image was a negative of the desired drawing,
the areas which were not painted will be left the same since black == 0, and the areas that were painted will
effectively have the pigment opposite from the brush colour 'sucked out' of them which darkens those areas but leaves
the correct coloured tint.
Here's a very crude example; drop a picture box on a form then paste in this code and run:

'***
Private Declare Function GetPixel Lib "GDI32.dll" (ByVal hDC As Long, _
ByVal x As Long, ByVal y As Long) As Long
Private Declare Function SetPixelV Lib "GDI32.dll" (ByVal hDC As Long, _
ByVal x As Long, ByVal y As Long, ByVal crColor As Long) As Long

Dim LastX As Single, LastY As Single
Dim PenCol As Long

Private Const ColOff As Long = 9
Private Const NumCol As Long = 6

Private Sub Form_Load()
Me.DrawWidth = 20
PenCol = ColOff

Picture1.Visible = False
Picture1.AutoRedraw = True
Picture1.DrawWidth = 20
End Sub

Private Sub Form_MouseDown(ByRef Button As Integer, _
ByRef Shift As Integer, ByRef x As Single, ByRef y As Single)
LastX = x
LastY = y
Me.ForeColor = QBColor(PenCol)
Picture1.ForeColor = Me.ForeColor Xor vbWhite
Picture1.Line (0, 0)-(Picture1.ScaleWidth, Picture1.ScaleHeight), vbBlack, BF
End Sub

Private Sub Form_MouseMove(ByRef Button As Integer, _
ByRef Shift As Integer, ByRef x As Single, ByRef y As Single)
If (Button) Then ' Draw to both screen and back-buffer
Me.Line (LastX, LastY)-(x, y)
Picture1.Line (LastX, LastY)-(x, y)
LastX = x
LastY = y
End If
End Sub

Private Sub Form_MouseUp(ByRef Button As Integer, _
ByRef Shift As Integer, ByRef x As Single, ByRef y As Single)
PenCol = (((PenCol - ColOff) + 1) Mod NumCol) + ColOff

Me.MousePointer = vbHourglass

Call Form1.Cls
Form1.AutoRedraw = True ' Eugh..

Dim LoopX As Long, LoopY As Long

Const BlendAmt As Byte = &H40 ' 25%

For LoopY = 0 To Picture1.ScaleY(Picture1.ScaleHeight, Picture1.ScaleMode, vbPixels) - 1
For LoopX = 0 To Picture1.ScaleX(Picture1.ScaleWidth, Picture1.ScaleMode, vbPixels) - 1
Call SetPixelV(Form1.hDC, LoopX, LoopY, BlendCol( _
GetPixel(Form1.hDC, LoopX, LoopY), _
GetPixel(Picture1.hDC, LoopX, LoopY), BlendAmt))
Next LoopX
Next LoopY

Set Form1.Picture = Form1.Image
Form1.AutoRedraw = False

Me.MousePointer = vbNormal
End Sub

Private Function BlendCol(ByVal inSrc As Long, ByVal inDst As Long, ByVal inOpac As Byte) As Long
Dim R As Long, G As Long, B As Long

' Blend colours
R = (inSrc And &HFF&) - (((inDst And &HFF&) * inOpac) \ &H100)
G = ((inSrc And &HFF00&) \ &H100&) - (((inDst And &HFF00&) * inOpac) \ &H10000)
B = ((inSrc And &HFF0000) \ &H10000) - ((((inDst And &HFF0000) \ &H100) * inOpac) \ &H10000)

' Clip low values
If (R < 0) Then R = 0
If (G < 0) Then G = 0
If (B < 0) Then B = 0

' Return composite colour
BlendCol = RGB(R, G, B)
End Function

Private Sub Form_Resize() ' Make sure back-buffer is right size
Call Picture1.Move(0, 0, Me.ScaleWidth, Me.ScaleHeight)
End Sub
'***

In a production application the picture box would of course be replaced with a GDI back-buffer, no drawing would be made
to the form directly apart from the final buffer flip and direct data manipulation would be used in place of
Get/SetPixel() but this method keeps the code simple.
Of course since the brush stroke is applied after it's drawn, effects such as wet edges can be applied to the brush
stroke buffer before it's blended (I would imagine a Gaussian convolution kernel would likely do the trick, you really
just want the edges to be a stronger colour while the centre is slightly washed out.)
Hope this helps,

Mike


- Microsoft Visual Basic MVP -
E-Mail: EDais@xxxxxxxx
WWW: Http://EDais.mvps.org/


.


Loading