Implementing css/svg contrast filter using sharp (libvips) - svg-filters

I'm using libvips to transform images on the backend with a css/svg preview on the frontend to save resources.
I'm struggling with implementing the contrast css/svg filter function.
The specification shows contrast as a linear transformation in form of:
out = slope * in + intercept
where intercept should be:
intercept = - (0.5 * slope) + 0.5
This way, I can use contrast(1.25) in css preview of image modifications.
However, implementing this linear function in libvips through JS library sharp:
sharp.linear(contrast, - (0.5 * contrast) + 0.5)
Looking deeper into contrast changes for an image, the expectat result is that highs are put even higher and lows even lower. This looks like a contradition with the specification because the spec applies linear transformation so it should always multiple and add, makes highs higher but also lows a little big higher.
Using linear in sharp (and so in libvips) to change contrast the output actually looks like a brightness filter, which in the spcecification for css/svg filter is in form linear transformation without addition
out = slope * in
This looks to me like I might be misunderstanding what intercept does in svg linear function. Also, comparing svg and css shows differences. Using contrast(2) in css should mimic slope = 2 and intercept = -(0.5 * 2) + 0.5 = -0.5 in svg, which is not the case in this fiddle:
.svg {
filter: url(#contrast);
}
.css {
filter: contrast(2);
}
<img src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<img class="svg" src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<img class="css" src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<svg>
<filter id="contrast">
<feComponentTransfer>
<feFuncR type="linear" slope="2" intercept="-0.5"/>
<feFuncG type="linear" slope="2" intercept="-0.5"/>
<feFuncB type="linear" slope="2" intercept="-0.5"/>
</feComponentTransfer>
</filter>
</svg>
You can clearly see that the second image with svg filter looks different than the third one using css filter.
Is my understanding of filters completely wrong? I would expect there should some treshold somewhere to invert multiplication into division for lows.
How can I implement css contrast in different evnironments as a linear function with the same result?

Your intuition is not correct :) For input values less than 0.5 - the formula decreases the brightness - why? Let's take a contrast value of 2 and an input value of 0.4
Output = 2*0.4 - (0.5 *2) + 0.5
Output = 0.8 - 1 + 0.5
Output = 0.3
As you can see, when the input is lower than 0.5, the output will be always be less than the input because the sum of the slope component and the first (negative) component of the intercept will be equal to the contrast multiplied by the difference between the input and 0.5
This is the formula results on unitized values (floored and ceilinged on 0/1).
Also CSS Filters use sRGB color space by default. SVG Filters use linearRGB. You need to set the SVG filter color space to sRGB by adding attribute: color-interpolation-filters="sRGB" to your svg element. When you do this - your images look the same.
.svg {
filter: url(#contrast);
}
.css {
filter: contrast(2);
}
<img src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<img class="svg" src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<img class="css" src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<svg color-interpolation-filters="sRGB">
<filter id="contrast">
<feComponentTransfer>
<feFuncR type="linear" slope="2" intercept="-0.5"/>
<feFuncG type="linear" slope="2" intercept="-0.5"/>
<feFuncB type="linear" slope="2" intercept="-0.5"/>
</feComponentTransfer>
</filter>
</svg>

https://github.com/lovell/sharp/issues/1958
here you have a working formula from css:
filter: `contrast(${contrast})`
to sharp:
brightness = 1;
image.linear(brightness * constrast, brightness * (-(128 * contrast) + 128));
although, I havent figure out how to incorporate brightness into this yet

Related

Moving and scaling text layer relative to the parent in Vue.js

I'm creating a visual T-Shirt designer where the user can add custom text to a specific position. I develop this tool with Vue.js.
For the drag actions for the custom text I'm using the "vue-draggable-resizable' package for vue.js (https://github.com/mauricius/vue-draggable-resizable) with the following code:
<div v-for="layer of getLayers" class="vconf-create-builder-layer" :key="layer.uid" :style="{ background: 'url(' + getLayerImage(layer) + ') center / contain no-repeat' }">
<template v-for="tile of layer.data">
<VueDragResize :key="tile.uid" v-if="tile.type=='typography'" :parentLimitation="true" :isActive="true" :x="tile.boxPositionX" :y="tile.boxPositionY" :w="tile.editBoxWidth" :h="tile.editBoxHeight" #resizestop="updateTypoBoxSize($event, layer, tile)" #dragstop="updateTypoBoxPosition($event, layer, tile)">
<h2 class="typographyTextPreview" :style="{'font-size': tile.fontSize + 'px', 'line-height': tile.lineHeight + 'px', color: tile.fontColor}">{{tile.text ? tile.text : "Please enter an initial text in the settings"}}</h2>
</VueDragResize>
</template>
</div>
To save the position of the text I'm using the dragstop event. There I get the x and y coordinates relative to the parent. My parent is the t-shirt itself, embedded in a DIV container as background-image with background-size: contain. You can see it in the code above.
So when I resize the browser window, the t-shirt becomes smaller proportionally. The problem: The text layer changes the position. It makes sense, because the x and y coordinates are static values in pixel.
Image examples with resized browser window:
What would be the right way to be sure that the text layer always has the same position relative to the parent with a background image embedded, without to define the image with static pixel size?

SVG and dynamic resizing - vuejs

I am pulling hair from my head for weeks already.
I can't figure out how to deal with SVG files.
Essentially I want to have all my svg icons as components in vue, so I started converting them to components.
Here is an example of svg
<svg xmlns="http://www.w3.org/2000/svg">
<path d="M.75 10.5A.749.749 0 010 9.75v-7.5a.749.749 0 01.75-.75h6a.749.749 0 01.75.75.75.75 0 01-.75.75H1.5v6h6V6.75A.75.75 0 018.25 6a.75.75 0 01.75.75v3a.75.75 0 01-.75.75zm3.97-3.22a.75.75 0 010-1.06l4.5-4.5a.727.727 0 01.08-.053L8.379.746A.437.437 0 018.687 0h2.876A.437.437 0 0112 .437v2.876a.436.436 0 01-.745.308l-.921-.921a.588.588 0 01-.053.08l-4.5 4.5a.749.749 0 01-.531.22.749.749 0 01-.531-.22z"/>
</svg>
Now I was thinking to wrap it something like this:
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
:width="width"
:height="height"
viewBox="0 0 18 18"
:aria-labelledby="iconName"
role="presentation"
>
<title
:id="iconName"
lang="en"
>{{ iconName }} icon</title>
<g :fill="iconColor">
<slot/>
</g>
</svg>
</template>
But no matter what I do it does not resize, I tried to change viewBox to 0 0 100 100 or dynamic with 0 0 width height, with last one it does change but in not the way I understand it.
Also I struggled to make path to fill svg container, so if I change width and height svg is always bigger than path which also is not nice behavior. The only way I managed to be the size I want (but not dynamic) is by adding transform="scale(1.34,1.34)" to path, but that's not a fix.
What is the best way to create icons like that ? I fried my brain.
It helps to ensure the SVG files are created or exported nicely from whatever program (Figma, Illustrator, etc) to ensure there's no excess whitespace from the artboard included in the SVG. The SVG code provided looks like the SVG was placed on an artboard which was much larger than the graphic itself.
Beyond that, I manually experimented with the viewBox numbers to get a decent result (correct exporting avoids this manual work). Once the viewBox attributes are correctly set, you can apply other manipulations via classes or inline css to achieve your desired result.
<svg viewbox="0 0 12 11" xmlns="http://www.w3.org/2000/svg">
<path d="M.75 10.5A.749.749 0 010 9.75v-7.5a.749.749 0 01.75-.75h6a.749.749 0 01.75.75.75.75 0 01-.75.75H1.5v6h6V6.75A.75.75 0 018.25 6a.75.75 0 01.75.75v3a.75.75 0 01-.75.75zm3.97-3.22a.75.75 0 010-1.06l4.5-4.5a.727.727 0 01.08-.053L8.379.746A.437.437 0 018.687 0h2.876A.437.437 0 0112 .437v2.876a.436.436 0 01-.745.308l-.921-.921a.588.588 0 01-.053.08l-4.5 4.5a.749.749 0 01-.531.22.749.749 0 01-.531-.22z"/>
</svg>
I would recommend using class or style binding for hooking up any css styles in Vue, the docs cover this here: https://v2.vuejs.org/v2/guide/class-and-style.html
Demo with plain SVG and Vue versions: https://codepen.io/nikcornish/pen/OJXoWvj
You mean you want to change the size of the icon itself right?
If so, the width and the height attributes won't do anything for you. You can just apply regular css to your svg for that matter.
example:
.my-svg {
width: 100px;
height: 100px;
}
Regarding the viewBox attribute, that is a different concept:
The viewBox attribute defines the position and dimension, in user
space, of an SVG viewport.
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
EDIT:
Please note that you can also use svg-to-vue-component to use SVGs as components.

Does CanvasDrawingSession.DrawSvg() work in C++/winrt?

I've loaded a series of svg images by creating and storing a CanvasSvgDocument for each, and using its LoadAsync() method to load the svg:
nextSvg = CanvasSvgDocument(resourceCreator.Device());
auto fileStream = co_await nextFile.OpenReadAsync();
co_await nextSvg.LoadAsync(resourceCreator.Device(),fileStream);
This appears to load the svg, but when I use that stored svg in a drawing session nothing appears in the CanvasControl. Other items draw fine: shapes, lines, etc. - just not svgs:
session.DrawSvg(m_svg, boxSize, top, left);
In an attempt to discover the problem I've tried using GetXml() on the svg document in the hope that would show me its contents. The result is an abbreviated svg with no drawing information. I provide that here in case it's a hint:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>
Perhaps that is all that GetXml() is supposed to return?
I have successfully drawn svgs if they loaded as imagesources; the resulting bitmap image works. But is it possible that session.DrawSvg() is just not completely implemented yet in Win2D? I'd rather render the svg directly this way if it can be done.
[Update] p.s. The svgs are version 1.1 svgs converted from pdfs by one of the online conversion services. As I mentioned, they render fine if opened in Edge or other browsers.
[Update2] Thinking perhaps there is something in the svgs that Win2D doesn't like, I tried creating a simple one using as a model an online example - it just has a rectangle and a circle, as follows. But it also does not draw:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 140"
preserveAspectRatio="xMidYMid meet">
<title>Scalable Vector Graphics (SVG) Demo</title>
<circle cx="100" cy="100" r="50" stroke="#000" stroke-width="1" fill="#ccc" />
<rect x="200" y="50" width="250" height="100" rx="5" ry="5"
stroke="#000" stroke-width="1" fill="#ccc" />
</svg>
[Update] It appears that the problem lies in the way the svg is read from the Storage File. I'll post my own answer as soon as I have enough that it might be useful to someone.
The answer is that my code contains an egregious error: As was pointed out to me at GitHub, LoadAsync() is a static method. In my first line above, I create a CanvasSvgDocument, but in the third line I ignore the results of LoadAsync. LoadASync is also a constructor, so the proper code is
auto fileStream = co_await nextFile.OpenReadAsync();
nextSvg = co_await CanvasSvgDocument::LoadAsync(resourceCreator.Device(),fileStream);
That works! Only trouble is that what I really want is to use LoadFromXaml(), another static method, and there is a different issue with that to be addressed later. Probably also my error.

Flood Color Not respected on certain image

So I'm trying to use a filter on an image in an HTML page like so in order to overlay the image with a certain color. My sample image is white:
img {
filter: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' height='0'><defs><filter id='bronzo-filter'><feColorMatrix type='luminanceToAlpha' result='L2A'/><feFlood flood-color='green' result='colorfield'/><feBlend mode='multiply' in='L2A' in2='colorfield'/><feComposite operator='in' in2='SourceGraphic'/></filter></defs></svg>#bronzo-filter");
}
<img src="https://i.stack.imgur.com/K8CIc.png">
It seems that the result is always a black image even though the flood-color is specified as cyan. Is this because only a gray image would work properly for this kind of filtering overlay?
On the other hand for this image it seems with the same filter the flood color is respected. The question I want answered is why.
img {
filter: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' height='0'><defs><filter id='bronzo-filter'><feColorMatrix type='luminanceToAlpha' result='L2A'/><feFlood flood-color='green' result='colorfield'/><feBlend mode='multiply' in='L2A' in2='colorfield'/><feComposite operator='in' in2='SourceGraphic'/></filter></defs></svg>#bronzo-filter");
}
<img src="http://via.placeholder.com/350x150">
I tried with a third image, a greyified version of the first at #aaaaaa. It seems to respect the flood color. Here is the third try:
img {
filter: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' height='0'><defs><filter id='bronzo-filter'><feColorMatrix type='luminanceToAlpha' result='L2A'/><feFlood flood-color='green' result='colorfield'/><feBlend mode='multiply' in='L2A' in2='colorfield'/><feComposite operator='in' in2='SourceGraphic'/></filter></defs></svg>#bronzo-filter");
}
<img src="https://i.stack.imgur.com/V4gGT.png" />
After some testing I realized that flood colors can simply be specified via rgb like rgb(208,164,114); This still doesn't answer the question of why the white image always goes black while the other ones colored #aaaaaa always respect the flood color.
luminanceToAlpha converts the source image to a solid black image with varying transparency, it literally converts the luminance to alpha values and zeros out the regular color channels.
For your white image, it converts it into a solid black image (rgb 0,0,0) with 100% opacity. When you multiply anything with 0 you get 0. So when you multiply this with any flood color you get black.
For your other non-white images, the filter is converting them into partially transparent black images. When they reach the blend step, they're pre-multiplied into fully opaque shades of grey before being multiplied with the flood-color, so you're seeing the flood color show up.
For your use case of white images, you don't want to use L2A, you just want a simple multiply.
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' height='0'>
<defs>
<filter id='bronzo-filter'>
<feFlood flood-color='green' result='colorfield'/>
<feBlend mode='multiply' in='SourceGraphic' in2='colorfield'/>
<feComposite operator='in' in2='SourceGraphic'/>
</filter>
</defs>
</svg>

vuejs (2) style binding top/left properties

I'm trying to bind two style properties within a json object to an element in my html. I've tried using pixels and percentages, written in various ways (I realize pixel coordinates shown below do not match where % coordinates would place something, this is just an example).
{left: 30 + 'px', top:25 + 'px'} {left: '30px', top:'25px'}
{left: 30 + '%', top:25 + '%'} {left: '30%', top:'25%'}
and I'm binding like:
`v-bind:style="objnamehere"
When I reload the page, the element is not positioned at those locations. I'm not sure what I'm doing wrong. One of those should have worked based off the official examples here: https://v2.vuejs.org/v2/guide/class-and-style.html
In my case work this:
<div class="caption-text" :style="{top:y + 'px',left:x+'px'}">
Y and X are props in my Vue component
'top' and 'left' are css properties
Hope it will help somebody