Bluecakes
Bluecakes

Reputation: 2159

Convert Specific BitmapImage Colour to Transparent

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?

Upvotes: 1

Views: 2935

Answers (1)

Clemens
Clemens

Reputation: 128157

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);
}

Upvotes: 3

Related Questions