Given the previous posts on this topic, I’d hope it’s no great surprise to regular readers that this month’s “Plugin of the Month” consists of a tool to simplify the capturing of screenshots within AutoCAD.
This month’s tool allows you to capture the current document, the entire application and an area of the drawing specified by the user or the extents of a set of objects, sending the results to a file or to the clipboard. It has optional settings to remap the background colour (which is useful for people working with a non-white background colour but who want to capture images for print documents), the foreground colour – whether to black or to grayscale – as well as some additional capabilities such as sending the results directly to a printer.
Now that Scott Sheppard is back from his well-deserved sabbatical (I’m very jealous of his 6-weeks off, less jealous of the inevitable email backlog), he has posted this month’s plugin to the Autodesk Labs site and has announced it on his blog. The version of the code provided on Labs is very similar to the final design iteration (#3) with some minor bugfixes to make sure we handle dialog cancellation properly (if the Objects option is chosen) and to make sure we wait long enough for the file selection dialog to be unpainted before we capture the screenshot. It also uses the code we’ve seen in previous plugins to automatically create demand-loading Registry entries on first load.
For completeness, here’s the C# code from the plugin distribution (to build this I suggest getting the code from Labs, as it includes the full project including the required demand-loading.cs file):
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.GraphicsSystem;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Colors;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.Drawing.Drawing2D;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Collections;
using System.Configuration;
using System;
using DemandLoading;
namespace Screenshot
{
public class ScreenshotApplication : IExtensionApplication
{
// Define a class for our custom data
public class AppData : ApplicationSettingsBase
{
[UserScopedSetting()]
[DefaultSettingValue("true")]
public bool Clipboard
{
get { return ((bool)this["Clipboard"]); }
set { this["Clipboard"] = (bool)value; }
}
[UserScopedSetting()]
[DefaultSettingValue("false")]
public bool Print
{
get { return ((bool)this["Print"]); }
set { this["Print"] = (bool)value; }
}
[UserScopedSetting()]
[DefaultSettingValue("false")]
public bool WhiteBackground
{
get { return ((bool)this["WhiteBackground"]); }
set { this["WhiteBackground"] = (bool)value; }
}
[UserScopedSetting()]
[DefaultSettingValue("false")]
public bool BlackForeground
{
get { return ((bool)this["BlackForeground"]); }
set { this["BlackForeground"] = (bool)value; }
}
[UserScopedSetting()]
[DefaultSettingValue("false")]
public bool Grayscale
{
get { return ((bool)this["Grayscale"]); }
set { this["Grayscale"] = (bool)value; }
}
}
// A struct for communicating colours to/from AutoCAD
public struct AcColorSettings
{
public UInt32 dwGfxModelBkColor;
public UInt32 dwGfxLayoutBkColor;
public UInt32 dwParallelBkColor;
public UInt32 dwBEditBkColor;
public UInt32 dwCmdLineBkColor;
public UInt32 dwPlotPrevBkColor;
public UInt32 dwSkyGradientZenithColor;
public UInt32 dwSkyGradientHorizonColor;
public UInt32 dwGroundGradientOriginColor;
public UInt32 dwGroundGradientHorizonColor;
public UInt32 dwEarthGradientAzimuthColor;
public UInt32 dwEarthGradientHorizonColor;
public UInt32 dwModelCrossHairColor;
public UInt32 dwLayoutCrossHairColor;
public UInt32 dwParallelCrossHairColor;
public UInt32 dwPerspectiveCrossHairColor;
public UInt32 dwBEditCrossHairColor;
public UInt32 dwParallelGridMajorLines;
public UInt32 dwPerspectiveGridMajorLines;
public UInt32 dwParallelGridMinorLines;
public UInt32 dwPerspectiveGridMinorLines;
public UInt32 dwParallelGridAxisLines;
public UInt32 dwPerspectiveGridAxisLines;
public UInt32 dwTextForeColor;
public UInt32 dwTextBkColor;
public UInt32 dwCmdLineForeColor;
public UInt32 dwAutoTrackingVecColor;
public UInt32 dwLayoutATrackVecColor;
public UInt32 dwParallelATrackVecColor;
public UInt32 dwPerspectiveATrackVecColor;
public UInt32 dwBEditATrackVecColor;
public UInt32 dwModelASnapMarkerColor;
public UInt32 dwLayoutASnapMarkerColor;
public UInt32 dwParallelASnapMarkerColor;
public UInt32 dwPerspectiveASnapMarkerColor;
public UInt32 dwBEditASnapMarkerColor;
public UInt32 dwModelDftingTooltipColor;
public UInt32 dwLayoutDftingTooltipColor;
public UInt32 dwParallelDftingTooltipColor;
public UInt32 dwPerspectiveDftingTooltipColor;
public UInt32 dwBEditDftingTooltipColor;
public UInt32 dwModelDftingTooltipBkColor;
public UInt32 dwLayoutDftingTooltipBkColor;
public UInt32 dwParallelDftingTooltipBkColor;
public UInt32 dwPerspectiveDftingTooltipBkColor;
public UInt32 dwBEditDftingTooltipBkColor;
public UInt32 dwModelLightGlyphs;
public UInt32 dwLayoutLightGlyphs;
public UInt32 dwParallelLightGlyphs;
public UInt32 dwPerspectiveLightGlyphs;
public UInt32 dwBEditLightGlyphs;
public UInt32 dwModelLightHotspot;
public UInt32 dwLayoutLightHotspot;
public UInt32 dwParallelLightHotspot;
public UInt32 dwPerspectiveLightHotspot;
public UInt32 dwBEditLightHotspot;
public UInt32 dwModelLightFalloff;
public UInt32 dwLayoutLightFalloff;
public UInt32 dwParallelLightFalloff;
public UInt32 dwPerspectiveLightFalloff;
public UInt32 dwBEditLightFalloff;
public UInt32 dwModelLightStartLimit;
public UInt32 dwLayoutLightStartLimit;
public UInt32 dwParallelLightStartLimit;
public UInt32 dwPerspectiveLightStartLimit;
public UInt32 dwBEditLightStartLimit;
public UInt32 dwModelLightEndLimit;
public UInt32 dwLayoutLightEndLimit;
public UInt32 dwParallelLightEndLimit;
public UInt32 dwPerspectiveLightEndLimit;
public UInt32 dwBEditLightEndLimit;
public UInt32 dwModelCameraGlyphs;
public UInt32 dwLayoutCameraGlyphs;
public UInt32 dwParallelCameraGlyphs;
public UInt32 dwPerspectiveCameraGlyphs;
public UInt32 dwModelCameraFrustrum;
public UInt32 dwLayoutCameraFrustrum;
public UInt32 dwParallelCameraFrustrum;
public UInt32 dwPerspectiveCameraFrustrum;
public UInt32 dwModelCameraClipping;
public UInt32 dwLayoutCameraClipping;
public UInt32 dwParallelCameraClipping;
public UInt32 dwPerspectiveCameraClipping;
public int nModelCrosshairUseTintXYZ;
public int nLayoutCrosshairUseTintXYZ;
public int nParallelCrosshairUseTintXYZ;
public int nPerspectiveCrosshairUseTintXYZ;
public int nBEditCrossHairUseTintXYZ;
public int nModelATrackVecUseTintXYZ;
public int nLayoutATrackVecUseTintXYZ;
public int nParallelATrackVecUseTintXYZ;
public int nPerspectiveATrackVecUseTintXYZ;
public int nBEditATrackVecUseTintXYZ;
public int nModelDftingTooltipBkUseTintXYZ;
public int nLayoutDftingTooltipBkUseTintXYZ;
public int nParallelDftingTooltipBkUseTintXYZ;
public int nPerspectiveDftingTooltipBkUseTintXYZ;
public int nBEditDftingTooltipBkUseTintXYZ;
public int nParallelGridMajorLineTintXYZ;
public int nPerspectiveGridMajorLineTintXYZ;
public int nParallelGridMinorLineTintXYZ;
public int nPerspectiveGridMinorLineTintXYZ;
public int nParallelGridAxisLineTintXYZ;
public int nPerspectiveGridAxisLineTintXYZ;
};
// For the coordinate tranformation we need...
// A Win32 function:
[DllImport("user32.dll")]
static extern bool ClientToScreen(IntPtr hWnd, ref Point pt);
// And to access the colours in AutoCAD, we need ObjectARX...
[DllImport("acad.exe",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?acedGetCurrentColors@@YAHPAUAcColorSettings@@@Z"
)]
static extern bool acedGetCurrentColors32(
out AcColorSettings colorSettings
);
[DllImport("acad.exe",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?acedSetCurrentColors@@YAHPAUAcColorSettings@@@Z"
)]
static extern bool acedSetCurrentColors32(
ref AcColorSettings colorSettings
);
// 64-bit versions of these functions...
[DllImport("acad.exe",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?acedGetCurrentColors@@YAHPEAUAcColorSettings@@@Z"
)]
static extern bool acedGetCurrentColors64(
out AcColorSettings colorSettings
);
[DllImport("acad.exe",
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?acedSetCurrentColors@@YAHPEAUAcColorSettings@@@Z"
)]
static extern bool acedSetCurrentColors64(
ref AcColorSettings colorSettings
);
// Helper functions that call automatically to 32- or 64-bit
// versions, as appropriate
static bool acedGetCurrentColors(
out AcColorSettings colorSettings
)
{
if (IntPtr.Size > 4)
return acedGetCurrentColors64(out colorSettings);
else
return acedGetCurrentColors32(out colorSettings);
}
static bool acedSetCurrentColors(
ref AcColorSettings colorSettings
)
{
if (IntPtr.Size > 4)
return acedSetCurrentColors64(ref colorSettings);
else
return acedSetCurrentColors32(ref colorSettings);
}
// IExtensionApplication protocol
public void Initialize()
{
try
{
RegistryUpdate.RegisterForDemandLoading();
}
catch
{ }
}
public void Terminate()
{
}
[CommandMethod("ADNPLUGINS", "REMOVESS", CommandFlags.Modal)]
static public void RemoveScreenshot()
{
RegistryUpdate.UnregisterForDemandLoading();
}
// Command to capture the main and active drawing windows
// or a user-selected portion of a drawing
[CommandMethod("ADNPLUGINS", "SCREENSHOT", CommandFlags.Modal)]
static public void CaptureScreenShot()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
// Retrieve our application settings (or create new ones)
AppData ad = new AppData();
ad.Reload();
if (ad != null)
{
string filename = "";
bool settingschosen;
PromptPointResult ppr;
do
{
settingschosen = false;
// Ask the user for the screen window to capture
PrintSettings(ed, ad);
PromptPointOptions ppo =
new PromptPointOptions(
"\nSelect first point of capture window or " +
"[Document/Application/Objects/Settings]: ",
"Document Application Objects Settings"
);
// Get the first point of the capture window,
// or a keyword
ppr = ed.GetPoint(ppo);
if (ppr.Status == PromptStatus.Keyword)
{
if (ppr.StringResult == "Document")
{
// Capture the active document window
if (!ad.Clipboard)
filename = GetFileName(ed);
ScreenShotToFile(
Application.DocumentManager.
MdiActiveDocument.Window,
30, 26, 10, 10,
filename,
ad
);
}
else if (ppr.StringResult == "Application")
{
// Capture the entire application window
if (!ad.Clipboard)
filename = GetFileName(ed);
ScreenShotToFile(
Application.MainWindow,
0, 0, 0, 0,
filename,
ad
);
}
else if (ppr.StringResult == "Objects")
{
// Ask the user to select a number of entities
PromptSelectionResult psr =
ed.GetSelection();
if (psr.Status == PromptStatus.OK)
{
// Regenerate to clear any selection highlighting
ed.Regen();
// Generate screen coordinate points based on the
// drawing points selected
// First we get the viewport number
short vp =
(short)Application.GetSystemVariable("CVPORT");
// Then the handle to the current drawing window
IntPtr hWnd = doc.Window.Handle;
// Get the screen extents of the selected entities
Point pt1, pt2;
GetExtentsOfSelection(
ed, doc, hWnd, vp, psr.Value, out pt1, out pt2
);
// Now save this portion of our screen as a raster
// image
if (!ad.Clipboard)
filename = GetFileName(ed);
ScreenShotToFile(pt1, pt2, filename, ad);
}
}
else if (ppr.StringResult == "Settings")
{
if (GetSettings(ed, ad))
ad.Save();
settingschosen = true;
}
}
}
while (settingschosen); // Loop if settings were modified
if (ppr.Status == PromptStatus.OK)
{
// Now we're ready to select the second point
Point3d first = ppr.Value;
ppr =
ed.GetCorner(
"\nSelect second point of capture window: ",
first
);
if (ppr.Status != PromptStatus.OK)
return;
Point3d second = ppr.Value;
// Generate screen coordinate points based on the
// drawing points selected
Point pt1, pt2;
// First we get the viewport number
short vp =
(short)Application.GetSystemVariable("CVPORT");
// Then the handle to the current drawing window
IntPtr hWnd = doc.Window.Handle;
// Now calculate the selected corners in screen coordinates
pt1 = ScreenFromDrawingPoint(ed, hWnd, first, vp, true);
pt2 = ScreenFromDrawingPoint(ed, hWnd, second, vp, true);
// Now save this portion of our screen as a raster image
if (!ad.Clipboard)
filename = GetFileName(ed);
ScreenShotToFile(pt1, pt2, filename, ad);
}
}
}
// Iterate through a selection-set and get the overall extents
// of the various objects relative to the screen
// (this is imperfect: our extents in WCS may not translate to
// the extents on the screen. A more thorough approach would be
// to get a number of points from an object and check each)
private static void GetExtentsOfSelection(
Editor ed,
Document doc,
IntPtr hWnd,
short vp,
SelectionSet ss,
out Point min,
out Point max
)
{
// Create minimum and maximum points for the "on screen"
// extents of our objects
min = new Point();
max = new Point();
// Know which is the first pass through
bool first = true;
// Some variables to store transformation results
Point pt1 = new Point(), pt2 = new Point();
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
foreach (SelectedObject so in ss)
{
DBObject obj =
tr.GetObject(so.ObjectId, OpenMode.ForRead);
Entity ent = obj as Entity;
if (ent != null)
{
// Get the WCS extents of each object
Extents3d ext = ent.GeometricExtents;
// Calculate the extent corners in screen coordinates
// (this may not be the true screen extents, but we'll
// hope it's good enough)
pt1 =
ScreenFromDrawingPoint(
ed, hWnd, ext.MinPoint, vp, false
);
pt2 =
ScreenFromDrawingPoint(
ed, hWnd, ext.MaxPoint, vp, false
);
// The points may not be ordered, so get the min and max
// values for both X and Y from both points
int minX = Math.Min(pt1.X, pt2.X);
int minY = Math.Min(pt1.Y, pt2.Y);
int maxX = Math.Max(pt1.X, pt2.X);
int maxY = Math.Max(pt1.Y, pt2.Y);
// On the first run through, just get the points
if (first)
{
min = new Point(minX, minY);
max = new Point(maxX, maxY);
first = false;
}
else
{
// On subsequent runs through, we need to compare
if (minX < min.X) min.X = minX;
if (minY < min.Y) min.Y = minY;
if (maxX > max.X) max.X = maxX;
if (maxY > max.Y) max.Y = maxY;
}
}
}
tr.Commit();
}
}
// Print the current application settings to the command-line
private static void PrintSettings(Editor ed, AppData ad)
{
ed.WriteMessage(
"\nCurrent settings: Output={0}, Print={1}, " +
"Background={2}, Foreground={3}, Grayscale={4}",
ad.Clipboard ? "Clipboard" : "File",
ad.Print ? "Yes" : "No",
ad.WhiteBackground ? "ForceToWhite" : "Normal",
ad.BlackForeground ? "ForceToBlack" : "Normal",
ad.Grayscale ? "On" : "Off"
);
}
// Ask the user to modify the application settings
private static bool GetSettings(Editor ed, AppData ad)
{
// At our top-level settings prompt, make the default
// to exit back up
PromptKeywordOptions pko =
new PromptKeywordOptions(
"\nSetting to change " +
"[Output/Print/Background/Foreground/Grayscale/Exit]: ",
"Output Print Background Foreground Grayscale Exit"
);
pko.Keywords.Default = "Exit";
PromptResult pr;
bool settingschanged = false;
do
{
// Start by printing the current settings
PrintSettings(ed, ad);
pr = ed.GetKeywords(pko);
if (pr.Status == PromptStatus.OK)
{
if (pr.StringResult == "Output")
{
// If Output is selected, ask whether to put the
// image on the clipboard or save to file
PromptKeywordOptions pko2 =
new PromptKeywordOptions(
"\nSave to file or place on the clipboard " +
"[File/Clipboard]: ",
"File Clipboard"
);
// The default depends on our current settings
pko2.Keywords.Default =
(ad.Clipboard ? "Clipboard" : "File");
PromptResult pr2 = ed.GetKeywords(pko2);
if (pr2.Status == PromptStatus.OK)
{
// Change the settings, as needed
bool clipboard =
(pr2.StringResult == "Clipboard");
if (ad.Clipboard != clipboard)
{
ad.Clipboard = clipboard;
settingschanged = true;
}
}
}
else if (pr.StringResult == "Print")
{
// If Print is different, ask whether to
// send the image to the printer
bool different =
GetYesOrNo(
ed,
"\nSend image to printer once captured",
ad.Print
);
if (different)
{
ad.Print = !ad.Print;
settingschanged = true;
}
}
else if (pr.StringResult == "Background")
{
// If Background is different, ask whether to
// force the background colour to white
// (we could allow selection of a colour,
// but that's out of scope, for now)
bool different =
GetYesOrNo(
ed,
"\nForce background color to white",
ad.WhiteBackground
);
if (different)
{
ad.WhiteBackground = !ad.WhiteBackground;
settingschanged = true;
}
}
else if (pr.StringResult == "Foreground")
{
// If Foreground is different, ask whether to
// force the foreground colour to black
// (we could allow selection of a colour,
// but that's out of scope, for now)
bool different =
GetYesOrNo(
ed,
"\nForce foreground color to black",
ad.BlackForeground
);
if (different)
{
ad.BlackForeground = !ad.BlackForeground;
settingschanged = true;
}
}
else if (pr.StringResult == "Grayscale")
{
// If Grayscale is different, ask whether to
// force the foreground pixels to be gray
bool different =
GetYesOrNo(
ed,
"\nConvert image to grayscale",
ad.Grayscale
);
if (different)
{
ad.Grayscale = !ad.Grayscale;
settingschanged = true;
}
}
}
}
while (
pr.Status == PromptStatus.OK &&
pr.StringResult != "Exit"
); // Loop until Exit or cancel
return settingschanged;
}
// Ask the user to enter yes or no to a particular question,
// setting the default option appropriately
private static bool GetYesOrNo(
Editor ed,
string prompt,
bool defval
)
{
bool changed = false;
PromptKeywordOptions pko =
new PromptKeywordOptions(prompt + " [Yes/No]: ", "Yes No");
// The default depends on our current settings
pko.Keywords.Default =
(defval ? "Yes" : "No");
PromptResult pr = ed.GetKeywords(pko);
if (pr.Status == PromptStatus.OK)
{
// Change the settings, as needed
bool newval =
(pr.StringResult == "Yes");
if (defval != newval)
{
changed = true;
}
}
return changed;
}
// Ask the user to select a location to save our file to
private static string GetFileName(Editor ed)
{
string filename = "";
// The entries here will drive the behaviour of the
// GetFormatForFile() function
PromptSaveFileOptions pofo =
new PromptSaveFileOptions(
"\nSelect image location: "
);
pofo.Filter =
"Bitmap (*.bmp)|*.bmp|" +
"GIF (*.gif)|*.gif|" +
"JPEG (*.jpg)|*.jpg|" +
"PNG (*.png)|*.png|" +
"TIFF (*.tif)|*.tif";
// Set the default save location to be the current drawing
string fn = ed.Document.Database.Filename;
if (fn.Contains("."))
{
int extIdx = fn.LastIndexOf(".");
if (fn.Substring(extIdx + 1) != "dwt" &&
fn.Contains("\\"))
{
int sepIdx = fn.LastIndexOf("\\");
pofo.InitialDirectory =
fn.Substring(0, sepIdx);
}
}
PromptFileNameResult pfnr =
ed.GetFileNameForSave(pofo);
if (pfnr.Status == PromptStatus.OK)
{
filename = pfnr.StringResult;
// If a file was selected, wait for some time to allow
// the "file already exists" dialog to disappear
// (1000ms = one second - may need tweaking)
System.Threading.Thread.Sleep(1000);
}
return filename;
}
// Perform our tranformations to get from UCS
// (or WCS) to screen coordinates
private static Point ScreenFromDrawingPoint(
Editor ed,
IntPtr hWnd,
Point3d pt,
short vpNum,
bool useUcs
)
{
// Transform from UCS to WCS, if needed
Point3d wcsPt =
(useUcs ?
pt.TransformBy(ed.CurrentUserCoordinateSystem)
: pt
);
// Then get the screen coordinates within the client
// and translate these for the overall screen
Point res = ed.PointToScreen(wcsPt, vpNum);
ClientToScreen(hWnd, ref res);
return res;
}
// Save the display of an AutoCAD window as a raster file
// and/or an image on the clipboard
private static void ScreenShotToFile(
Autodesk.AutoCAD.Windows.Window wd,
int top, int bottom, int left, int right,
string filename,
AppData ad
)
{
Point pt = wd.Location;
Size sz = wd.Size;
pt.X += left;
pt.Y += top;
sz.Height -= top + bottom;
sz.Width -= left + right;
SaveScreenPortion(pt, sz, filename, ad);
}
// Save a screen window between two corners as a raster file
// and/or an image on the clipboard
private static void ScreenShotToFile(
Point pt1,
Point pt2,
string filename,
AppData ad
)
{
// Create the top left corner from the two corners
// provided (by taking the min of both X and Y values)
Point pt =
new Point(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y));
// Determine the size by subtracting X & Y values and
// taking the absolute value of each
Size sz =
new Size(Math.Abs(pt1.X - pt2.X), Math.Abs(pt1.Y - pt2.Y));
SaveScreenPortion(pt, sz, filename, ad);
}
// Save a portion of the screen display as a raster file
// and/or an image on the clipboard
private static void SaveScreenPortion(
Point pt,
Size sz,
string filename,
AppData ad
)
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
Manager gsm = doc.GraphicsManager;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
AcColorSettings ocs = new AcColorSettings();
ObjectId vtrId = ObjectId.Null,
sbId = ObjectId.Null;
bool in3DView = is3D(gsm);
if (ad.WhiteBackground)
{
if (in3DView)
{
Set3DBackground(
ed,
db,
tr,
new EntityColor(255, 255, 255),
out vtrId,
out sbId
);
}
else
{
// Get the current system colours
acedGetCurrentColors(out ocs);
// Take a copy - we'll leave the original to reset
// the values later on, once we've finished
AcColorSettings cs = ocs;
// Make both background colours white (the 3D
// background isn't currently being picked up)
cs.dwGfxModelBkColor = 16777215;
cs.dwGfxLayoutBkColor = 16777215;
//cs.dwParallelBkColor = 16777215;
// Set the modified colours
acedSetCurrentColors(ref cs);
ed.Regen();
}
// Update the screen to reflect the changes
ed.UpdateScreen();
}
// Set the bitmap object to the size of the window
Bitmap bmp =
new Bitmap(
sz.Width,
sz.Height,
PixelFormat.Format32bppArgb
);
using (bmp)
{
// Create a graphics object from the bitmap
using (Graphics gfx = Graphics.FromImage(bmp))
{
// Take a screenshot of our window
gfx.CopyFromScreen(
pt.X, pt.Y, 0, 0, sz,
CopyPixelOperation.SourceCopy
);
Bitmap processed;
if (ad.BlackForeground || ad.Grayscale)
{
System.Drawing.Color col;
if (in3DView)
{
if (ad.WhiteBackground)
{
col = System.Drawing.Color.White;
}
else
{
acedGetCurrentColors(out ocs);
uint bgcol = ocs.dwParallelBkColor;
col =
System.Drawing.Color.FromArgb((int)bgcol);
}
}
else
{
bool inModelspace =
((short)Application.GetSystemVariable(
"TILEMODE"
) == 0);
uint bgcol =
(inModelspace
? ocs.dwGfxModelBkColor
: ocs.dwGfxLayoutBkColor
);
col =
System.Drawing.Color.FromArgb((int)bgcol);
}
processed =
ConvertToGrayscale(
bmp,
col,
ad.BlackForeground,
System.Drawing.Color.Black
);
}
else
{
processed = bmp;
}
// Take a copy of the bitmap for printing
using (Bitmap toPrint = processed)
{
// Save the screenshot to the specified location
if (filename != null && filename != "")
{
processed.Save(filename, GetFormatForFile(filename));
ed.WriteMessage(
"\nImage captured and saved to \"{0}\".",
filename
);
}
// Copy it to the clipboard
if (ad.Clipboard)
{
System.Windows.Forms.Clipboard.SetImage(processed);
ed.WriteMessage(
"\nImage captured to the clipboard."
);
}
// Send it to a printer
if (ad.Print)
{
PrintDocument pdoc = new PrintDocument();
pdoc.PrintPage +=
delegate(object sender, PrintPageEventArgs e)
{
int wid = toPrint.Width,
hgt = toPrint.Height;
// Store the ratio between width and height
double ratio = (double)wid / (double)hgt;
// If the image's width isn't the
// same as the page...
if (wid != e.MarginBounds.Width)
{
// Change the width to fit the page
wid = e.MarginBounds.Width;
// Adjust the height to maintain scale
// (even if bigger than the page height)
hgt = (int)(wid / ratio);
}
// If the image's height is bigger than the
// page...
if (hgt > e.MarginBounds.Height)
{
// Change the height to fit the paper
hgt = e.MarginBounds.Height;
// Adjust the width to maintain scale
wid = (int)(ratio * hgt);
}
// Set the interpolation settings to high
// quality
e.Graphics.InterpolationMode =
InterpolationMode.HighQualityBicubic;
// And send the image out to the page
e.Graphics.DrawImage(
toPrint,
e.MarginBounds.X,
e.MarginBounds.Y,
wid,
hgt
);
};
// Create and show the print dialog
System.Windows.Forms.PrintDialog pdlg =
new System.Windows.Forms.PrintDialog();
pdlg.Document = pdoc;
if (pdlg.ShowDialog() ==
System.Windows.Forms.DialogResult.OK)
pdoc.Print(); // Print on OK
}
}
}
}
if (ad.WhiteBackground)
{
if (vtrId != ObjectId.Null || sbId != ObjectId.Null)
{
Remove3DBackground(db, tr, vtrId, sbId);
}
else
{
acedSetCurrentColors(ref ocs);
ed.Regen();
}
ed.UpdateScreen();
}
tr.Commit();
}
}
// Check whether the active viewport is 3D
private static bool is3D(Manager gsm)
{
short vp =
(short)Application.GetSystemVariable("CVPORT");
View v = gsm.GetGsView(vp, false);
using (v)
{
return (v != null);
}
}
// Return the image format to use for a particular filename
private static ImageFormat GetFormatForFile(string filename)
{
// If all else fails, let's create a PNG
// (might also choose to throw an exception)
ImageFormat imf = ImageFormat.Png;
if (filename.Contains("."))
{
// Get the filename's extension (what follows the last ".")
string ext =
filename.Substring(filename.LastIndexOf(".") + 1);
// Get the first three characters of the extension
if (ext.Length > 3)
ext = ext.Substring(0, 3);
// Choose the format based on the extension (in lowercase)
switch (ext.ToLower())
{
case "bmp":
imf = ImageFormat.Bmp;
break;
case "gif":
imf = ImageFormat.Gif;
break;
case "jpg":
imf = ImageFormat.Jpeg;
break;
case "tif":
imf = ImageFormat.Tiff;
break;
case "wmf":
imf = ImageFormat.Wmf;
break;
default:
imf = ImageFormat.Png;
break;
}
}
return imf;
}
// Set the background colour of a 3D view
private static void Set3DBackground(
Editor ed,
Database db,
Transaction tr,
EntityColor ec,
out ObjectId vtrId,
out ObjectId sbId
)
{
// We're be returning IDs of the Viewport Table Record
// and of the background itself
vtrId = ObjectId.Null;
sbId = ObjectId.Null;
ed.UpdateTiledViewportsInDatabase();
ViewportTable vt =
(ViewportTable)tr.GetObject(
db.ViewportTableId,
OpenMode.ForRead
);
if (vt.Has("*Active"))
{
// Let's get the Viewport Table Record
vtrId = vt["*Active"];
DBDictionary nod =
(DBDictionary)tr.GetObject(
db.NamedObjectsDictionaryId,
OpenMode.ForRead
);
// And create the background dictionary, if none exists
ObjectId bkdId = ObjectId.Null;
DBDictionary bkDict = null;
const string dictKey = "ACAD_BACKGROUND";
const string bkKey = "ADNPlugin_Screenshot";
if (nod.Contains(dictKey))
{
bkdId = nod.GetAt(dictKey);
bkDict =
(DBDictionary)tr.GetObject(bkdId, OpenMode.ForWrite);
}
else
{
bkDict = new DBDictionary();
nod.UpgradeOpen();
bkdId = nod.SetAt(dictKey, bkDict);
tr.AddNewlyCreatedDBObject(bkDict, true);
}
// Get or create our background object
if (bkDict.Contains(bkKey))
{
sbId = bkDict.GetAt(bkKey);
}
else
{
SolidBackground sb = new SolidBackground();
sb.Color = ec;
sbId = bkDict.SetAt(bkKey, sb);
tr.AddNewlyCreatedDBObject(sb, true);
}
// And set it to the viewport
ViewportTableRecord vtr =
(ViewportTableRecord)tr.GetObject(
vtrId,
OpenMode.ForWrite
);
vtr.Background = sbId;
}
}
// Remove the previously set 3D background colour
private static void Remove3DBackground(
Database db,
Transaction tr,
ObjectId vtrId,
ObjectId sbId
)
{
// First remove it from the viewport
if (vtrId != ObjectId.Null)
{
ViewportTableRecord vtr =
(ViewportTableRecord)tr.GetObject(
vtrId,
OpenMode.ForWrite
);
vtr.Background = ObjectId.Null;
}
// And then erase the object itself (although
// I suspect this is redundant)
if (sbId != ObjectId.Null)
{
SolidBackground sb =
(SolidBackground)tr.GetObject(
sbId,
OpenMode.ForRead,
true
);
if (!sb.IsErased)
{
sb.UpgradeOpen();
sb.Erase();
}
}
}
// Return a grayscale version of a provided bitmap,
// with the option of forcing non-background pixels to
// be black
public static Bitmap ConvertToGrayscale(
Bitmap src,
System.Drawing.Color bgcol,
bool force,
System.Drawing.Color fgcol
)
{
// From http://www.bobpowell.net/grayscale.htm
Bitmap bmp = new Bitmap(src.Width, src.Height);
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
System.Drawing.Color c = src.GetPixel(x, y);
int lum =
(force && !SameColors(c, bgcol) ?
0 :
(int)(c.R * 0.3 + c.G * 0.59 + c.B * 0.11)
// 0.299R + 0.587G + 0.114B
);
bmp.SetPixel(
x,
y,
(lum == 0 ?
fgcol :
System.Drawing.Color.FromArgb(lum, lum, lum))
);
}
}
return bmp;
}
// Return whether two colour can be considered equivalent
// in terms of RGB values
private static bool SameColors(
System.Drawing.Color a,
System.Drawing.Color b
)
{
// Ignore Alpha channel, just compare RGB
return (a.R == b.R && a.G == b.G && a.B == b.B);
}
}
}
Now I’m going to spend some time repackaging next month’s plugin, Layer Reporter, which has kindly been provided by Terry Dotson, a long-time member of the Autodesk Developer Network.