microsoft / Win2D

Win2D is an easy-to-use Windows Runtime API for immediate mode 2D graphics rendering with GPU acceleration. It is available to C#, C++ and VB developers writing apps for the Windows Universal Platform (UWP). It utilizes the power of Direct2D, and integrates seamlessly with XAML and CoreWindow.
http://microsoft.github.io/Win2D
Other
1.82k stars 287 forks source link

Is it possible to reduce the amount of colors in a Gradient? #517

Closed juanpaexpedite closed 7 years ago

juanpaexpedite commented 7 years ago

I've successfully parsed the linked gradients from Inkscape to Win2D. This question is a bit difficult to explain, so suppose we have the following green gradient:

win2dhigh

I would like to achieve a gradient with for instance 10 stop colors values to have an effect like the following (do not mind the resolution):

image

At the moment the only solution I think it is possible is do by hand recreating the gradient with 10 stops and adjusting the stops by hand, but before doing that, It is possible a better way?

I know it is not relevant for the library but in case it is possible I have not realize how to do that. Thank you!

clandrew commented 7 years ago

I think the strategy might not be to adjust anything about the gradient itself, but to use a DiscreteTransferEffect. The discrete transfer effect will allow you to "bucket"-ize the intensities of colors into a number of "buckets" of your choosing.

There's a screenshot of this effect on the Direct2D help page.

juanpaexpedite commented 7 years ago

Really interesting, I've just tested that with the following result:

image

CanvasCommandList cl = new CanvasCommandList(session.Device);
using (CanvasDrawingSession clds = cl.CreateDrawingSession())
{
    clds.FillGeometry(geometry2, pen, opacityBrush);
}

DiscreteTransferEffect effect = new DiscreteTransferEffect();
float[] table = { 0.0f, 0.5f, 1.0f };
effect.GreenTable = table;
effect.RedTable = table;
effect.BlueTable = table;
effect.Source = cl;
session.DrawImage(effect);

May I have to use different values in the table?

Anyway thank you I'll investigate examples about that...

shawnhar commented 7 years ago

Afraid I am not following what you are trying to do. What is the difference between your first and second images, other than the obvious low resolution of the second?

juanpaexpedite commented 7 years ago

I think it is clear in the third image, using the DiscreteTransferEffect appear 4 colors for the gradient. That is exactly what I want but with the right values according to the gradient colors ramp.

How should I adapt the values to follow the color ramp?

Thank you.

clandrew commented 7 years ago

Yeah, thinking about it- a drawback of using DiscreteTransferEffect is that while you'll get the banding, it won't honor your exact source colors.

What about if you add more stops? For example, if you wanted just red-green-blue,

        CanvasGradientStop[] gradientStops = new CanvasGradientStop[6];
        gradientStops[0].Color = Colors.Red;
        gradientStops[0].Position = 0;
        gradientStops[1].Color = Colors.Red;
        gradientStops[1].Position = 0.33f;

        gradientStops[2].Color = Colors.Green;
        gradientStops[2].Position = 0.33f;
        gradientStops[3].Color = Colors.Green;
        gradientStops[3].Position = 0.66f;

        gradientStops[4].Color = Colors.Blue;
        gradientStops[4].Position = 0.66f;
        gradientStops[5].Color = Colors.Blue;
        gradientStops[5].Position = 1.0f;
juanpaexpedite commented 7 years ago

Yes, It is what I thought in first instance. Create a Method that gives me the color for the band position and then create an stepped gradient like you shown.

Let's see how I figure out the band extraction. Anyway thank you now I will see all the effects to see what can I add to the tool app I am developing!

clandrew commented 7 years ago

Definitely, you could try something like


        CanvasGradientStop[] CreateBandedStops(Color color1, Color color2, int numberOfSteps)
        {
            CanvasGradientStop[] gradientStops = new CanvasGradientStop[numberOfSteps * 2];

            float increment = 1.0f / (float)(numberOfSteps);

            float colorIncrementCount = numberOfSteps - 1;
            float redIncrement = ((float)color2.R - (float)color1.R) / colorIncrementCount;
            float greenIncrement = ((float)color2.G - (float)color1.G) / colorIncrementCount;
            float blueIncrement = ((float)color2.B - (float)color1.B) / colorIncrementCount;

            float red = color1.R;
            float green = color1.G;
            float blue = color1.B;
            float position = 0;

            int index = 0;
            while (index < gradientStops.Length)
            {
                Color currentColor = Color.FromArgb(255, (byte)red, (byte)green, (byte)blue);

                gradientStops[index].Color = currentColor;
                gradientStops[index].Position = position;
                index++;
                position += increment;

                gradientStops[index].Color = currentColor;
                gradientStops[index].Position = position;
                index++;

                red += redIncrement;
                green += greenIncrement;
                blue += blueIncrement;
            }

            return gradientStops;
        }

If this is helpful, then I'll close the issue for now, but definitely feel free to re-open if there's anything else you need.

juanpaexpedite commented 7 years ago

Yes I understand the idea, (it was a bit late around here).This morning I remembered my first shader and I start to look a solution using a shader because the colors might be multiple, the stops are not really equally separated and finally with easy code I found the following solution:

image

 D2D_PS_ENTRY(D2D_ENTRY)
 {
float4 color = D2DGetInput(0);
int levels = floor(Levels);
color.rgb *= levels;
color.rgb = floor(color.rgb);
color.rgb /= levels;
color.rgb *= color.a;
return color;
  }

Pretty cool. I tested it with 32 levels. It might be possible to make it more perfect (I appreciate any advices) but at the moment is the most I get about bands.

Now I have found a way to make it work with dithering like the following:

image

I am not an expert neither in shaders nor dithering so that's the most accurate shader I was able to create. Let's see if in the future I take it with more patience.

Thank you.

clandrew commented 7 years ago

Great, it looks like you were able to get the kind of gradient banding that you want. Gradient banding isn't exposed directly in D2D and therefore it is not exposed in Win2D, but the foundation blocks are there.

Ah I love the dithering, it reminds me of old DOS games. What makes a good dithering technique is highly dependent on the content you're trying to apply gradients to- there are choices in how many bands you want, the palletization method, type of dither pattern, etc. It looks like you found something that works well for your purposes.