Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A way to erase pixels or overwrite alpha channel of pixels using the draw_line method() and a new blend mode #10255

Closed
blurymind opened this issue Aug 11, 2017 · 45 comments

Comments

@blurymind
Copy link

blurymind commented Aug 11, 2017

I am currently trying to create a tiny pixelart tool for godot, but it's not possible to use the draw_line() method for erasing pixels. There seems to be no way in replacing the alpha value of a pixel with another alpha value at the moment - in the way you can draw with another color on top of old lines.

As @Zylann said

@blurymind you could overwrite alpha pixels as if they were a normal color component, I believe there are blending modes in OpenGL to do that, but Godot doesn't expose them. This is a precision I added at the beginning of the thread, no need to repost the entire code :)

this use case concerns blending mode when rendering on texture, not on screen (which wouldn't make much sense because screen has no alpha)

I think there is a workaround but it's impractical, you would need to do your own blending with a shader by drawing transparent pixels as a red color on a separate texture, and then blends your image + the red texture together with a shader that erases pixels that come in contact with red pixels, to render it on the final render target which you can then save... but an appropriate control on blending mode would be much more simpler.
Currently it is possible, but very impractical.

Here is an example code for drawing freehand lines:

extends Container

var _pen = null
var _prev_mouse_pos = Vector2()
var viewport = Viewport.new()

func _ready():
	set_process_input(true)
	# Make it so it renders on a texture
	viewport.set_as_render_target(true)
	# Set the size of it
	viewport.set_rect(get_parent().get_rect())
	viewport.set_transparent_background(true)
	# I tried to set the background color, but none of this worked...
	_pen = Node2D.new()
	viewport.add_child(_pen)
	_pen.connect("draw", self, "_on_draw")
	# Don't clear the frame when it gets updated so it will accumulate drawings
	viewport.set_render_target_clear_on_new_frame(false)
	add_child(viewport)
	
	# Use a sprite to display the result texture
	var rt = viewport.get_render_target_texture()
	var board = Sprite.new()
	board.set_texture(rt)
	board.set_centered(false)
	add_child(board)
	set_process(true)

func _process(delta):
	_pen.update()

var color = Color(1, 1, 0,1)
func _on_draw():
	var mouse_pos = _pen.get_global_mouse_pos()
	_pen.draw_line(mouse_pos, _prev_mouse_pos,color,5 )
	_prev_mouse_pos = mouse_pos

func _input(event):
	if event.is_action_pressed("eraser"):
		print('using eraser now')
		color = Color(VisualServer.get_default_clear_color())  #<-- this approach to setting the brush to be an eraser doesnt work!
	if event.is_action_pressed("save"):
		viewport.queue_screen_capture()
		yield(get_tree(), "idle_frame")
		yield(get_tree(), "idle_frame")
		yield(get_tree(), "idle_frame")
		var captured = viewport.get_screen_capture()
		captured.save_png("res://screenshot.png") #<-- capture and you will see why

color = Color(VisualServer.get_default_clear_color())
Will fail to erase the yellow pixels in the example.

Instead it simply writes the background color and you get this ugly grey color when you export the image with viewport.get_screen_capture():
screenshot

The ideal solution for me personally is if I could set the color value to (0,0,0,0) and have that act as an eraser.
If we could have Godot actually have VisualServer.get_default_clear_color() be a color that erases pixels whenever set_transparent_background is true- that would make more sense too.
Or perhaps the best approach to this is to have a new blending mode that supports it.

I am assuming that something has to be done to the way godot's draw_line method works when writing to a texture. Any solution to this is very much welcome.
Making it possible will allow people to implement a freehand eraser tool and possibly other very useful things using the draw_line method

@Zylann
Copy link
Contributor

Zylann commented Aug 11, 2017

I would precise that this use case concerns blending mode when rendering on texture, not on screen (which wouldn't make much sense because screen has no alpha)

@blurymind
Copy link
Author

blurymind commented Aug 12, 2017

@Zylann we can still screen capture with alpha though

The viewport has
set_transparent_background ( true )
http://docs.godotengine.org/en/stable/classes/class_viewport.html?highlight=screenshot#class-viewport-queue-screen-capture

@blurymind
Copy link
Author

has anyone figured out a way to erase pixels using the draw_line method?

@Zylann
Copy link
Contributor

Zylann commented Aug 26, 2017

@blurymind it's about adding a new blend mode I think, that will use alpha like a regular color, to replace the alpha already in the target buffer. Maybe @reduz knows how to do it?

@blurymind
Copy link
Author

blurymind commented Aug 26, 2017

@reduz is there a way to erase pixels or overwrite pixels using the draw_line method?
I need it to implement a simple freehand eraser tool

@Zylann
Copy link
Contributor

Zylann commented Aug 30, 2017

@Tvs-Frank you can't write the background color to "erase" pixels if your background is not a solid color (or worse, animated)

@blurymind
Copy link
Author

blurymind commented Aug 31, 2017

I was wondering, is there a way to overwrite pixels? If so, we could over write the pixels with 1 for alpha with pixels with 0 alpha.
How do you do that in godot?

@Zylann
Copy link
Contributor

Zylann commented Aug 31, 2017

@blurymind you could overwrite alpha pixels as if they were a normal color component, I believe there are blending modes in OpenGL to do that, but Godot doesn't expose them. This is a precision I added at the beginning of the thread, no need to repost the entire code :)

I think there is a workaround but it's impractical, you would need to do your own blending with a shader by drawing transparent pixels as a red color on a separate texture, and then blends your image + the red texture together with a shader that erases pixels that come in contact with red pixels, to render it on the final render target which you can then save... but an appropriate control on blending mode would be much more simpler.

@blurymind
Copy link
Author

blurymind commented Aug 31, 2017 via email

@akien-mga
Copy link
Member

Sounds like a good feature request. If you can edit the original post for clarity based on the follow-up discussion, that would be great.

@blurymind blurymind changed the title Eraser tool - how to blend alpha like other colors or erase pixels in the draw() function Eraser tool -a way to erase pixels or overwrite alpha channel of pixels using the draw_line method() Aug 31, 2017
@blurymind blurymind changed the title Eraser tool -a way to erase pixels or overwrite alpha channel of pixels using the draw_line method() A way to erase pixels or overwrite alpha channel of pixels using the draw_line method() Aug 31, 2017
@blurymind
Copy link
Author

blurymind commented Aug 31, 2017

@akien-mga Thank you :)
I updated the title and the first post to more accurately reflect the request
Please let me know if it needs more amendments

@Zylann another approach could be to have VisualServer.get_default_clear_color() be a color that erases pixels whenever set_transparent_background is true? Not sure what design would be most elegant here

@Zylann
Copy link
Contributor

Zylann commented Aug 31, 2017

a color that erases pixels whenever set_transparent_background is true

A Color cannot hold such info beyond having an alpha of zero. What you mean is again a blend mode (say, instead of mixing alpha, OpenGL will replace alpha with a different calculation)

@blurymind blurymind changed the title A way to erase pixels or overwrite alpha channel of pixels using the draw_line method() A way to erase pixels or overwrite alpha channel of pixels using the draw_line method() and a new blend mode Aug 31, 2017
@blurymind
Copy link
Author

blurymind commented Aug 31, 2017

@Zylann A new blending mode would be awesome to get. Is there anyone interested in implementing it?
Which node will acquire the new blending mode and how would an example gdscript that enables and uses it to freehand erase pixel look like?

I updated the title and description in the first post again to reflect that

@Zylann
Copy link
Contributor

Zylann commented Aug 31, 2017

I think it would be an option in the material (or shader?) that let you choose how the result is blended, and it would work on anything that currently can have a blend mode (so anything that the engine can draw, basically).

Currently we have these:
image
Mix is the most commonly used blend mode.
I'm not sure what Premult Alpha does though, and not sure how add, sub and mul treat it either.
In Unity shaders, blending is specified as two keywords instead of one, so there might be combination of things at play.

@blurymind
Copy link
Author

@Zylann would we still be using gdscript and draw_line method though? Or are we writing a shader?

@Zylann
Copy link
Contributor

Zylann commented Aug 31, 2017

You would still draw anything you want like the engine allows you to. I mentionned shaders just for the "replace alpha" part, but it's not really about coding the shader, mostly specifying a blending mode the Godot way so that OpenGL does what you need in the renderer. Like, blend mode "Add" adds color to the image. "Sub" subtracts it. "Mul" multiplies it. It sounds logical to have a way to say "Replace alpha" then, or something close to what OpenGL needs.

This SO threads is a similar problem to yours https://stackoverflow.com/questions/4074955/blending-a-texture-to-erase-alpha-values-softly-with-opengl
Same "easy" solutions are being discussed too in case your background is a solid color, but the general blending mode option is also mentionned.

@blurymind
Copy link
Author

blurymind commented Aug 31, 2017

They mention this:

...If so, instead of "erasing", you could simply paint background over it. That would be somewhat simpler, as you would only change the color and not the blending mod.

Which is pretty much one of the suggestions we came up with here.. Notably:
color = Color(VisualServer.get_default_clear_color())
Unfortunately godot doesnt treat that color as a color with 0 alpha that erases other colors - which would the expected behaviour be.

That said, I would take the option of a new blending mode too. Either would solve it. The question now is which one is a lower hanging fruit and does it fit best with the current design?
Do we need to have both another blending mode and VisualServer.get_default_clear_color() do what it's supposed to, instead of this:
https://user-images.githubusercontent.com/6495061/29914660-45be9be4-8e31-11e7-8ee6-7d014da28662.png

What would the most elegant gdscript method be to set the masking color that erases pixels?

What part of the source code is in charge of blending modes. I wish I knew as much c++ as I do python 🖌

@reduz
Copy link
Member

reduz commented Aug 31, 2017

I think the best way to do this would be:

  1. To add a draw_line function to Image
  2. To upload the image when it changes
  3. To use it as either light or mask for the framebuffer

@Zylann
Copy link
Contributor

Zylann commented Aug 31, 2017

@blurymind VisualServer.get_default_clear_color() will never help you draw on the alpha channel, at best it will give you which color to draw in order to "fake" transparency by drawing the same background, but then the image won't be transparent if you want to save it afterwards.

@reduz why not exposing the blend options? They exactly do what an app involving painting would need, and work faster than painting on the CPU and uploading the image every frame (also would miss all features the renderer let us draw).
Sure, having a 1px-wide breisenham on CPU would fit this very specific request a bit better than GDScript, but I feel like it would miss a awesome quick win for using the renderer.

Also maintaining a secondary texture as mask would work, but you would need to redraw on it the same things that you draw on the main texture, when not using the eraser, in order to refill alpha to 1. And when it comes to save as image another rendering would occur to flatten those two textures into one. It would maybe work, but in an impractical way.

@reduz
Copy link
Member

reduz commented Aug 31, 2017

@Zylann there is not much of a blend option to expose, GPU blending is very very limited, not near as complex as what something like Photoshop can do.

He can also do this using GPU by drawing to another viewport and use it to blend..

@Zylann
Copy link
Contributor

Zylann commented Aug 31, 2017

Are you sure it's not supported? The SO thread mentionned this:
glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
Also the same kind of options are available to write in Unity shaders.

@reduz
Copy link
Member

reduz commented Aug 31, 2017

@Zylann Exposing higher level blending options will not happen, because Godot tweaks the shader and changes the blend function according to what it's doing internally.

In Unity you can do this, but you have to write your own shader passes for forward base, forward add, deferred, shadow, etc. That's not the idea in Godot, as It's meant to be more user friendly.

That said, given this is 2D only, adding color replace and alpha replace models should be possible to the list of 2D blend modes.

@blurymind
Copy link
Author

blurymind commented Sep 1, 2017

@reduz you're right, having a color replace and alpha replace models would be only useful for 2d drawing anyway.
@Zylann makes a good point that it would be beneficial to have it, since getting this sort of functionality in a game or an app would be both much more straightforward and more performant.

I would be very thankful if somebody looks into it and will help in any way possible with testing/examples :)

A drawing app is not the only use case for this I can think of either. There are many interesting things that can be done. Cool editor addons can come out of it

Even a simple pixel editing app addon could help rapid prototyping without leaving the editor. A dirty mockup used to design gameplay can be made by the designer and later used by an artist for sprite dimensions

@Zylann
Copy link
Contributor

Zylann commented Mar 23, 2018

While thinking about implementing GPU-based terrain painting and sculpting, I figured out I needed alpha override blend modes (among other things). Indeed, the case is similar to general texture painting, while in this case the texture is used as a "data" rather than forcibly something to draw as is.

What I would need would basically treat the alpha channel like any other channel (R, G, B), which gives the ability to paint on full splatmaps or various other maps that use 4 channels at once. That use case needs a 2D viewport though.

Edit: I realized one problem with this, though... if those blend modes are available in 2D only, but those textures are then used in 3D, that means it could create a constant reimport loop due to the fact VisualServer auto-reimport color space of such textures when used for 3D :x

@BastiaanOlij
Copy link
Contributor

I second the option to add a blend mode which simply says "replace" or simply "off". Basically just turn off blending when rendering with that shader. The alpha (along with the RGB value) will just be written into the frame buffer as is without blending it with the current value.

@Bauxitedev
Copy link

I'm running into this issue as well when working on my texture painter. There seems to be no way to decreate alpha by drawing something, aka erasing. So if the alpha of a pixel is 1, there's no way to get it back down to 0. The painting shader is 2D, so a "alpha replace" 2D blend mode would be perfect and solve all my issues.

@BastiaanOlij
Copy link
Contributor

BastiaanOlij commented May 21, 2018 via email

@BastiaanOlij
Copy link
Contributor

Oh look, my PR is even referenced up above :) #18462 :) Couldn't see that on my poor mobile phone

@blurymind
Copy link
Author

blurymind commented May 21, 2018

@BastiaanOlij can you share a simple code snippet demonstrating how this could be applied on a draw operation such as draw_line for example

I kind of already posted a minimal example where it doesnt work because of the feature missing. :) I wanna test it out in the weekend and was wondering what to alter in my little example to make it erase pixels

@BastiaanOlij
Copy link
Contributor

BastiaanOlij commented May 21, 2018

@blurymind check out the shaders in my terrain editor:
https://github.com/BastiaanOlij/godot-terrain-edit/blob/master/Shaders/Splatmap-brush.shader

Keep in mind, you need a fresh master build for this, it was only added a week or so ago so this is not in any of the official builds and won't be till 3.1 comes out.

(and also keep in mind that if you do use SCREEN_TEXTURE, it creates a copy of your viewport texture for the first shader that uses it, so you can't do any overlapping logic)

@blurymind
Copy link
Author

blurymind commented May 22, 2018

@BastiaanOlij so it can only be done via a shader? Is there no way to use a simple draw() method?
Seems like taking a long route just to be able to erase pixels you painted in a simple pixel editor.
It does make a lot more sense in the context of a 3d terrain editor though

@BastiaanOlij
Copy link
Contributor

@blurymind thing is, when you're using viewports you're dealing with a texture that lives in GPU memory (whatever the correct term for that is:)). Shaders are the most effective way to write into that but I do see that in the use case you sketch it feels like overkill :)

However you don't have simple direct access to it.

I guess we could add some sort of interface that allows you to call glTexSubImage2D to overwrite a small part of a texture with a subimage.

@blurymind
Copy link
Author

blurymind commented May 22, 2018

In my usecase, the main problem was that we cant use any of the draw methods to erase pixels - which is required if one is to create a pixel editing tool with a simple eraser brush.

The original idea for the addon was to enable editing pixel art inside godot without leaving it- just the ability to draw lines and erase them with a brush. We got the first part down.

So in order to be able to also delete pixels, I have to now completely abandon the draw_line method - which is really what is expected to be used in the api for such a thing.

I was hoping that one would be able to simply set the color of a line to a specific value - with any alpha value - and that alpha value should be able to overwrite any pixels that the line is drawn ontop of.
So to make an eraser - it could be as simple as setting the alpha to 0 and putting the line on an overwrite pixels below instead of composit blend mode.

I think that is what was suggested as another blending mode - surely there is some complexity to achieve it - exposing that functionality to draw methods would make them much more powerful imo. Without it they feel kind of limited to only be able to draw new lines, erase specific lines, or erase the entire canvas. We cant directly erase pixels by drawing lines ontop of them at all

@Zylann
Copy link
Contributor

Zylann commented May 22, 2018

In my usecase, the main problem was that we cant use any of the draw methods to erase pixels - which is required if one is to create a pixel editing tool with a simple eraser brush.

You can if the viewport in which you draw has a render-target. Any draw method will work, with the appropriate material or shader, nothing complex. This is the actual solution to being able to erase pixels, that's how I prototyped a drawing tool :)

@AlvaroAV
Copy link

AlvaroAV commented Mar 10, 2019

I managed to delete drawn pixels following @Zylann suggestion.

I have the next Node structure (Control Node --> Viewport, Sprite)

image

The viewport has this properties:

  • Transparent BG = True
  • Clear Mode = Next Frame

The board node is a Sprite with a CanvasItemMaterial on the Material property:
image

On the CanvasItemMaterial the Blend Mode is set to add:

image

Then I can use the method draw_line with an HEX ARGB color to erase previously drawn pixels.

I use next color to delete drawn pixels:

Color("#ff000000")

@blurymind
Copy link
Author

blurymind commented Mar 11, 2019 via email

@AlvaroAV
Copy link

@blurymind I'll try to upload an example project with this working on Godot 3.1

I'm quite busy until weekend, I'll let you know when it's ready

@enrics
Copy link

enrics commented Apr 18, 2019

Any update on this? I've tried @AlvaroAV method but having the texture in add blend mode is problematic if I intend to have multiple textures stacked, need them on normal mode to be able to paint them properly.

@starryalley
Copy link
Contributor

starryalley commented Mar 4, 2020

I also managed to clear pixels using what is suggested above: Using a Viewport and draw inside that Viewport. However I'm using Line2D to implement both draw and eraser.

The drawing Line2D has material blend mode BLEND_MODE_MIX, while the eraser Line2D has material blend mode BLEND_MODE_SUB.

Very simple demo project:
https://github.com/starryalley/godot-draw-eraser-demo

@akien-mga
Copy link
Member

Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.

The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.

If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!

@jonSP12
Copy link

jonSP12 commented Jun 29, 2023

I also managed to clear pixels using what is suggested above: Using a Viewport and draw inside that Viewport. However I'm using Line2D to implement both draw and eraser.

The drawing Line2D has material blend mode BLEND_MODE_MIX, while the eraser Line2D has material blend mode BLEND_MODE_SUB.

Very simple demo project: https://github.com/starryalley/godot-draw-eraser-demo

Eraser: CanvasItemMaterial.BLEND_MODE_SUB
no longer works on godot 4

eraser

@AThousandShips
Copy link
Member

Please read above, this has been closed for over 3 years, open a proposal if one doesn't exist if you're interested in this feature, but this is long closed and doesn't need resurrection

@jonSP12
Copy link

jonSP12 commented Jun 29, 2023

Please read above, this has been closed for over 3 years, open a proposal if one doesn't exist if you're interested in this feature, but this is long closed and doesn't need resurrection

Ho that's great... then y just have to wait 3 more years until someone else mentions this again !

@AThousandShips
Copy link
Member

Open a proposal if you want to see this added, or wait, it's up to you:
#10255 (comment)

@jonSP12
Copy link

jonSP12 commented Jun 29, 2023

Very simple demo project: https://github.com/starryalley/godot-draw-eraser-demo

its Canvas Material LIGHT MODE
In function Ready add material LIGHT_MODE_NORMAL and LIGHT_MODE_LIGHT_ONLY

func _ready():
draw_material = CanvasItemMaterial.new();
draw_material.blend_mode = CanvasItemMaterial.BLEND_MODE_MIX
draw_material.light_mode = CanvasItemMaterial.LIGHT_MODE_NORMAL; ##############

erase_material = CanvasItemMaterial.new();
erase_material.blend_mode = CanvasItemMaterial.BLEND_MODE_SUB;
erase_material.light_mode = CanvasItemMaterial.LIGHT_MODE_LIGHT_ONLY; ########

pass

eraser1

----------------//----------//---------------

eraser2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests