Inkscape.org
Beginners' Questions Filter for B&W invert
  1. #1
    ronburk ronburk @ronburk

    I'm working in pure black and white and it would be very handy to have a filter that inverts the color of the target where it is the same as the color of the rendered image beneath it. IOW, if I draw a black line, I would like any portions of that line that cover up black pixels (from the rendered image beneath it in the Z-order) to turn into white. I can't locate a built-in filter that does that. Did I miss one, or is it possible to accomplish this with a custom filter?

  2. #2
    inklinea inklinea @inklineaā›°ļø
    *

    This can be done, and is easy, but requires a bit of practise.

    It is achieved using path operation, rather than a filter.

    I used the path intersection and paste in place to create the attached svg.

    Intersection creates the basic shape of the line overlapping the background.

    The result can be set to black or white fill

    Paste in place pastes the background back.

    The stacking order for that background image in the layer is then changed using pageup / pagedown / end / or home keys

    Is that what you are looking for ?Ā 

    Ā 

    Difftestdone
    Difftest
  3. #3
    ronburk ronburk @ronburk
    šŸ‘šŸ‘

    Thanks, I'm really after filter information here and all the convenience of being able to make the inversion "just work" as I make arbitrary changes to the background.

    Seems like this should be possible with a filter, but I haven't made one work yet. I see the "need to add 'enable-background' tag" in the manual but I'm not sure whether that's my problem or if it's even still true in the latest Inkscape release. Seems like that is deprecated in browsers these days.

    Ā 

  4. #4
    ronburk ronburk @ronburk

    The (possibly outdated) trick in the manual to force "enable-background" on a layer does not seem to do anything in the latest release build (perhaps it's not supposed to).

    I made an feColorMatrix filter for inversion, which seemed to work fine on "Source Graphic", but if I connected it to "Background Image" the target simply disappeared. Edited the XML to force an enable-background (both "new" and "accumulate") on the enclosing layer with no difference. So it seems like I cannot figure out how to apply a given filter primitive to "Background Image."

  5. #5
    Luiz Lucas Luiz Lucas @luizlllucas

    Nice!

  6. #6
    Xav Xav @XavšŸ‘¹

    @ronburk You're right, the old trick doesn't seem to work any more and, as you've found, manually adding the attribute doesn't help. The 'enable-background' attribute is listed as deprecated in SVG2 (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/enable-background), so I can only assume that the move to support more SVG2 features in Inkscape has meant that what little support there was for this feature is effectively gone.

    It's a shame, as it could enable effects and tricks that just aren't possible any other way - and I never really understood why it was never fixed to work out-of-the-box with the filter editor, without requiring the layer blend mode hack. So many good features in SVG are being subsumed into CSS - but with less capability - and I think this is probably a victim of the same 'progress'.

    Ā 

    Unfortunately I can't think of any practical workarounds. There might be something possible with a Frankenstein combination of clones, masks and filters, but nothing that's as simple and practical as just applying a single filter that uses the background image as a source. It's a real measure of just how complex something is when I describe creating a filter in Inkscape as the simpler option!

    Ā 

  7. #7
    Tyler Durden Tyler Durden @TylerDurdenāš–

    Not sure what the final result needs to be, but keep layer modes in mind.

    Difference mode set on top layer.

    Layermode
  8. #8
    ronburk ronburk @ronburk

    Thanks! See also this thread. Upshot there is you can add an enable-background="new" attribute to the root node, but you have to restart for it to take effect, which I was missing.

    I'm not sure why I find this hard to describe. I finally sat down and drew the truth table and realized that all I'm looking for in essence is a filter that completely ignores the target object and that flips all the bits in the (covered portion of the) background image. In B&W, which is all I care about, a black pixel would have 00000000b in each channel, which would get inverted to 11111111b (white) and vice versa. In Boolean, this is a simple NOT(background-image), Terribly simple, but not necessarily with SVG filter primitives I guess, which I struggle to familiarize myself with.

  9. #9
    ronburk ronburk @ronburk
    *

    Ah, finally got it right! The top-most rectangle (the one being filtered) has a fill of Black.

    Ā 

    Ā 

    Ā 

  10. #10
    Xav Xav @XavšŸ‘¹

    That's good news! It's a shame that there's no way to add the attribute except via the XML editor (why does this still not get added when a filter uses Background Image/Alpha?), and even more of a shame that a restart is needed (I'm pretty certain that didn't used to be the case, but I wouldn't swear to it).

    You also need to add a Composite step, with the operator set to "In" and the second source as "Source alpha", for it to work with shapes other than horizontally aligned rectangles.

    Ā 

    You can also use a Component Transfer filter instead of the Color Matrix filter, which is arguably more in keeping with the spirit of what you're trying to do - i.e. a step function where any values below 0.5 are converted to 1.0, and values above 0.5 are converted to 0. The documentation for Component Transfer is usually tricky to follow, and gets too mathematical, but the short version is that setting R, G & B to 'Discrete' with values of '1 0' does the trick (example attached).

    Ā 

  11. #11
    ronburk ronburk @ronburk

    Thank you for fixing my naive attempt, @Xav ! Yours worked a champ (once I carefully followed all your instructions.)

    I think I saw an old thread where someone discussed setting that attribute on your template file so you never have to worry about it, but I often have a gajillion nodes so I'm all for not paying for it when I'm not using it.

    BTW, I want to use this for things in line art like a chain-link fence and rain that have background objects. Real artists just tediously switch between blank ink and white out, but I'm lazy enough I would rather draw foreground separately, scan them into Inkscape, then composite them with the filter. Fair use example from Spiegelman (clearly a "real" artist).

  12. #12
    Xav Xav @XavšŸ‘¹

    For anyone that's interested in this topic, and why I suggested using Component Transfer rather than Color Matrix, here's a bit of a breakdown of what's happening.

    Aim: The filter should invert the colour of every pixel beneath it such that black (RGB: 0, 0, 0) becomes white (RGB 255, 255, 255) and vice versa.

    Usually filters just act on the object to which they're applied. For example, if you add a Gaussian Blur to a rectangle it's only the pixels in the rectangle that are used, with the final result being overlaid on the background. For this filter, however, we need to read each background pixel and use it to calculate the value of the filter's output. This is nominally achieved by connecting to the "Background Image" source in the filter editor, but there's more to it than that...

    In order to read the pixel data from the Background Image source, Inkscape first has to calculate what the cumulative effect is of every element beneath the filtered one - in other words it has to internally render the whole image stack up to that point before it can even begin to calculate the results of the filter. This is a lot of work, and prevents some rendering optimisations being used. For this reason the SVG spec requires a file to 'opt-in' to this mode of operation, rather than making it the default.

    The easiest way to opt-in is to add an "enable-background" attribute to the root SVG element, with a value of "new". There used to be a way to indirectly achieve this via the Layers dialog, but that no longer works in 1.0, so it needs to be added via the XML editor. Unfortunately it doesn't actually take effect until the file is saved and Inkscape is reloaded.

    Ā 

    With that done, it's time to look at the filter itself. @ronburk's original approach was to use the Color Matrix filter primitive. This allows some powerful manipulations of the R, G, B and A channels of each pixel - such as mapping R to G, or adding half the B value and a quarter of the G channel to R together with a fixed offset. If we just consider the R channel, the default values are "1 0 0 0 0". This means that the resultant R value should be the input R value multiplied by 1, plus GĀ Ć— 0, plus B Ɨ 0, plus A Ɨ 0, plus a fixed offset of 0. In other words this simplifies down to: R' = R Ɨ 1 (where R is the value of the red channel from the background pixel, and R' is the output value for the red channel from the filter for that pixel).

    This can be shown on a simple chart of the mapping from input (x-axis) to output (y-axis). Note that, in filters, the values are mapped to a 0-1 range (e.g. each value is divided by 256), so the R value for a white pixel is 1.0 rather than 255.

    To invert the values you can multiply each one by -1. But this gives values that fall below the 0 line:

    By adding a fixed offset of +1, the whole line is shifted upwards to give the desired result.

    Now any R value of 0 is converted to 1, and any R value of 1 is converted to 0. The relevant line in the Color Matrix filter is "1 0 0 0 1", and equivalent lines for the G and B channels finish the inversion part of the filter.

    This works fine, and there's nothing wrong with this approach at all, but it can arguably be improved slightly. Firstly it only works as intended for values that are exactly 0 and 1 (i.e. 0 and 255). Other values aren't mapped to these extremes, but rather to a mid value, so if you accidentally use anything other than pure black and white in the image the output will also not be pure. It's also a little excessive, as we don't need a function that can map red to green - just one that can map the values of red back to other values of red. That's exactly what Component Transfer does.

    If we wanted to exactly implement the graph above using Component Transfer, we could set the Mode to "Linear", the Slope to "-1" and the Intercept to "1" (for each colour channel). Anyone who remembers "y =mx+c" or similar from their school maths lessons will notice that this is just setting m and c on a straight-line graph, and would give us exactly the same result as above. But it still leaves us with the same problem regarding values that aren't exactly 0 or 255.

    An alternative is to set the Mode to "Discrete". This lets us provide a series of (space-separated) steps for our map that will be distributed evenly across the input range. For example, if we use "1 0.66 0.33 0" then the graph would look like this:

    Hopefully you can see that this would cause any R value from 0-0.25 to be mapped to 1, while values from 0.25-0.5 would be mapped to 0.66, 0.5-0.75 goes to 0.33 and 0.75-1.0 turns into 0. We've basically "posterized" the input range, turning 256 values into just 4.

    Now you should be able to see why the final inversion filter uses "1 0" as its values: it just means that any input from 0-0.5 is turned into 1.0, and anything from 0.5-1.0 becomes 0:

    Ā 

    That handles the inversion itself, but so far this will just invert everything from the background that falls within the filter region. We want to be more specific than that, and only filter the parts where the object we've drawn is visible. This is the reason for the addition of the Composite filter primitive. By setting it to "In", and using "Source Alpha" for the second input, it effectively acts like a clipping path or mask, restricting the output of the filter to only those areas covered by the shape being filtered.


    That ended up as a much longer explanation than I expected, but hopefully it will help someone out - especially with regard to the under-used (and under-understood) Component Transfer filter primitive. It also has some other modes that I won't go into here, but suffice to say that when you want to map a colour channel back to itself, rather than to another channel, this is probably the right thing to use over the Color Matrix.

    Ā 

    Ā 

  13. #13
    ronburk ronburk @ronburk

    That's really awesome, @xav !Ā  I spent some time searching for decent not-afraid-of-math explanations of the SVG filter primitives and just couldn't find much. They are mostly nearly-identical recitations of the specifications of the primitives, not anything very helpful for problem-solving with filters. I wish your explanation could land on the web in a more visible spot and grow to encompass all the primitives. Obviously the strictly SVG portion has a much broader audience than just Inkscape users.

  14. #14
    Xav Xav @XavšŸ‘¹
    ronburk

    I wish your explanation could land on the web in a more visible spot and grow to encompass all the primitives.

    Like in a long-running series of articles in a free-to-download magazine, for example?

    http://www.peppertop.com/blog/?p=1563

    I covered filters in parts 48-57, with the last of those detailing the Component Transfer primitive.

    Admittedly it's not quite as visible as being directly on a web page, but maybe that will come in time (if I can ever find the time to do it).

  15. #15
    ronburk ronburk @ronburk

    Yes, that is a really valuable series that is regrettably not so visible on the web. Google does index it, but there is a penalty for being inside the PDF and I think most people won't connect with the relevant content via Google search. Maybe it's simply the inability to direct link to the article inside the PDF that kills this in the Google rankings. Even if you only pulled the filter tutorials out onto the web where they could be linked to directly from here, I suspect that would boost their visibility greatly. But who has time? Logically, the best leverage would be if Full Circle would (also) generate each PDF issue into separated web pages, helping all their content get more visibility, not just yours.