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

Better Recolored Images #2

Open
0phoff opened this issue Apr 10, 2024 · 5 comments
Open

Better Recolored Images #2

0phoff opened this issue Apr 10, 2024 · 5 comments
Assignees

Comments

@0phoff
Copy link

0phoff commented Apr 10, 2024

I saw your vault tour video and was instantly hooked on your page-blueprint class!
I love the way you managed to recolor images to fit the theme as well, but noticed this could use some improvement.
Your current recolor-images class only works for images like diagrams, where the background is transparent. If there is no transparency in the image, the entire image becomes a single color.

Proposed Solution

To fix this, we thus need a way to transform the brightness of an image to an opacity. We then need to merge the original alpha values of the image with this newly computed alpha mask (to retain the original transparency of the image). Finally, we can colorize the image.

flowchart LR
  A[IMAGE] --> B[LUMINANCE TO ALPHA]
  A --> C[COMPOSITE ALPHA]
  B --> C
  C --> D[COLORIZE]
Loading

One decision that is important in this pipeline, is whether you want white or black pixels to show fully drawn after the recolor operation. Most images out there are made with darker "ink" on a white background, so I think it is more useful to show black pixels fully. This means we need to add an invert(1) operation before the pipeline.

CSS Implementation

This image pipeline is not trivial, but such a pipeline is possible with SVG filters:

<svg xmlns="http://www.w3.org/2000/svg">
  <filter id="luminance-to-alpha">
    <!-- STEP1: Convert luminance to alpha -->
    <!-- https://stackoverflow.com/a/596243 -->
    <feColorMatrix type="matrix" result="lumalpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.213 0.715 0.072 0 0"/>

    <!-- STEP2: Multiply original alpha mask with luminance -->
    <feComposite in="SourceGraphic" in2="lumalpha" operator="arithmetic" k1="1" k2="0" k3="0" k4="0" />
  </filter>
</svg>

If this SVG is available somewhere in the HTML file, you can use this filter in the CSS.
Even better, you can embed this entire SVG string entirely in CSS:

img {
  --svg-luminance-alpha: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><filter id="f"><feColorMatrix type="matrix" result="lumalpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.213 0.715 0.072 0 0"/><feComposite in="SourceGraphic" in2="lumalpha" operator="arithmetic" k1="1" /></filter></svg>#f');
  --image-effect: invert(52%) sepia(60%) saturate(2521%) hue-rotate(105deg) brightness(96%) contrast(84%);
  filter: invert(1) var(--svg-luminance-alpha) var(--image-effect);
}

A final "improvement" here is that I removed the first brightness(0) saturate(100%) steps of all of your --image-effect variables. The SVG pipeline already transforms the image to a pure black+alpha version and thus these 2 steps are not needed anymore.

You can find a little demo of this effect here.
In this demo you will see the need to composite the original alpha mask in the first example (jittered borders).
The other examples show the difference of the effect with and without invert(1). Some look better without the inversion, but all are legible with it so that's what I decided to use (see rationale above).

Excalidraw Integration

While I am fine with the inversion choice I made, there is one part where this does not work.
I use excalidraw and make my diagrams with white ink on a dark background in these files.
I thus added a specific CSS selector for my excalidraw drawings and changed the filter to var(--svg-luminance-alpha) var(--image-effect).

The CSS selector for your excalidraw embeds depends on the excalidraw setting: "Image type in markdown preview".
I have it set to "Native SVG". and thus my selector is simply svg {}.


Thanks for sharing your setup and inspiring me to make my notes nicer!
Feel free to do whatever you want with this effect and to discard this issue if it does not fit your needs.

I'll end this issue with a small example of the effect working on one of my notes. The original images are black/white without any transparency, but thanks to the SVG filter everything still looks awesome!

Example

image

image

@CyanVoxel CyanVoxel self-assigned this Apr 11, 2024
@CyanVoxel
Copy link
Owner

This looks incredible!! Being able to recolor any image without the need for transparency is a massive game changer - I never knew the CSS was possible! Thank you so much for putting in all this effort, I'll try to get this implemented!

@0phoff
Copy link
Author

0phoff commented Apr 17, 2024

Quick note about the invert operator.

While you still need to pick whether you want white or black pixels showing opaque by default, I noticed you can use the alt property of images to toggle the behavior.

Here is the snippet of code I use:

:is(.page-white, .page-manila, .page-blueprint, .pen-white, .pen-gray, .pen-black, .pen-red, .pen-green, .pen-blue).recolor-img {
  --svg-composite-luminance: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><filter id="f"><feColorMatrix type="matrix" result="lumalpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.213 0.715 0.072 0 0"/><feComposite in="SourceGraphic" in2="lumalpha" operator="arithmetic" k1="1" /></filter></svg>#f');

  img {
    filter: invert(1) var(--svg-composite-luminance) var(--image-effect);
  }
  img[alt*="invert"] {
    filter: var(--svg-composite-luminance) var(--image-effect);
  }

  /* Excalidraw Specific */
  .excalidraw-svg>svg {
    filter: var(--svg-composite-luminance) var(--image-effect);
  }
  .excalidraw-svg-invert>svg {
    filter: invert(1) var(--svg-composite-luminance) var(--image-effect);
  }
}

This allows me to have a default state (black pixels are opaque for images, white for SVGs), but invert the behavior by adding the "invert" word in the alt field:

# Regular mode
![[image.png]]
![[excalidraw drawing]]

# Inverted mode
![[image.png|invert]]
![[excalidraw drawing|invert]]

# Inverted with size options
![[image.png|invert|600]]
![[excalidraw drawing|invert|1000]]

@CyanVoxel
Copy link
Owner

Woah, I thought this couldn't get any better! Sorry for the delay in getting this implemented, it's still very much on my radar and I'll see about getting to this soon!

@0phoff
Copy link
Author

0phoff commented Apr 18, 2024

Woah, I thought this couldn't get any better! Sorry for the delay in getting this implemented, it's still very much on my radar and I'll see about getting to this soon!

No issues, I already have everything implemented on my vault, so take your time.
I will just keep posting here if I find any other issues/addons! 😇

@Fire0nTop
Copy link

Fire0nTop commented Nov 5, 2024

Wow this is amazing!!

Maybe make a pull request to share this.

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

No branches or pull requests

3 participants