5

Using XAML preferably,

I need to dynamically apply a gray-scale filter on a colored image from bottom to top. That's to say, i would like to be able to change the offset value of the gray-scale filter applied to my image pro grammatically.

For example, I would like to programmatically set 50% of the image in color (from the bottom of the image to its middle), and the other 50% of the image in gray-scale (e.g. from the middle of the image to its top).

I have read a lot of different answers, tried a lot of different things, and thought about a lot of different ways to do this.

  • I could have two images, one on top of the other. One would be in gray-scale and the other would be in color. I would then programmatically change the size of the gray-scale image so that the image in color below it would partially show and create a sort of half-half view to the user.

However, this solution poses an issue that I do not seem to be able to resolve. When changing the height value of the gray-scale image, the image automatically rescale itself due to the 'Stretch' property which is set to Uniform (and that I do not wish to change to None).

  • Another way to do this would be to programmatically change the color pixels of the image. I have had some success with this in the past, however, this is too slow for what I am trying to do here (ideally, I would be changing the colors of the image from gray-scale to the original color of the image selected by the user every 50 milliseconds until a certain height is reached).

  • A third method could be to apply an Opacity Mask on the image and use LinearGradientBrush to change the offset value to the desired position. This is the code I am currently using, it works but simply apply a gray color to the image without changing the original colors of the image to gray-scale (resulting in a sort of a washed out colored image):

XAML:

<Image x:Name="myImage" HorizontalAlignment="Left" VerticalAlignment="Top" Source="C:\Users\Clement\Desktop\test.png" Canvas.Left="159" Canvas.Top="81" Width="500" Height="375" >
            <Image.OpacityMask>
                <LinearGradientBrush EndPoint="0.5,0" MappingMode="RelativeToBoundingBox" StartPoint="0.5,1">
                    <GradientStop x:Name="myImageLinearGradientBrushStop" Color="Black"/>
                    <GradientStop Color="Transparent" Offset="1"/>
                </LinearGradientBrush>
            </Image.OpacityMask>
</Image>

Code-behind (in a timerEventProcessor that repeats itself every 50ms):

myImageLinearGradientBrushStop.Offset = percent * 0.01;

As mentioned previously, I have browsed a lot of different websites, read a lot of similar questions and tried a lot of different answers and still cant satisfactorily solve the problem.

TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • Sounds like would want to make your own affect, though i dont know if you can bind to them, or derive from image and add a gradient at the bitmap level potentially – TheGeneral Feb 18 '18 at 05:03
  • Hi Michael, thank you for taking the time to reply to my question. Making my own grayscale effect bound to my image control in XAML is what I am trying to achieve indeed. However, I am not sure where to start unfortunately. If you have any ideas please do let me know. –  Feb 18 '18 at 05:23

2 Answers2

5

The XAML below uses a Grid with two rows to display two Images on top of each other. The Width and Heigh of the top Image are bound to the size of the bottom Image, which spans both Grid rows. An inner Grid is used to clip the top Image.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <Image x:Name="image" Grid.RowSpan="2" Source="..."/>

    <Grid>
        <Image Width="{Binding ActualWidth, ElementName=image}"
               Height="{Binding ActualHeight, ElementName=image}"
               HorizontalAlignment="Center" VerticalAlignment="Top">
            <Image.Source>
                <FormatConvertedBitmap Source="{Binding Source, ElementName=image}"
                                       DestinationFormat="Gray8"/>
            </Image.Source>
        </Image>
    </Grid>
</Grid>
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Hi Clemens, Thank you very much for looking into this issue for me. Unfortunately, your snippet of code did not really resolve my issue. The second image (gray-scale) does not seem to be generated properly and seems invisible. However, I have been thinking about a 4th way to get this done: - Generate 101 images (0 to 100% of the height of the original colored image). This may take up to a minute I'd say, depending on the algorithm used to create the grayscale images. - Store these images inside a List. - Switch quickly between the correct images according to the % of height required. –  Feb 19 '18 at 06:29
  • 1
    "does not seem to be generated properly and seems invisible" isn't a very clear problem description. The FormatConvertedBitmap works fine for me. – Clemens Feb 19 '18 at 08:03
  • Hi Clemens, My apologies if I did not succeed in making myself clear. What I meant by " The second image (gray-scale) does not seem to be generated properly and seems invisible" is that, using your code, I am able to display the image I wish to show by replacing "Source="..."" with the correct path of the image, however, the second image which should be the Gray8 one, is not displayed on the form (only an empty rectangle the same size of the original image is represented). However, now that I think of it, it might be due to the image I am using (a png with transparent background). –  Feb 19 '18 at 08:18
  • If you use the exact XAML above, it will show the upper half of the grayscale image (i.e. first row) on top if the color image. By changing the relative row height, you can change the visible fraction. – Clemens Feb 19 '18 at 08:21
  • Hi Clemens, I got it to work! However, I am still struggling with two things: changing the height of the grayscale image without shrinking the image (if I change Height="{Binding ActualHeight, ElementName=image}" to Height="200" for example, it rescales the image to a smaller one, which defeats the purpose of having two images on top of each other, but I think that I must be doing it wrong). Second, and more difficult here I believe, the Gray8 image loses its transparency, which is a problem in my scenario. In any case, thank you so much for all your help so far! –  Feb 19 '18 at 08:33
  • If you remove the Height Binding the images do of course not have the same height. As said, just set the Height of the first Grid row, either relative or absolute. – Clemens Feb 19 '18 at 08:38
  • Thank you very much Clemens, I have managed to do as you advised. It worked and this is the end result: https://i.imgur.com/Cf4xDOv.png As you can see, it works very well, my only remaining problem being that the transparency of the original image is lost when converted to Gray8. Is there anything that can be done to add the transparency back to the grayscaled image? Thanks again for all your help nonetheless, I really appreciate it. –  Feb 19 '18 at 09:09
  • I did it! I set the converted image’s opacity mask to that of the source image to get the transparencies back and it worked :) –  Feb 19 '18 at 09:23
  • How would I do this in UWP as there is no FormatConvertedBitmap? – Gail Foad Nov 22 '19 at 15:41
2

Thanks to Clemens' help, I was able to get a solution to my problem!

This is the final XAML code for those whom might be interested:

<Grid Width="500" Height="375">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <Image x:Name="image" Grid.RowSpan="2" Source="C:\Users\Clement\Desktop\test_image.png" />

            <Grid Margin="0,0,0,166" Grid.RowSpan="2">
                <Image Width="{Binding ActualWidth, ElementName=image}"
           Height="{Binding ActualHeight, ElementName=image}"
           HorizontalAlignment="Center" VerticalAlignment="Top">
                    <Image.Source>
                        <FormatConvertedBitmap Source="{Binding Source, ElementName=image}"
                                   DestinationFormat="Gray8"/>
                    </Image.Source>
                    <Image.OpacityMask>
                        <ImageBrush ImageSource="C:\Users\Clement\Desktop\test_image.png"/>
                    </Image.OpacityMask>
                </Image>
            </Grid>
        </Grid>

Here I simply added the OpacityMask to the Gray8 image to bring back its transparency.

Happy coding!