CyanVoxel / Obsidian-Vault-Template

A Template for my Obsidian Vault.
MIT License
225 stars 25 forks source link

Better Recolored Images #2

Open 0phoff opened 2 months ago

0phoff commented 2 months ago

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]

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](https://github.com/CyanVoxel/Obsidian-Vault-Template/assets/11853089/42a417c5-c9ca-49ea-ad2d-741430a6a03b) ![image](https://github.com/CyanVoxel/Obsidian-Vault-Template/assets/11853089/1ac895c0-e818-43ca-88a3-494a4eab688a)
CyanVoxel commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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! :innocent: