Convert Specific BitmapImage Colour to Transparent

1

I'm currently recreating my Image Explorer application, formerly written in Windows Forms to the Windows Presentation Framework.

My WinForms application was using the WindowsThumbnailProvider from @DanielPeñalba (See this link for the original version of the code)

WinForms Version - Successfully converting 0 alpha, 0 red, 0 green and 0 blue to Transparent WinForms Version

WPF Version - Almost working enter image description here

WPF Code - Slightly modified version of the original WindowsThumbnailProvider to support System.Windows.Media.Imaging.BitmapImage instead of System.Drawing.Bitmap

MainWindow.xaml - For all the testing

<Window x:Class="WpfFileFolderThumbnails.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfFileFolderThumbnails"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="600">
    <Grid>
        <Image x:Name="ThumbnailImage1" HorizontalAlignment="Left" Height="256" Margin="10,20,0,0" VerticalAlignment="Top" Width="256"/>
        <Image x:Name="ThumbnailImage2" HorizontalAlignment="Left" Height="256" Margin="326,20,0,0" VerticalAlignment="Top" Width="256"/>
    </Grid>
</Window>

MainWindow.xaml.cs - Test code to call the GetThumbnail and CreateAlphaBitmapImage methods

using System.Windows;

namespace WpfFileFolderThumbnails
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();

            var thumbnail = WindowsThumbnailProviderWpf.GetThumbnail(@"D:\Pictures\Art\Anime", 256, 256,
                ThumbnailOptions.ThumbnailOnly);

            var alphaThumbnail = WindowsThumbnailProviderWpf.CreateAlphaBitmapImage(thumbnail);

            this.ThumbnailImage1.Source = thumbnail;
            this.ThumbnailImage2.Source = alphaThumbnail;
        }
    }
}

WindowsThumbnailProviderWpf.cs - Class to get Folder Thumbnail and make it Transparent

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Path = System.IO.Path;

namespace WpfFileFolderThumbnails
{
    [Flags]
    public enum ThumbnailOptions
    {
        None = 0x00,
        BiggerSizeOk = 0x01,
        InMemoryOnly = 0x02,
        IconOnly = 0x04,
        ThumbnailOnly = 0x08,
        InCacheOnly = 0x10,
    }

    public static class WindowsThumbnailProviderWpf
    {
        private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";

        [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        internal static extern int SHCreateItemFromParsingName(
            [MarshalAs(UnmanagedType.LPWStr)] string path,
            // The following parameter is not used - binding context.
            IntPtr pbc,
            ref Guid riid,
            [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);

        [DllImport("gdi32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool DeleteObject(IntPtr hObject);

        [ComImport]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
        internal interface IShellItem
        {
            void BindToHandler(IntPtr pbc,
                [MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
                [MarshalAs(UnmanagedType.LPStruct)]Guid riid,
                out IntPtr ppv);

            void GetParent(out IShellItem ppsi);
            void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);
            void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
            void Compare(IShellItem psi, uint hint, out int piOrder);
        };

        internal enum SIGDN : uint
        {
            NORMALDISPLAY = 0,
            PARENTRELATIVEPARSING = 0x80018001,
            PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
            DESKTOPABSOLUTEPARSING = 0x80028000,
            PARENTRELATIVEEDITING = 0x80031001,
            DESKTOPABSOLUTEEDITING = 0x8004c000,
            FILESYSPATH = 0x80058000,
            URL = 0x80068000
        }

        internal enum HResult
        {
            Ok = 0x0000,
            False = 0x0001,
            InvalidArguments = unchecked((int)0x80070057),
            OutOfMemory = unchecked((int)0x8007000E),
            NoInterface = unchecked((int)0x80004002),
            Fail = unchecked((int)0x80004005),
            ElementNotFound = unchecked((int)0x80070490),
            TypeElementNotFound = unchecked((int)0x8002802B),
            NoObject = unchecked((int)0x800401E5),
            Win32ErrorCanceled = 1223,
            Canceled = unchecked((int)0x800704C7),
            ResourceInUse = unchecked((int)0x800700AA),
            AccessDenied = unchecked((int)0x80030005)
        }

        [ComImport()]
        [Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface IShellItemImageFactory
        {
            [PreserveSig]
            HResult GetImage(
            [In, MarshalAs(UnmanagedType.Struct)] NativeSize size,
            [In] ThumbnailOptions flags,
            [Out] out IntPtr phbm);
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct NativeSize
        {
            private int width;
            private int height;

            public int Width { set { this.width = value; } }
            public int Height { set { this.height = value; } }
        };

        [StructLayout(LayoutKind.Sequential)]
        public struct RGBQUAD
        {
            public byte rgbBlue;
            public byte rgbGreen;
            public byte rgbRed;
            public byte rgbReserved;
        }

        public static BitmapImage GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
        {
            IntPtr hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);

            try
            {
                // return a System.Drawing.Bitmap from the hBitmap
                return GetBitmapImageFromHBitmap(hBitmap);
            }
            finally
            {
                // delete HBitmap to avoid memory leaks
                DeleteObject(hBitmap);
            }
        }

        public static BitmapImage GetBitmapImageFromHBitmap(IntPtr nativeHBitmap)
        {
            var bmpSource =  Imaging.CreateBitmapSourceFromHBitmap(nativeHBitmap,
                                                                    IntPtr.Zero,
                                                                    Int32Rect.Empty,
                                                                    BitmapSizeOptions.FromEmptyOptions());

            var bmpImage = BitmapSourceToBitmapImage(bmpSource);

            return bmpImage;
        }

        // Conversion code
        public static BitmapImage BitmapSourceToBitmapImage(BitmapSource bitmapSource)
        {
            JpegBitmapEncoder encoder = new JpegBitmapEncoder();
            MemoryStream memorystream = new MemoryStream();
            BitmapImage tmpImage = new BitmapImage();
            encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
            encoder.Save(memorystream);

            tmpImage.BeginInit();
            tmpImage.StreamSource = new MemoryStream(memorystream.ToArray());
            tmpImage.EndInit();

            memorystream.Close();
            return tmpImage;
        }

        public static BitmapImage CreateAlphaBitmapImage(BitmapImage sourceBitmapImage)
        {
            var bmp = sourceBitmapImage.Clone();
            var pixels = new int[(int)bmp.Width * (int)bmp.Height];
            var stride = (bmp.PixelWidth * bmp.Format.BitsPerPixel + 7) / 8;

            bmp.CopyPixels(pixels, stride, 0);
            var oldColor = pixels[0];
            var red = 255;
            var green = 255;
            var blue = 255;
            var alpha = 0;
            var color = (alpha << 24) + (red << 16) + (green << 8) + blue;

            for (var i = 0; i < (int)bmp.Width * (int)bmp.Height; i++)
            {
                if (pixels[i] == oldColor)
                {
                    pixels[i] = color;
                }
            }

            //remake the bitmap source with these pixels
            var source = BitmapSource.Create(bmp.PixelWidth, bmp.PixelHeight, bmp.DpiX, bmp.DpiY, PixelFormats.Bgra32, bmp.Palette, pixels, stride);

            //return sourceBitmapImage;
            return BitmapSourceToBitmapImage(source);
        }

        private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
        {
            IShellItem nativeShellItem;
            Guid shellItem2Guid = new Guid(IShellItem2Guid);
            int retCode = SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem);

            if (retCode != 0)
                throw Marshal.GetExceptionForHR(retCode);

            NativeSize nativeSize = new NativeSize();
            nativeSize.Width = width;
            nativeSize.Height = height;

            IntPtr hBitmap;
            HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out hBitmap);

            Marshal.ReleaseComObject(nativeShellItem);

            if (hr == HResult.Ok) return hBitmap;

            throw Marshal.GetExceptionForHR((int)hr);
        }
    }
}

While i understand i could just simply reference System.Drawing and use the already working solution, i'd like to know if it's possible to do the same thing in WPF.

Question - Is there a simple way to loop through each pixel of a BitmapImage (similar to a Bitmap), change a specific pixel combination and create a copy of the BitmapImage with transparency?

c#
wpf
image
winforms
xaml
asked on Stack Overflow Aug 28, 2016 by Bluecakes

1 Answer

1

You may call CopyPixels on a BitmapSource to get the raw pixel buffer, then modify the buffer as you like, and create a new BitmapSource from the modified buffer.

The method below shows how this could work for a BitmapSource with a 32-bit BGRA format.

private static BitmapSource CreateTransparency(BitmapSource source)
{
    if (source.Format != PixelFormats.Bgra32)
    {
        return source;
    }

    var bytesPerPixel = (source.Format.BitsPerPixel + 7) / 8;
    var stride = bytesPerPixel * source.PixelWidth;
    var buffer = new byte[stride * source.PixelHeight];

    source.CopyPixels(buffer, stride, 0);

    for (int y = 0; y < source.PixelHeight; y++)
    {
        for (int x = 0; x < source.PixelWidth; x++)
        {
            var i = stride * y + bytesPerPixel * x;
            var b = buffer[i];
            var g = buffer[i + 1];
            var r = buffer[i + 2];
            var a = buffer[i + 3];

            if (...)
            {
                buffer[i + 3] = 0d; // set transparent 
            }
        }
    }

    return BitmapSource.Create(
        source.PixelWidth, source.PixelHeight,
        source.DpiX, source.DpiY,
        source.Format, null, buffer, stride);
}
answered on Stack Overflow Aug 28, 2016 by Clemens • edited Aug 28, 2016 by Clemens

User contributions licensed under CC BY-SA 3.0