简体   繁体   中英

Wpf ArcSegment with 3 color gradient

I try to paint an ArcSegment in my WPF control that is used as an Cyclic Progress Bar and has a three color gradient. It should start with red for 0 % has yellow for 50% and green for 100%. So for 75% it should look like:

喜欢是应该看起来像

I try this with a LinearGradientBrush

<Path StrokeThickness="25">
    <Path.Data>
        <PathGeometry>
            <PathFigure StartPoint="50, 12.5">

                <ArcSegment RotationAngle="0" SweepDirection="Clockwise"
                            IsLargeArc="true"
                            Point="12.5, 75" 
                            Size="62.5, 62.5">
                </ArcSegment>
            </PathFigure>
        </PathGeometry>
    </Path.Data>
    <Path.Stroke>
        <LinearGradientBrush StartPoint="0, 0.5" EndPoint="1, 0.5">
            <GradientStop Offset="0" Color="Green" />
            <GradientStop Offset="0.5" Color="Red" />
            <GradientStop Offset="1.0" Color="Yellow"/>
        </LinearGradientBrush>
    </Path.Stroke>
</Path>

Or in other direction:

<Path.Stroke>
    <LinearGradientBrush StartPoint="0.5, 0" EndPoint="0.5, 1">
        <GradientStop Offset="0" Color="Green" />
        <GradientStop Offset="0.5" Color="Red" />
        <GradientStop Offset="1.0" Color="Yellow"/>
    </LinearGradientBrush>
</Path.Stroke>

But the problem is that the gradient is used for the complete surface and so i have red painted two instead of one time and not correct on start of the cycle:

看纯色渐变

I try to use a Picture Brush with a picture having the gradient as I want it.

<Path.Stroke>
    <ImageBrush ImageSource="../brush.png"  />
</Path.Stroke>

This works fine for values > 75% but for lower values also the green part is drawn what not should be.

在此处输入图片说明

Any idear what I can do to make this work?

I get it working with an ImageBrush. The ting is to use only the part of the image that is needed to be painted by cropping it.

To do so I used a Converter that returns a ImageBrush vor the current percentage value.

public class ValueToImageBrush : IValueConverter
{
    private const string _filePath = "pack://application:,,,/XXX;component/brush.png";
    private BitmapImage _baseImage;

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var doubleValue = value as double?;
        if (doubleValue == null) return new SolidColorBrush(Colors.LightSlateGray);

        var baseImg = LoadImage();           
        var img = CutImage(baseImg, doubleValue.Value);

        return InitImageBrush(img);
    }       


    private BitmapImage LoadImage()
    {
        if (_baseImage == null)
        {
            _baseImage = new BitmapImage();
            _baseImage.BeginInit();
            _baseImage.UriSource = new Uri(_filePath);
            _baseImage.EndInit();
        }
        return _baseImage;
    }

    private static ImageSource CutImage(BitmapImage baseImg, double doubleValue)
    {
        var img = baseImg as ImageSource;
        if (doubleValue < 50)
        {
            var usedHight = Math.Max(25.0, baseImg.PixelHeight * (doubleValue * 2 / 100));
            img = new CroppedBitmap(baseImg,
                new Int32Rect(baseImg.PixelWidth / 2, 0, baseImg.PixelWidth / 2, (int)usedHight));
        }
        else if (doubleValue < 75)
        {
            var usedWidth = baseImg.PixelWidth * (doubleValue / 100);
            img = new CroppedBitmap(baseImg, new Int32Rect(200 - (int)usedWidth, 0, (int)usedWidth, baseImg.PixelHeight));
        }

        return img;
    }
    private static object InitImageBrush(ImageSource img)
    {
        ImageBrush imgBrush = new ImageBrush();
        imgBrush.ImageSource = img;
        imgBrush.Stretch = Stretch.None;
        return imgBrush;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

I'm not sure if this is the smartest solution but it performs fast and was not really complex to be implemented. May be someone else has a better solution.

I don't think this sort of display is possible using a linear gradient brush.

My solution would be to divide the control up into a number of small wedge-shaped elements, each of which has a single solid colour. The colour for each element is calculated based on its angle and the start / end angle of the control (if you're going to make the effort to create such a control, you might as well make it a little more generic) and the required colour stops.

You can't just use the Red / Green / Blue colour space and interpolate the colour for an individual element though - you'd have to use something like Hue / Saturation / Luminosity instead.

I realized, it's a little tricky, but deviding the ArcSegment in four pieces at most would help, if the starting point is right at the top, bottom, left or right. I can explain more tomorrow, but the tricky part was to find the gradient steps prjocted on a diagonal line from starting point to end point of each ArcSegment. If the starting point was anywhere, the projection line would be harder to find as it may have non rectangular angles.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM