Thursday, 26 April 2018

C# - COM - Pass on excellent .NET Image classes to COM clients

In the Windows API image processing is done with GDI and GDI+, the latter now has a 64-bit dimension. Some programmers working on COM clients, like VBA, might like to wrestle with the Windows API declarations and the management of handles but I personally would opt for the .NET approach and I would expose the key methods to COM clients.

Below is some C# code for a class library that is ComVisible(true) and has been 'Registered for COM interop'. For some client code see the VBA listing on this counterpart blog post. Together these programs allow a picture to written to Excel cells like this...


using System;
using System.Drawing; // added assembly reference to System.Drawing
using System.Runtime.InteropServices;
using io = System.IO;

/// <summary>
/// Be a good COM citizen and define the interface separately.
/// In AssemblyInfo.cs we have [assembly: ComVisible(true)].
/// In Project Properties->Build tab under the Output section we have checked the 'Register for COM interop' checkbox.
/// </summary>

namespace ImageToByteArray
{

    public interface IColour
    {
        byte G { get; }
        bool IsNamedColor { get; }
        bool IsEmpty { get; }
        bool IsKnownColor { get; }
        byte A { get; }
        byte B { get; }
        byte R { get; }
    }


    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(IColour))]
    public class Colour : IColour
    {
        Color m_Colour;

        byte IColour.G => m_Colour.G;

        bool IColour.IsNamedColor => m_Colour.IsNamedColor;

        bool IColour.IsEmpty => m_Colour.IsEmpty;

        bool IColour.IsKnownColor => m_Colour.IsKnownColor;

        byte IColour.A => m_Colour.A;

        byte IColour.B => m_Colour.B;

        byte IColour.R => m_Colour.R;

        public Colour(Color col)
        {
            m_Colour = col;
        }
    }

    public interface IBitMap
    {
        int Width { get; }
        int Height { get; }

        Colour GetPixel(int x, int y);
        bool LoadImage(string sFilePath);
    }

    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(IBitMap))]
    public class BitMap : IBitMap
    {
        Bitmap m_bmp;
        bool m_bLoaded;

        int IBitMap.Width
        {
            get
            {
                if (m_bLoaded)
                {
                    return m_bmp.Width;
                } else
                {
                    throw new Exception("#load an image first!");
                }
            }
        }

        int IBitMap.Height
        {
            get
            {
                if (m_bLoaded)
                {
                    return m_bmp.Height;
                }
                else
                {
                    throw new Exception("#load an image first!");
                }
            }
        }

        Colour IBitMap.GetPixel(int x, int y)
        {
            if (m_bLoaded)
            {
                Color col = m_bmp.GetPixel(x, y);
                return new Colour(col);
            }
            else
            {
                throw new Exception("#load an image first!");
            }
        }

        bool IBitMap.LoadImage(string sFilePath)
        {

            if (!io.File.Exists(sFilePath))
            {
                throw new Exception("#File '" + sFilePath + "' does not exist!");
            }
            try
            {
                m_bmp = new Bitmap(sFilePath);
                m_bLoaded = true;
            }
            catch (Exception ex)
            {
                throw new Exception("#Something went wrong whilst opening bitmap file '" + sFilePath + "'", ex);
            }

            return true;
        }
    }
}