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 "Add Image Diff" node #2385

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

Splendide-Imaginarius
Copy link
Contributor

This PR implements an "Add Image Diff" node. The intended usage looks like this:

  1. I have a high-res image "HQ.png" and a low-res image "LQ.png", with identical content.
  2. I also have a low-res image "LQ-plus.png", which is identical in size and content to "LQ.png" except that it has a translucent overlay applied to it.
  3. I want to apply the translucent overlay to "HQ.png" without losing the high-res detail that's already in "HQ.png".
  4. I upscale both of the low-res images with ESRGAN to match the resolution of "HQ.png".
  5. I run the "Add Image Diff" node. The input image is "HQ.png"; the reference initial image is the upscaled "LQ.png"; the reference goal image is the upscaled "LQ-plus.png".
  6. The result is that the difference between "LQ-Plus.png" and "LQ.png" (which is equivalent to the overlay by itself) has been added to "HQ.png", thus the overlay has been applied without losing any high-res detail in "HQ.png".

@Splendide-Imaginarius
Copy link
Contributor Author

Not sure what the Pyright error is about; it looks like it's unhappy about this line:

invalid_mask = result_alpha <= 0

Is that a false positive? The code seems to work OK.

@joeyballentine
Copy link
Member

Just add a # type: ignore comment. Pyright complains about using math ops on numpy arrays even though they're perfectly valid.

@RunDevelopment
Copy link
Member

It kinda sounds like what you want is this:

image

@Splendide-Imaginarius
Copy link
Contributor Author

It kinda sounds like what you want is this:

image

@RunDevelopment You're suggesting applying the High Pass node to the HQ.png? That's definitely an interesting approach, and it does look mildly better than the original LQ-plus.png, but for my test images it doesn't look nearly as good as what this PR produces (unless I'm doing it wrong).

@RunDevelopment
Copy link
Member

I think I understand what you want to do now. Essentially, you have an LR image L, an HR image H, and an LR image with an overlay o. So you have L, H, and L+o as images. You then upscale L and L+o to get up(L) and up(L+o). Finally, this node is supposed to output the result of H + up(L+o) - up(L), which is approximately H + up(o) (but not really because upscaling in finicky).

This would also explain why my suggestion with High Pass kinda works. High Pass can somewhat approximate the up(L+o) - up(L) term.

We can split what your node is doing in 2 parts: calculating up(L+o) - up(L) and adding that to H.

  1. up(L+o) - up(L): This is very similar to what High Pass is doing. Given an image A, High Pass just essentially returns A - blur(A) + 0.5. If instead of blurring, we configured High Pass to take another image as the "blurred image", then we can calculate up(L+o) - up(L) no problem.
  2. H + x: This operation is already implemented by the Overlay blend mode in Blend Images. Kinda.

Of course, my above suggestions assume that I understood your use case correctly. So did I get that correctly? If so, what do you think about my suggestions?

@RunDevelopment
Copy link
Member

Actually, I think I'm gonna implement custom "blurred" images for High Pass quickly myself. It's a feature I wanted before anyway.

@Splendide-Imaginarius
Copy link
Contributor Author

@RunDevelopment Yes, I think your description of my use case is accurate (unless I'm misreading, which I suppose is possible). The main reason I combined them into one node was that I wasn't sure if there was any practical use for a diff image besides adding it to another image, and also I wasn't sure if passing around negative RGB values between nodes would break anything. If passing around negative RGB values between nodes is OK, then yes, I think splitting this node in two is OK.

One potential caveat: my implementation has special-case logic for fully transparent pixels, which was necessary for RGBA images to look correct. I'm not sure whether High Pass and Overlay can do that out of the box, nor am I sure whether adding that special-case logic to High Pass and Overlay will break other use cases.

@RunDevelopment
Copy link
Member

The main reason I combined them into one node was that I wasn't sure if there was any practical use for a diff image besides adding it to another image

I have an entire folder of just these high frequency details :) They are super useful to have around when you want to add more crunch to a texture.

I wasn't sure if passing around negative RGB values between nodes would break anything.

I would. We only allow passing 0-1 values. That's why High Pass does the + 0.5. This gets rid of the negatives. Almost. We still clip values outside 0-1, so it's a bit lossy. You can change the contrast to fix this though.

One potential caveat: my implementation has special-case logic for fully transparent pixels, which was necessary for RGBA images to look correct. I'm not sure whether High Pass and Overlay can do that out of the box, nor am I sure whether adding that special-case logic to High Pass and Overlay will break other use cases.

The alpha logic in High Pass is just something tacked on. It's kinda broken rn tbh. We might as well fix it...

It's the same old alpha == 0 -> diff = 0, right?

@Splendide-Imaginarius
Copy link
Contributor Author

I would. We only allow passing 0-1 values. That's why High Pass does the + 0.5. This gets rid of the negatives. Almost. We still clip values outside 0-1, so it's a bit lossy. You can change the contrast to fix this though.

@RunDevelopment Hmm, if potentially lossy workarounds are needed for passing between nodes, I think I'd prefer to keep this within one node, so that users don't have to analyze whether they're hitting the lossy condition. Unless you have another way to fix that?

It's the same old alpha == 0 -> diff = 0, right?

This PR copies the reference goal pixel (i.e. it discards the reference init pixel and the input pixel) if the input pixel is fully transparent. This is the correct behavior for my use case, but I don't know whether that behavior would break other use cases.

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.

None yet

3 participants