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

Implement cleanEdge rotation and scaling #794

Merged
merged 4 commits into from
Dec 24, 2022

Conversation

OverloadedOrama
Copy link
Member

@OverloadedOrama OverloadedOrama commented Dec 16, 2022

Implements #793. Clean4x CleanEdge is a new pixel art rotation algorithm in the form of a shader by torcado. See https://cohost.org/torcado/post/621636-clean4x-a-pixel-art and https://twitter.com/torcado/status/1603530954038992896 for more details.

Since the developer said that the code is public, I took the liberty of attempting to implement it in Pixelorama. Unfortunately, there was no way to rotate with the shader itself, so I'm attempting to combine it with nearest neighbor rotation. I'm not sure that I'm doing a good job though, since the results seem different that the ones shown by torcado. Which is why I'm opening this as a PR, to get more feedback of whether this is correct or not, and if anyone has any ideas of how else we could handle this, you can give it a shot.

EDIT: The clean4x algorithm has been updated and renamed to cleanEdge. The new code can be found here, along with a new website where you can import sprites and rotate and/or scale them with cleanEdge: http://torcado.com/cleanEdge/

@OverloadedOrama
Copy link
Member Author

image

Testing the bird sprite from one of the examples and rotating it to 315 degrees, the result seems quite different from the video posted by the developer. At first I thought there was something wrong with my modification, because I added NN into the shader, but I also tested it on another Godot project using the original version of the shader on a Sprite node, and then rendered the game window with the viewport stretch node, and the result also appears to be different from the video, and quite similar to Pixelorama, maybe even a bit worse. So my implementation may not be so wrong after all?

image

It could be possible that the sprite has slightly different colors that the original, or that the degree or the pivot are different. Testing this is a bit hard without an example project from the creator, but my main goal right now is to figure out if my implementation of the algorithm is correct and if it looks good. If the result is desirable, then we should go ahead and merge, even if there may be slight differences from the results generated by the original shader here and there.

@dphaldes
Copy link
Contributor

Does the image size match with the one provided in the video example ? Maybe the author used bigger sample images

@OverloadedOrama OverloadedOrama changed the title Implement clear4x rotation Implement clearEdge rotation Dec 22, 2022
@OverloadedOrama OverloadedOrama changed the title Implement clearEdge rotation Implement cleanEdge rotation Dec 22, 2022
@OverloadedOrama
Copy link
Member Author

OverloadedOrama commented Dec 22, 2022

The developer has updated the and renamed it to cleanEdge. Along with the update, they've also made a tool that lets us preview the algorithm in action.

Does the image size match with the one provided in the video example ? Maybe the author used bigger sample images

I think the image size was the same but I'm not sure. Now with the tool, I downloaded the exact same sample image and tested it. This is how it looks rotated 315 degrees on the website:
image

And this is how it looks on Pixelorama:
image

I think the results look identical, but I will do more tests. Still, this is an amazing algorithm and it seems to be working very well. The main negative I've noticed is that the editor is slow when editing the shader code, and Pixelorama takes a bit longer to load. It may be because cleanEdge is more complex and takes more time to compile compared to clean4x, the previous version of the algorithm. On my machine it's barely noticeable (NVIDIA GTX 1060 6GB), but I am slightly worried about devices with weaker GPUs.

I've also had to comment out the #defines, since Godot 3's shading language doesn't seem to support them. I think Godot 4 does, however, so we can uncomment them when we port to Godot 4.

@torcado194
Copy link

this is really cool to see!

Some notes on implementation that might be useful for optimization sake, etc. (which you may be doing, i havent checked the code. also some of this is mentioned briefly in the comments, but i figured id mention anyway):

  • The new version definitely takes longer to compile, i havent tested how much longer. as far as runtime speed, my tests showed me it was about 2 times slower, but it's hard to test that kind of thing robustly.
  • If you're just using the algorithm for rotations, you should definitely disable CLEANUP (so, in your case without #defines, you should remove those blocks of code entirely). these sections add a significant number of checks for little gain.
  • When I was implementing cleanEdge for the demo page, i had a lot of trouble regarding floating point accuracy and rounding. I hadn't run into much of that with my godot 4 test project, but it's very possible it could be different with godot 3 or just your project setup itself. You may need to take special care for odd width/height images vs even, such as offsetting the image by half a pixel. I had to do some strange offsetting in WebGL to get it to look right. For godot, i added a weird +0.0001 to the uv position. if you decrease this down to like +0.0000001, you'll notice the image break. and if you increase it up to like 0.1, youll see how it shifts the image. there's some middle-ground here that is ideal, but I don't know where and I don't know what it's based on. It might not improve results or anything, but it's something to keep in mind in case an image looks broken.
  • Other than that, visual differences from your implementation to the webpage/gifs may be a result of the rasterization system. for my test project in godot 4, i simply have a viewport scaling up a scene with default NN filtering. camera alignments or display settings may have an effect as well.

(If it would be useful, i can upload a barebones godot project with the algorithm, just to compare against)

There are likely some further optimizations I could do, someone with more technical knowledge of shaders might have some insight. There's some ideas regarding dFdx/dFdy and texelFetch that I'm going to look into.
I haven't considered what would make compiling take less time though. In either case I'll probably look into some optimizations, if I make any meaningful improvements I'll let you know :)

@OverloadedOrama
Copy link
Member Author

@torcado194 Thank you for your comments! :D

@OverloadedOrama OverloadedOrama changed the title Implement cleanEdge rotation Implement cleanEdge rotation and scaling Dec 24, 2022
@OverloadedOrama
Copy link
Member Author

OverloadedOrama commented Dec 24, 2022

I went ahead and implemented scaling using cleanEdge as well, as the 7th interpolation option in the Scale image dialog. Unfortunately, this means that I had to uncomment the CLEANUP-related code in the shader, which I think made the shader a bit slower than it was. I exposed slopes and cleanup as uniforms, since Godot 3 does not support defines. Hopefully I did it right. Cleanup is false on rotation, and true on scaling. Scaling definitely takes a bit of time, especially if there are a lot of frames and layers, so I would advice caution when using cleanEdge for large projects. But it seems to work pretty well and the results are beautiful.

showcase_landscape_v09_4x_nn
Scaled up 4 times using nearest interpolation. Art by @Erevoid.

showcase_landscape_v09_4x_cleanEdge
Scaled up 4 times using cleanEdge.

@OverloadedOrama
Copy link
Member Author

I think it's ready to get merged. The results seem to be fine and I don't see any issues with the implementation. My only worry is that Pixelorama may take longer to open now on slower hardware, but maybe loading the shader only when the scale or rotate dialog first opens could be a good solution, instead of using preload. I will leave it as is for now, and we can make changes and improvements later if we want to.

So I will go ahead and merge it now. Consider this a Christmas gift. :D Big thanks to torcado for the shader and for making it open source!

@OverloadedOrama OverloadedOrama merged commit 222e7a1 into Orama-Interactive:master Dec 24, 2022
@OverloadedOrama OverloadedOrama deleted the clean4x branch December 24, 2022 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants