How to get the same BlendMode as Photoshop Normal Brush in SFML

Started by
9 comments, last by ManitouVR 4 years, 4 months ago

Hello Forum,

I wrote a little painting software and now i try to mimic the behavior of photoshops "normal mode" blending style.

I did a lot of tests, but i cant find the right blend mode settings/formulas.

Essentially i have a circle object with a shader on it (for smooth borders) which draws onto a RenderTexture.

I think the problem lies in the blend mode and not in the shader.

Since its hard to explain in words i made a little video to show what iam talking about.

Any help is much appreciated!

Thanks! --> Video

3D Artist

Advertisement

Sorry, not going to watch the whole video.

Photoshop's 'normal' blending mode is basically: Result = (SourceColour * SourceOpacity) + (DestinationColour * (1.0-SourceOpacity))

If you write 50% opaque red into a transparent background, you'll get 50% opaque red. Write 50% red over that, you'll get 75% red. (Red * 0.5) + (Red * 0.5 * (1.0 - 0.5)) = Red * 0.75


You might also be overwriting pixels while moving the brush. I think Photoshop saves your stroke into a selection or channel before it gets applied. Otherwise you couldn't paint an area with only one level of transparency. Hard to explain, but hopefully it makes sense.

Yeah, it's almost like the behaviour for most painting is a bit like:

  1. On Mouse Down - create new hidden layer with Normal blend mode
  2. On Mouse Move - write the current colour and opacity values into each affected pixel, based on the brush shape
  3. On Mouse Up - merge the hidden layer down into the layer you intended to paint on

Two things at play here:

  1. When you are drawing in photoshop it looks like it draws to an intermediate surface, then actually merges it when you mouse up. (This is what I do in my painting app).
  2. The blending is a little more involved than you would normally use in games / apps, because it has to deal with drawing on a transparent, or partially transparent background.

If you draw red (255, 0, 0) with 127 alpha, the colour you get on a transparent background is NOT 127, 0, 0. The colour is is 255, 0, 0 but with 127 alpha.

Have a look in the alpha blending section here to get ideas:

https://en.wikipedia.org/wiki/Alpha_compositing

It is probably drawing to an intermediate with full colour range, then blending the temporary in to give a preview, then doing a merge when you release the mouse. This could be why you can't draw above the maximum opacity in photoshop.

Ha!!

I was just making another video with the blendmode that @Kylotan recommended.

It kinda works at a first glance, but not completly.

I also noticed that it must be a 2 Step process somehow with a layer that is created on Mouse Down and then blended down onto an existing Layer.

@Prototype Yes, you are right!! If the brush is set to soft, then the outer alpha "Ring" deletes the destination pixels.

I made another (short!!) video to demonstrate what i mean -> Photoshop vs My Program

Blend Settings i used:

sf::BlendMode BlendPre = sf::BlendMode(sf::BlendMode::SrcAlpha, sf::BlendMode::OneMinusSrcAlpha, sf::BlendMode::Equation::Add);

3D Artist

@lawnjelly Thank you for your tips!

When i looked closer to what actually happens in PS i started to think that it must be a 2 step process too.

Thanks for confirming that.

Also i work with full colors and extra Alpha.

I think i know already how to implement an extra temporary layer that gets blended down after Mouse Released.

The only problem i see at the moment is with the soft brush that deletes itself.

Because this will also happen on the temp. layer.

3D Artist

@lawnjelly How did you solve the problem with a softbrush that is mostly deleting itself ?

3D Artist

C3D_ said:

@lawnjelly How did you solve the problem with a softbrush that is mostly deleting itself ?

The softbrush draws the full colour (e.g. 255, 0, 0) with the first pass but the alpha might be e.g. 64. On the second pass you merge the RGB with whatever method based on the relative colour contributions (presumably giving 255, 0, 0) then increase the alpha.

Looking back at some old code I just added the alpha between the previous and the new value, but it wasn't exactly the same situation, so use whatever fits for your case.

Also something to bear in mind doing all this is the difference between linear colourspace and sRGB. It is more correct I think to do the blending in linear colourspace, however you will probably want to use a larger bit depth because 8 bits isn't enough for linear.

@lawnjelly Hello and thanks for your answer. I dont fully understand.

In SFML i have a circle object that is my brush. Its color and alpha is all zero.

Then i have a shader ON this brush object.

The shader has a color glFragColor.rgb and the alpha is glFragColor.a = GradientAlpha

Then on every mouse change i stamp down this circle object with the shader onto a RenderTexture with a previously set BlendMode.

So where in this process do i have to introduce a "first" and "second" pass?

As far as i know i can only draw the whole thing at once to the RenderTexture (i might be wrong).

This is my shader code. (expand creates the gradient / alpha is an additional transparency(opacity))

const char VertexShader[] =
		"void main()"
		"{"
		"gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
		"gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;"
		"gl_FrontColor = gl_Color;"
		"}";
 
	const char RadialGradient[] =
		"uniform vec3 color;"
		"uniform float alpha;"
		"uniform vec2 center;"
		"uniform float radius;"
		"uniform float expand;"
		"uniform float LayerHeight;"
 
		"void main(void)"
		"{"
		"vec2 SfmlCenter = vec2(center.x, LayerHeight - center.y);"
		"vec2 p = (gl_FragCoord.xy - SfmlCenter) / radius;"
		"float d = length(p);"
		"float GradientAlpha = smoothstep(0.0, 1.0 - expand, 1.0 - d);"
		"gl_FragColor.rgb = color;"
		"gl_FragColor.a = GradientAlpha - alpha;"
		"}";

3D Artist

This topic is closed to new replies.

Advertisement