制作一个可着色的图像 - 改变图像背后的代码源会混淆事件

时间:2014-06-19 15:36:23

标签: c# wpf

我试图制作ColorableImage自定义控件,但我不确定我尝试做的是否可行。这个课本身很简单:

public class ColorableImage : Image
{
    private bool ChangedByColor { get; set; }

    public static readonly DependencyProperty ColorProperty;
    public static readonly DependencyProperty UncoloredSourceProperty;

    static ColorableImage()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorableImage), new FrameworkPropertyMetadata(typeof(ColorableImage)));
        SourceProperty.OverrideMetadata(typeof(ColorableImage), new FrameworkPropertyMetadata(new PropertyChangedCallback(SourceChanged)));

        ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(ColorableImage), new FrameworkPropertyMetadata(new PropertyChangedCallback(ColorChanged)));
        UncoloredSourceProperty = DependencyProperty.Register("UncoloredSource", typeof(ImageSource), typeof(ColorableImage));
    }


    public Color Color
    {
        get { return (Color)GetValue(ColorProperty); }
        set { SetValue(ColorProperty, value); }
    }

    public ImageSource UncoloredSource
    {
        get { return (ImageSource)GetValue(UncoloredSourceProperty); }
        protected set { SetValue(UncoloredSourceProperty, value); }
    }

    private static void SourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ColorableImage cimg = (ColorableImage)d;
        if (!cimg.ChangedByColor)
        {
            cimg.UncoloredSource = cimg.Source;
            if (cimg.Color != null && !cimg.Color.ToString().Equals("#00000000"))
            {
                ColorChanged(d, e);
            }
        }
    }

    private static void ColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ColorableImage cimg = (ColorableImage)d;

        // This will call the SourceChanged which we don't want (since it will change UncoloredImage)
        cimg.ChangedByColor = true;
        cimg.Source = ColorSource(cimg.Source, cimg.Color);
        cimg.ChangedByColor = false;
    }

    private static ImageSource ColorSource(ImageSource source, Color color)
    {
        BitmapSource bitmapSource = (BitmapSource)source;

        if (bitmapSource.Format.BitsPerPixel != 32 && bitmapSource.Format != System.Windows.Media.PixelFormats.Bgra32)
        {
            bitmapSource = new FormatConvertedBitmap(bitmapSource, System.Windows.Media.PixelFormats.Bgra32, null, 0);
        }

        WriteableBitmap wbmp = new WriteableBitmap(bitmapSource);

        // Get necessary image info to copy into the pixel array
        var bytesPerPixel = (wbmp.Format.BitsPerPixel + 7) / 8;
        var stride = wbmp.PixelWidth * bytesPerPixel;
        var arraySize = stride * wbmp.PixelHeight;

        // Copy the image into the pixel array
        var pixelArray = new byte[arraySize];
        wbmp.CopyPixels(pixelArray, stride, 0);

        // Convert the System.Windows.Media.Color to a System.Drawing.Color
        System.Drawing.Color newColor = System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B);

        // Put the new pixels into the pixel array
        for (int i = 0; i < pixelArray.Length; i += bytesPerPixel)
        {
            // Only color those pixels that have no transparency
            // This is a very simple alogirthm; you can pretty much only have one color in the image, with the rest of it being completely transparent; it is primarily meant for small, simple icons
            if (pixelArray[i + 3] == 255)
            {
                pixelArray[i] = newColor.B;
                pixelArray[i + 1] = newColor.G;
                pixelArray[i + 2] = newColor.R;
                pixelArray[i + 3] = newColor.A;
            }
        }

        // Remake the image
        wbmp.WritePixels(new Int32Rect(0, 0, wbmp.PixelWidth, wbmp.PixelHeight), pixelArray, stride, 0);

        return wbmp;
    }
}

着色算法很简单,其目的是能够用给定的Color替换每个不透明的像素,这是控件的依赖属性。控件上还有一个名为UncoloredSource的只读属性,它会返回原始图像源,因为实际的Source因着色而发生了变化。

但是,我认为我正在做的是打破Image控件对其Source属性的某种内部绑定。我正在使用ListViewItems对此进行测试,如下所示:

<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
    <local:ColorableImage x:Name="img" Source="plus-black.png" Color="Red"></local:ColorableImage>
    <ControlTemplate.Triggers>
        <Trigger Property="IsSelected" Value="True">
            <Setter TargetName="img" Property="Color" Value="Blue" />
            <Setter TargetName="img" Property="Source" Value="minus-black.png" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

...

<ListView>
    <ListViewItem Template="{DynamicResource ListBoxItemControlTemplate1}" />
    <ListViewItem Template="{DynamicResource ListBoxItemControlTemplate1}" />
    <ListViewItem Template="{DynamicResource ListBoxItemControlTemplate1}" />
</ListView>

图片会成功更改颜色,但我发现SourceChanged事件在Color更改后永远不会再次触发。我唯一的猜测是因为我在Source中重新分配代码隐藏中的ColorChanged属性,这会混淆触发器...但我很惊讶这会弄乱它。

另外,如果我稍微切换一下触发器,这就证实了我的想法。如果我没有设置初始颜色,然后在触发器中更改Source第一个和Color秒,那么源和颜色会发生变化,但是当我选择第二个列表项时,前一个列表项会完全消失,当我选择第三个,第二个完全消失等等,我认为这意味着触发器无法再访问旧源以恢复它(不确定原因)。

0 个答案:

没有答案