Wednesday, December 16, 2020

Obtaining Block's Image

 Showing a block's image is a very often-needed task in AutoCAD programming. AutoCAD drawing file stores a bitmap image in it as thumbnail image of the drawing, if the block drawing is saved properly. There are different way to retrieve the thumbnail image from drawing:

1. In the VBA era, there was a very populate COM control available DwgThumbnail.ocx, which can be placed in VBA's UserForm, or stand-alone app's UI. However, when most AutoCAD being used have moved to 64-bit VBA, this 32-bit COM control is no longer useful.

2. With .NET API, one can use Database.ThumbnailBitmap property to retrieve the block's image from block drawing file by opening it as side database.

However, the 2 approached mentioned above only work with block drawing files. What about getting the image of blocks that are in current opening drawing and show them? Well, there is also an .NET API approach to to it:

Autodesk.AutoCAD.Windows.Data.CMLContentSearchPreviews.GetBlockTRThumbnail(BlockTableRecord)

Because the APIs in namespace Autodesk.AutoCAD.Windows.Data is meant for AutoCAD's UI support, the block image generated with this method is in quite low resolution/small size. So, it is often a not satisfied solution to our custom CAD application needs.

Well, there is actually another much better API method right there available to get better images from blocks in drawing, whether the drawing is opened in AutoCAD, or not (i.e. we can open it in AutoCAD as side database). This method is

Autodesk.AutoCAD.Internal.Utils.GetBlockImage()

The good thing with this method is that one can pass image's width and height to let the method generate an image in desired size while keeping the image in relatively good quality.

With this method, I have written a demo app that retrieve images of all blocks of an opened drawing in AutoCAD and displayed them in a PaletteSet. This is quite similar one can see in "Design Center" PaletteSet window where all blocks' image are shown. The demo app also allows to retrieve block images from a not opened drawing (i.e. the drawing is opened as side database when images are retrieved). See the video clip showing how it works:

Form the video one may notice that it takes quite long time to load all block images from a file. That is because for each block the code retrieves 2 images, one is in smaller size (100 x100) while the other is in larger size (300 x 300), because I'd like to show an image in better quality and in larger size when user clicks each small image on the PaletteSet.

As one can imagine, this PaletteSet can be easily modified to simulate AutoCAD's new block inserting palette.

I am not going to post the code of the demo app itself, because the basic thing that makes it work is Internal.Utils.GetBlockImage()

Here are some code that wraps up that method to generate block image as System.IntPtr, System.Drawing.Image, or System.Windows.Media.ImageSource for different use (in Win Form application, or in WPF application):

using System;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Internal;
 
namespace UseInternalNamespace
{
    public class InternalNamespaceTool
    {
        public static System.IntPtr GetBlockImagePointer(
            string blkName, Database db, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            var blockId = GetBlockTableRecordId(blkName, db);
            if (!blockId.IsNull)
            {
                var imgPtr = Utils.GetBlockImage(blockId, imgWidth, imgHeight, backColor);
                return imgPtr;
            }
            else
            {
                throw new ArgumentException(
                    $"Cannot find block definition in current database: \"{blkName}\".");
            }
        }
 
        public static System.IntPtr GetBlockImagePointer(
            ObjectId blockDefinitionId, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            var imgPtr = Utils.GetBlockImage(blockDefinitionId, imgWidth, imgHeight, backColor);
            return imgPtr;
        }
 
        public static System.Drawing.Image GetBlockImage(
            string blkName, Database db, int imgWidth, int imgHeight, 
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            return System.Drawing.Image.FromHbitmap(
                GetBlockImagePointer(blkName, db, imgWidth, imgHeight, backColor));
 
        }
 
        public static System.Drawing.Image GetBlockImage(
            ObjectId blockDefinitionId, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            return System.Drawing.Image.FromHbitmap(
                GetBlockImagePointer(blockDefinitionId, imgWidth, imgHeight, backColor));
        }
 
        public static System.Windows.Media.ImageSource GetImageSource(
            string blkName, Database db, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            var imgPtr = GetBlockImagePointer(blkName, db, imgWidth, imgHeight, backColor);
            return ConvertBitmapToImageSource(imgPtr);
        }
 
        public static System.Windows.Media.ImageSource GetImageSource(
            ObjectId blockDefinitionId, int imgWidth, int imgHeight,
            Autodesk.AutoCAD.Colors.Color backColor)
        {
            var imgPtr = GetBlockImagePointer(blockDefinitionId, imgWidth, imgHeight, backColor);
            return ConvertBitmapToImageSource(imgPtr);
        }
 
        #region private methods
 
        private static ObjectId GetBlockTableRecordId(string blkName, Database db)
        {
            var blkId = ObjectId.Null;
 
            using (var tran = db.TransactionManager.StartTransaction())
            {
                var bt = (BlockTable)tran.GetObject(db.BlockTableId, OpenMode.ForRead);
                if (bt.Has(blkName))
                {
                    blkId = bt[blkName];
                }
                tran.Commit();
            }
 
            return blkId;
        }
 
        private static System.Windows.Media.ImageSource ConvertBitmapToImageSource(IntPtr imgHandle)
        {
            return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                imgHandle,
                IntPtr.Zero,
                System.Windows.Int32Rect.Empty,
                System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
 
        }
 
        #endregion
    }
}