5

Is it possible to have DropShadowEffect to ignore certain colors when rendering shadow? To have sort of masked (color selective) shadow?

My problem is what shadow can be assigned to whole visual element (graph). It looks like this:

And I want

Notice grid lines without shadow (except 0,0 ones). This can be achieved by having 2 synchronized in zooming/offset graphs, one without shadow effect containing grid and another with shadow containing the rest. But I am not very happy about this solution (I predict lots of problems in the future with that solution). So I'd rather prefer to modify DropShadowEffect somehow.

I can create and use ShaderEffect, but I have no knowledge of how to program shaders to have actual shadow effect (if it can be produced by shaders at all).

Perhaps there is much easier way of doing something with DropShadowEffect itself? Anyone?

Edit

I tried to make shader effect:

sampler2D _input : register(s0);
float _width : register(C0);
float _height : register(C1);
float _depth : register(C2); // shadow depth

float4 main(float2 uv : TEXCOORD) : COLOR
{
    // get pixel size
    float2 pixel = {1 / _width, 1 / _height};

    // find color at offset
    float2 offset = float2(uv.x - pixel.x * _depth, uv.y - pixel.y * _depth);
    float4 color = tex2D(_input, offset);

    // convert to gray?
    //float gray = dot(color, float4(0.1, 0.1, 0.1, 0));
    //color = float4(gray, gray, gray, 1);

    // saturate?
    //color = saturate(color);

    return tex2D(_input, uv) + color;
}

But fail at everything.

Edit

Here is screenshot of graph appearance, which I like (to those, who try to convince me not to do this):

Currently it is achieved by having special Graph which has template

<Border x:Name="PART_Border" BorderThickness="1" BorderBrush="Gray" CornerRadius="4" Background="White">
    <Grid>
        <Image x:Name="PART_ImageBack" Stretch="None"/>
        <Image x:Name="PART_ImageFront" Stretch="None">
            <Image.Effect>
                <DropShadowEffect Opacity="0.3"/>
            </Image.Effect>
        </Image>
    </Grid>
</Border>

Everything is rendered onto PART_ImageFront (with shadow), while grid is rendered onto PART_ImageBack (without shadow). Performance-wise it is still good.

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • You could try applying the shadow effect only to the element(s) that you want to have a shadow, e.g., only to the series line and axes. If you apply the shadow to an ancestor element, it will render for all descendant elements, which is what seems to be happening here. – Mike Strobel Oct 06 '14 at 15:24
  • @MikeStrobel, there is only one element - graph. It uses `GDI+` to draw everything. All drawn are non-visuals, I can only apply shadow to the whole graph. – Sinatr Oct 06 '14 at 15:27
  • 1
    Ah, in that case I'm afraid you may have to resort to a custom shader effect, or to somehow generating a shadow within the GDI+ drawing code. Hopefully someone will be able to point you in the right direction. As an alternative, the graph looks simple enough--have you considered using a native WPF graphing solution? Or do you not have access to the raw data? – Mike Strobel Oct 06 '14 at 15:32
  • @MikeStrobel, wpf drawing is deffered (lagged) and slow, gdi is fast and nearly instant. The picture with simple graph is to show problem, in reality there will be plots with millions points. I will check possibility to use gdi shadows, thanks. – Sinatr Oct 07 '14 at 06:59
  • `Gdi` shadows are slow and crap. – Sinatr Oct 10 '14 at 11:56
  • 2
    Why do you even want to do this? It ruins the graph by adding unnecessary and distracting noise. If this graph is supposed to be actually used, not just put into a pretty presentation for a boss, I suggest not adding shadows at all. – Athari Oct 10 '14 at 12:05
  • @Athari, now its plain and boring. It needs some fanciness and life. Your suggestion is ok, but I still want to find a solution if possible =P I was also thinking to *animate* shadow. – Sinatr Oct 10 '14 at 12:23
  • I don't understand why you can't have two separate objects with and without shadows overlayed. What's the actual problem you have or foresee? – kjbartel Oct 10 '14 at 12:34
  • @kjbartel, it's a solution (second picture is in fact 2 graphs), but there are problems to synchronize content, so they both will render content for actual zoom window. It also is not very optimal to have 2 comprehensive objects instead of one. If no `DropShadowEffect`- or `ShaderEffect`-based solution is possible, then I will either leave graph to be boring and plain or will try to separate graph (perhaps by having `ControlTemplate` with 2 images and exposing one for shadow effect). – Sinatr Oct 10 '14 at 12:49
  • I have to agree, the drop shadows are distracting clutter, and a little confusing in that I could see myself wasting a couple seconds trying to figure out their significance, only to determine that there is none. – Mike Strobel Oct 10 '14 at 13:14
  • @MikeStrobel, point taken, but it looks like an excuse do not to help poor me =D – Sinatr Oct 10 '14 at 13:30
  • By the way, if the graph really has millions of points, consider removing points at certain scales as they only require time and memory but do not add any value because they are too close to each other – Emond Oct 15 '14 at 05:18
  • @ErnodeWeerd, I though about that, but i rely completely on GDI clipping. If I draw 100000 lines (which has few million points), then it's only a small cycle for me, while GDI decides whenever to draw that line or not. That cycle take time, but if I try to reduce it, I might spend more by doing complicated math (to test if segments belong to rectangle and if not finding intersection points). Btw, in `wpf`, there is no clipping (I noticed that, when asking [this](http://stackoverflow.com/q/25546500/1997232) question)! So yes, in wpf you **must** simplify drawing. – Sinatr Oct 15 '14 at 07:25

2 Answers2

2

I have zero experience with pixel shaders, but here's my quick and dirty attempt at a shadow effect that ignores "uncolored" pixels:

sampler2D _input : register(s0);
float _width : register(C0);
float _height : register(C1);
float _depth : register(C2);
float _opacity : register(C3);

float3 rgb_to_hsv(float3 RGB) {
    float r = RGB.x;
    float g = RGB.y;
    float b = RGB.z;

    float minChannel = min(r, min(g, b));
    float maxChannel = max(r, max(g, b));

    float h = 0;
    float s = 0;
    float v = maxChannel;

    float delta = maxChannel - minChannel;

    if (delta != 0) {
        s = delta / v;

        if (r == v) h = (g - b) / delta;
        else if (g == v) h = 2 + (b - r) / delta;
        else if (b == v) h = 4 + (r - g) / delta;
    }

    return float3(h, s, v);
}

float4 main(float2 uv : TEXCOORD) : COLOR {
    float width = _width; // 512;
    float height = _height; // 512;
    float depth = _depth; // 3;
    float opacity = _opacity; // 0.25;

    float2 pixel = { 1 / width, 1 / height };

    float2 offset = float2(uv.x - pixel.x * depth, uv.y - pixel.y * depth);
    float4 srcColor = tex2D(_input, offset);

    float3 srcHsv = rgb_to_hsv(srcColor);
    float4 dstColor = tex2D(_input, uv);

    // add shadow for colored pixels only
    // tweak saturation threshold as necessary
    if (srcHsv.y >= 0.1) {
        float gray = dot(srcColor, float4(0.1, 0.1, 0.1, 0.0));
        float4 multiplier = float4(gray, gray, gray, opacity * srcColor.a);
        return dstColor + (float4(0.1, 0.1, 0.1, 1.0) * multiplier);
    } 

    return dstColor;
}

Here it is in action against a (totally legit) chart that I drew in Blend with the pencil tool:

Chart

The shader effect is applied on the root panel containing the axes, grid lines, and series lines, and it generates a shadow only for the series lines.

I don't think it's realistic to expect a shader to be able to apply a shadow to the axes and labels while ignoring the grid lines; the antialiasing on the text is bound to intersect the color/saturation range of the grid lines. I think applying the shadow to just the series lines is cleaner and more aesthetically pleasing anyway.

Mike Strobel
  • 25,075
  • 57
  • 69
  • You're welcome to try to improve on the idea, but I think this is about as close as you're going to get. This should deal with antialiasing just fine; the shadow opacity is multiplied by the source pixel opacity, so the shadow of a partially transparent edge will be more transparent than the rest of the shadow. If the grid lines and axes are both shades of black/gray, it'll be hard to differentiate one from the other; if you really want shadows on the axes, I'd add just enough color to them for the shader to add a shadow (maybe a dark blue or dark red). – Mike Strobel Oct 24 '14 at 12:28
  • I have idea to use `RGB.A` (alpha channel) value: if alpha is bigger than threshold, shadow is generated, otherwise is not. All colors I am using right now have alpha `1.0`, so all will drop shadow. Gray color can be obtained as black with low alpha. And this color will be ignored by shader! – Sinatr Oct 24 '14 at 13:00
  • The antialiased edges of the series lines don't have 1.0 alpha. Neither do the edges of the label text. – Mike Strobel Oct 24 '14 at 13:32
  • Yes, but it's not a perfect shadow anyway ;) – Sinatr Oct 24 '14 at 13:36
  • By the way, post your results when you're done--I'm curious to see what the end result looks like! – Mike Strobel Oct 24 '14 at 13:45
1
sampler2D input : register(s0);
float4 main(float2 uv : TEXCOORD) : COLOR  {

    float4 Color;
    Color = tex2D( input , uv.xy);
    return Color;
}

This is a basic 'do nothing' shader. the line with the text2D call takes the color that normally would be plotted at the current location. (And in this case simply returns it)

Instead of sampling uv.xy you could add an offset vector to uv.xy and return that color. This would shift the entire image in the direction of the offset vector.

You could combine these two:

  1. sample the uv.xy, if set to a visible color plot that color (this will keep all the lines visible at the right location)
  2. if it is transparent, sample a bit to the top left. if it is set to a color you want to have a shadow: return the shadow color.

Step 2. can be changed into: if it is set to a color you do not want to have a shadow, return a transparent color.

The offset and the colors to test and to use as shadow color could be parameters of the effect.

I strongly suggest to play around with Shazzam it will allow you to test your shader and it will generate the C# code for you.

Note that the uv coordinates are are not in pixels but scaled to 0.0 to 1.0.

Addition

A poor man's blur (anti-aliasing) could be obtained by sampling more pixels around the offset and calculating an average of the colors found that should cause a shadow. this will cause more pixels to receive a shadow.

To calculate the shadow color you could simply darken the existing color by multiplying it with a factor in between 0.0 (black) and 1.0 (original color)

By using the average from the blur you can multiply the shadow color again causing the blur to mix with the original color.

More precise (and expensive) would be to translate the rgb values to hls values and use 'official' darkening formulas to determine the shadow color.

Emond
  • 50,210
  • 11
  • 84
  • 115
  • Thanks for idea with shifting. Shift + blur = shadow. Will try Shazzam. – Sinatr Oct 14 '14 at 07:09
  • Just remember that your intention should not be to shift but to sample at an offset. – Emond Oct 14 '14 at 08:21
  • "fail at everything" is too vague. Could you be more specific? – Emond Oct 15 '14 at 09:11
  • I did offset, but I fail at mixing (?) it with current color to simulate shadow. I don't know what *shadow drop* is. Is it *darkening* of color corresponding to what? Also blurring, there are 1 pass motion blurs shaders everywhere, but I can't find gaussian blur or something. I don't understand how to make shadow. Easiest would be to see how wpf does it. – Sinatr Oct 15 '14 at 09:34
  • @Sinatr - I added to my answer. As far as I know there are no online sources of the drop shadow effect. – Emond Oct 15 '14 at 16:59
  • Thanks for your help, I learned one more thing which I can't do - writing shaders. – Sinatr Oct 17 '14 at 08:48
  • @Sinatr - :) you can learn it. Just start reading up on DirectX Shaders (and OpenGL Shaders if you want) and start small. – Emond Oct 17 '14 at 09:17