The following class is a WPF image control which is directly derived from Image. Therefore it can be used out-of-the-box like the standard WPF Image control. The image will automatically change to a gray scale image when the IsEnabled property of the control is set to false. The AutoGrayableImage can be used in within any other control. It works great with Data Binding.

/// <summary>
/// Image that switches to monochrome when disabled.
/// </summary>
public class AutoGrayableImage : Image
{
  // References to original and grayscale ImageSources
  private ImageSource _sourceColor;
  private ImageSource _sourceGray;
  // Original and grayscale opacity masks
  private Brush _opacityMaskColor;
  private Brush _opacityMaskGray;

  static AutoGrayableImage()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(AutoGrayableImage), new FrameworkPropertyMetadata(typeof(AutoGrayableImage)));
  }

  /// <summary>
  /// Overwritten to handle changes of IsEnabled, Source and OpacityMask properties
  /// </summary>
  protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
  {
    if (e.Property.Name.Equals(nameof(IsEnabled)))
    {
      if (e.NewValue as bool? == false)
      {
        Source = _sourceGray;
        OpacityMask = _opacityMaskGray;
      }
      else if (e.NewValue as bool? == true)
      {
        Source = _sourceColor;
        OpacityMask = _opacityMaskColor;
      }
    }
    else if (e.Property.Name.Equals(nameof(Source)) &&  // only recache Source if it's the new one from outside
             !ReferenceEquals(Source, _sourceColor) &&
             !ReferenceEquals(Source, _sourceGray))
    {
      SetSources();
    }
    else if (e.Property.Name.Equals(nameof(OpacityMask)) && // only recache opacityMask if it's the new one from outside
             !ReferenceEquals(OpacityMask, _opacityMaskColor) &&
             !ReferenceEquals(OpacityMask, _opacityMaskGray))
    {
      _opacityMaskColor = OpacityMask;
    }

    base.OnPropertyChanged(e);
  }

  /// <summary>
  /// Caches original ImageSource, creates and caches grayscale ImageSource and grayscale opacity mask
  /// </summary>
  private void SetSources()
  {
    // If grayscale image cannot be created set grayscale source to original Source first
    _sourceGray = _sourceColor = Source;

    // Create Opacity Mask for grayscale image as FormatConvertedBitmap does not keep transparency info
    _opacityMaskGray = new ImageBrush(_sourceColor);
    _opacityMaskGray.Opacity = 0.6;

    try
    {
      // Get the string Uri for the original image source first
      string stringUri = TypeDescriptor.GetConverter(Source).ConvertTo(Source, typeof(string)) as string;

      // Try to resolve it as an absolute Uri 
      if (!Uri.TryCreate(stringUri, UriKind.Absolute, out Uri uri))
      {
        // Uri is relative => requested image is in the same assembly as this object
        stringUri = "pack://application:,,,/" + stringUri.TrimStart(new char[2] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
        uri = new Uri(stringUri);
      }

      // create and cache grayscale ImageSource
      _sourceGray = new FormatConvertedBitmap(new BitmapImage(uri), PixelFormats.Gray8, null, 0);
    }
    catch (Exception e)
    {
      Debug.Fail("The Image used cannot be grayed out.", "Use BitmapImage or URI as a Source in order to allow gray scaling. Make sure the absolute Uri is used as relative Uri may sometimes resolve incorrectly.\n\nException: " + e.Message);
    }
  }
}

The code will use the Source property set in code, in XAML or via binding and create a grayscale representation of that image. Whenever the source changes, the internally stored images are refreshed.

The Source must either be set as an absolute URI, or as a relative URI. When setting the relative URI format, the related image file must be located in the same project as the AutoGrayableImage.

If something went wrong during conversion of the image, the original image with increased opacity will be used.

This is how the class will be used in XAML:

<Button Command="{Binding AddSortingDefinitionCommand}">
  <tools:AutoGrayableImage Source="pack://application:,,,/CMS.ResX;component/Images/Add.png" Width="16" Height="16" />
</Button>