明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 6049|回复: 1

[Kean专集] Kean专题(17)—Plugin of the Month

 关闭 [复制链接]
发表于 2010-2-3 21:42 | 显示全部楼层 |阅读模式
一、剪贴板管理器
Clipboard Manager: October’s ADN Plugin of the Month, now live on Autodesk Labs
As Scott is leaving on a well-deserved sabbatical, he has gone ahead and posted our next Plugin of the Month a few days ahead of schedule. Here’s a link to Scott’s post announcing the tool.
This is a very cool little application developed by Mark Dubbelaar from Australia. Mark has been drafting/designing with AutoCAD for the last 10+ years and, during this time, has used a variety of programming languages to customize AutoCAD: LISP, VBA and now VB.NET. Mark was inspired by the “clipboard ring” functionality that used to be in Microsoft Office (at least I say “used to be” because I haven’t found it in Office 2007), and decided to implement similar functionality in AutoCAD.
The implementation of the tool is quite straightforward but the functionality is really very compelling: after having NETLOADed the tool and run the CLIPBOARD command, as you use Ctrl-C to copy drawing objects from inside AutoCAD to the clipboard a custom palette gets populated with entries containing these sets of objects. Each entry contains a time-stamp and an automatically-generated name which you can then change to something more meaningful.
When you want to use these clipboard entries, you simply right-click on one and choose the appropriate paste option (which ultimately just calls through to the standard AutoCAD paste commands, PASTECLIP, PASTEBLOCK and PASTEORIG, reducing the complexity of the tool).

That’s really all there is to it: a simple yet really useful application. Thanks for providing such a great little tool, Mark! :-)
Under the hood, the code is quite straightforward. The main file, Clipboard.vb, sets up the application to create demand-loading entries when first loaded into AutoCAD and defines a couple of commands – CLIPBOARD and REMOVECB, which removes the demand-loading entries to “uninstall” the application. It also contains the PaletteSet that contains our CbPalette and gets displayed by the CLIPBOARD command.
  1. Imports Autodesk.AutoCAD.Runtime
  2. Imports Autodesk.AutoCAD.Windows
  3. Imports Autodesk.AutoCAD.EditorInput
  4. Public Class ClipBoard
  5.   Implements IExtensionApplication
  6.   <DebuggerBrowsable(DebuggerBrowsableState.Never)> _
  7.   Private _cp As CbPalette = Nothing
  8.   Public ReadOnly Property ClipboardPalette() As CbPalette
  9.     Get
  10.       If _cp Is Nothing Then
  11.         _cp = New CbPalette
  12.       End If
  13.       Return _cp
  14.     End Get
  15.   End Property
  16.   Private _ps As PaletteSet = Nothing
  17.   Public ReadOnly Property PaletteSet() As PaletteSet
  18.     Get
  19.       If _ps Is Nothing Then
  20.         _ps = New PaletteSet("Clipboard", _
  21.           New System.Guid("ED8CDB2B-3281-4177-99BE-E1A46C3841AD"))
  22.         _ps.Text = "Clipboard"
  23.         _ps.DockEnabled = DockSides.Left + _
  24.           DockSides.Right + DockSides.None
  25.         _ps.MinimumSize = New System.Drawing.Size(200, 300)
  26.         _ps.Size = New System.Drawing.Size(300, 500)
  27.         _ps.Add("Clipboard", ClipboardPalette)
  28.       End If
  29.       Return _ps
  30.     End Get
  31.   End Property
  32.   Private Sub Initialize() _
  33.     Implements IExtensionApplication.Initialize
  34.     DemandLoading.RegistryUpdate.RegisterForDemandLoading()
  35.   End Sub
  36.   Private Sub Terminate() _
  37.     Implements IExtensionApplication.Terminate
  38.   End Sub
  39.   <CommandMethod("ADNPLUGINS", "CLIPBOARD", CommandFlags.Modal)> _
  40.   Public Sub ShowClipboard()
  41.     PaletteSet.Visible = True
  42.   End Sub
  43.   <CommandMethod("ADNPLUGINS", "REMOVECB", CommandFlags.Modal)> _
  44.   Public Sub RemoveClipboard()
  45.     DemandLoading.RegistryUpdate.UnregisterForDemandLoading()
  46.     Dim ed As Editor = _
  47.       Autodesk.AutoCAD.ApplicationServices.Application _
  48.       .DocumentManager.MdiActiveDocument.Editor()
  49.     ed.WriteMessage(vbCr + _
  50.       "The Clipboard Manager will not be loaded" _
  51.       + " automatically in future editing sessions.")
  52.   End Sub
  53. End Class
It’s the Clipboard_Palette.vb file that contains the more interesting code, implementing the behaviour of the CbPalette object. The real “magic” is how it hooks into AutoCAD’s COPYCLIP by attaching itself as the default “clipboard viewer”.
  1. Imports AcApp = Autodesk.AutoCAD.ApplicationServices.Application
  2. Imports System.Windows.Forms
  3. Public Class CbPalette
  4.   ' Constants for Windows API calls
  5.   Private Const WM_DRAWCLIPBOARD As Integer = &H308
  6.   Private Const WM_CHANGECBCHAIN As Integer = &H30D
  7.   ' Handle for next clipboard viewer
  8.   Private _nxtCbVwrHWnd As IntPtr
  9.   ' Boolean to control access to clipboard data
  10.   Private _internalHold As Boolean = False
  11.   ' Counter for our visible clipboard name
  12.   Private _clipboardCounter As Integer = 0
  13.   ' Windows API declarations
  14.   Declare Auto Function SetClipboardViewer Lib "user32" _
  15.     (ByVal HWnd As IntPtr) As IntPtr
  16.   Declare Auto Function SendMessage Lib "User32" _
  17.     (ByVal HWnd As IntPtr, ByVal Msg As Integer, _
  18.     ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Long
  19.   ' Class constructor
  20.   Public Sub New()
  21.     ' This call is required by the Windows Form Designer
  22.     InitializeComponent()
  23.     ' Register ourselves to handle clipboard modifications
  24.     _nxtCbVwrHWnd = SetClipboardViewer(Handle)
  25.   End Sub
  26.   Private Sub AddDataToGrid()
  27.     Dim currentClipboardData As DataObject = _
  28.       My.Computer.Clipboard.GetDataObject
  29.     ' If the clipboard contents are AutoCAD-related
  30.     If IsAutoCAD(currentClipboardData.GetFormats) Then
  31.       ' Create a new row for our grid and add our clipboard
  32.       ' data stored in the "tag"
  33.       Dim newRow As New DataGridViewRow()
  34.       newRow.Tag = currentClipboardData
  35.       ' Increment our counter
  36.       _clipboardCounter += 1
  37.       ' Create and add a cell for the name, using our counter
  38.       Dim newNameCell As New DataGridViewTextBoxCell
  39.       newNameCell.Value = "Clipboard " & _clipboardCounter
  40.       newRow.Cells.Add(newNameCell)
  41.       ' Get the current time and place that in another cell
  42.       Dim newTimeCell As New DataGridViewTextBoxCell
  43.       newTimeCell.Value = Now.ToLongTimeString
  44.       newRow.Cells.Add(newTimeCell)
  45.       ' Add our row to the data grid and select it
  46.       clipboardDataGridView.Rows.Add(newRow)
  47.       clipboardDataGridView.FirstDisplayedScrollingRowIndex = _
  48.         clipboardDataGridView.Rows.Count - 1
  49.       newRow.Selected = True
  50.     End If
  51.   End Sub
  52.   ' Move the selected item's data into the clipboard
  53.   ' Check whether the clipboard data was created by AutoCAD
  54.   Private Function IsAutoCAD(ByVal Formats As String()) As Boolean
  55.     For Each item As String In Formats
  56.       If item.Contains("AutoCAD") Then Return True
  57.     Next
  58.     Return False
  59.   End Function
  60.   Private Sub PasteToClipboard()
  61.     ' Use a variable to make sure we don't edit the
  62.     ' clipboard contents at the wrong time
  63.     _internalHold = True
  64.     My.Computer.Clipboard.SetDataObject( _
  65.       clipboardDataGridView.SelectedRows.Item(0).Tag)
  66.     _internalHold = False
  67.   End Sub
  68.   ' Send a command to AutoCAD
  69.   Private Sub SendAutoCADCommand(ByVal cmd As String)
  70.     AcApp.DocumentManager.MdiActiveDocument.SendStringToExecute( _
  71.       cmd, True, False, True)
  72.   End Sub
  73.   ' Our context-menu command handlers
  74.   Private Sub PasteToolStripButton_Click( _
  75.     ByVal sender As Object, ByVal e As EventArgs) _
  76.     Handles PasteToolStripMenuItem.Click
  77.     ' Swap the data from the selected item in the grid into the
  78.     ' clipboard and use the internal AutoCAD command to paste it
  79.     If clipboardDataGridView.SelectedRows.Count = 1 Then
  80.       PasteToClipboard()
  81.       SendAutoCADCommand("_pasteclip ")
  82.     End If
  83.   End Sub
  84.   Private Sub PasteAsBlockToolStripMenuItem_Click( _
  85.     ByVal sender As Object, ByVal e As EventArgs) _
  86.     Handles PasteAsBlockToolStripMenuItem.Click
  87.     ' Swap the data from the selected item in the grid into the
  88.     ' clipboard and use the internal AutoCAD command to paste it
  89.     ' as a block
  90.     If clipboardDataGridView.SelectedRows.Count = 1 Then
  91.       PasteToClipboard()
  92.       SendAutoCADCommand("_pasteblock ")
  93.     End If
  94.   End Sub
  95.   Private Sub PasteToOriginalCoordinatesToolStripMenuItem_Click( _
  96.     ByVal sender As Object, ByVal e As EventArgs) _
  97.     Handles PasteToOriginalCoordinatesToolStripMenuItem.Click
  98.     ' Swap the data from the selected item in the grid into the
  99.     ' clipboard and use the internal AutoCAD command to paste it
  100.     ' at the original location
  101.     If clipboardDataGridView.SelectedRows.Count = 1 Then
  102.       PasteToClipboard()
  103.       SendAutoCADCommand("_pasteorig ")
  104.     End If
  105.   End Sub
  106.   Private Sub RemoveAllToolStripButton_Click( _
  107.     ByVal sender As Object, ByVal e As EventArgs) _
  108.     Handles RemoveAllToolStripButton.Click
  109.     ' Remove all the items in the grid
  110.     clipboardDataGridView.Rows.Clear()
  111.   End Sub
  112.   Private Sub RenameToolStripMenuItem_Click( _
  113.     ByVal sender As Object, ByVal e As EventArgs) _
  114.     Handles RenameToolStripMenuItem.Click
  115.     ' Rename the selected row by editing the name cell
  116.     If clipboardDataGridView.SelectedRows.Count = 1 Then
  117.       clipboardDataGridView.BeginEdit(True)
  118.     End If
  119.   End Sub
  120.   Private Sub RemoveToolStripMenuItem_Click( _
  121.     ByVal sender As Object, ByVal e As EventArgs) _
  122.     Handles RemoveToolStripMenuItem.Click
  123.     ' Remove the selected grid item
  124.     If clipboardDataGridView.SelectedRows.Count = 1 Then
  125.       clipboardDataGridView.Rows.Remove( _
  126.         clipboardDataGridView.SelectedRows.Item(0))
  127.     End If
  128.   End Sub
  129.   ' Our grid view event handlers
  130.   Private Sub ClipboardDataGridView_CellMouseDown( _
  131.     ByVal sender As Object, _
  132.     ByVal e As DataGridViewCellMouseEventArgs) _
  133.     Handles clipboardDataGridView.CellMouseDown
  134.     ' Responding to this event allows us to make sure the
  135.     ' correct row is properly selected on right-click
  136.     If e.Button = Windows.Forms.MouseButtons.Right Then
  137.       clipboardDataGridView.CurrentCell = _
  138.         clipboardDataGridView.Item(e.ColumnIndex, e.RowIndex)
  139.     End If
  140.   End Sub
  141.   Private Sub ClipboardDataGridView_MouseDown( _
  142.     ByVal sender As System.Object, ByVal e As MouseEventArgs) _
  143.     Handles clipboardDataGridView.MouseDown
  144.     ' On right-click display the row as selected and show
  145.     ' the context menu at the location of the cursor
  146.     If e.Button = Windows.Forms.MouseButtons.Right Then
  147.       Dim hti As DataGridView.HitTestInfo = _
  148.         clipboardDataGridView.HitTest(e.X, e.Y)
  149.       If hti.Type = DataGridViewHitTestType.Cell Then
  150.         clipboardDataGridView.ClearSelection()
  151.         clipboardDataGridView.Rows(hti.RowIndex).Selected = True
  152.         ContextMenuStrip.Show(clipboardDataGridView, e.Location)
  153.       End If
  154.     End If
  155.   End Sub
  156.   ' Override WndProc to get messages
  157.   Protected Overrides Sub WndProc(ByRef m As Message)
  158.     Select Case m.Msg
  159.   ' The clipboard has changed
  160.   Case Is = WM_DRAWCLIPBOARD
  161.     If Not _internalHold Then AddDataToGrid()
  162.     SendMessage(_nxtCbVwrHWnd, m.Msg, m.WParam, m.LParam)
  163.     ' Another clipboard viewer has removed itself
  164.   Case Is = WM_CHANGECBCHAIN
  165.     If m.WParam = CType(_nxtCbVwrHWnd, IntPtr) Then
  166.       _nxtCbVwrHWnd = m.LParam
  167.     Else
  168.       SendMessage(_nxtCbVwrHWnd, m.Msg, m.WParam, m.LParam)
  169.     End If
  170.     End Select
  171.     MyBase.WndProc(m)
  172.   End Sub
  173. End Class
  174. Public Class PaletteToolStrip
  175.   Inherits ToolStrip
  176.   Public Sub New()
  177.     MyBase.New()
  178.   End Sub
  179.   Public Sub New(ByVal ParamArray Items() As ToolStripItem)
  180.     MyBase.New(Items)
  181.   End Sub
  182.   Protected Overrides Sub WndProc(ByRef m As Message)
  183.     If m.Msg = &H21 AndAlso CanFocus AndAlso Not Focused Then
  184.       Focus()
  185.     End If
  186.     MyBase.WndProc(m)
  187.   End Sub
  188. End Class
I also added a VB.NET version of the C# code that automatically registers an AutoCAD .NET application for demand-loading based on the commands it defines:
  1. Imports System.Collections.Generic
  2. Imports System.Reflection
  3. Imports System.Resources
  4. Imports System
  5. Imports Microsoft.Win32
  6. Imports Autodesk.AutoCAD.DatabaseServices
  7. Imports Autodesk.AutoCAD.Runtime
  8. Namespace DemandLoading
  9.   Public Class RegistryUpdate
  10.     Public Shared Sub RegisterForDemandLoading()
  11.   ' Get the assembly, its name and location
  12.   Dim assem As Assembly = Assembly.GetExecutingAssembly()
  13.   Dim name As String = assem.GetName().Name
  14.   Dim path As String = assem.Location
  15.   ' We'll collect information on the commands
  16.   ' (we could have used a map or a more complex
  17.   ' container for the global and localized names
  18.   ' - the assumption is we will have an equal
  19.   ' number of each with possibly fewer groups)
  20.   Dim globCmds As New List(Of String)()
  21.   Dim locCmds As New List(Of String)()
  22.   Dim groups As New List(Of String)()
  23.   ' Iterate through the modules in the assembly
  24.   Dim mods As [Module]() = assem.GetModules(True)
  25.   For Each [mod] As [Module] In mods
  26.     ' Within each module, iterate through the types
  27.     Dim types As Type() = [mod].GetTypes()
  28.     For Each type As Type In types
  29.       ' We may need to get a type's resources
  30.       Dim rm As New ResourceManager(type.FullName, assem)
  31.       rm.IgnoreCase = True
  32.       ' Get each method on a type
  33.       Dim meths As MethodInfo() = type.GetMethods()
  34.       For Each meth As MethodInfo In meths
  35.         ' Get the methods custom command attribute(s)
  36.         Dim attbs As Object() = _
  37.           meth.GetCustomAttributes( _
  38.         GetType(CommandMethodAttribute), True)
  39.         For Each attb As Object In attbs
  40.           Dim cma As CommandMethodAttribute = _
  41.             TryCast(attb, CommandMethodAttribute)
  42.           If cma IsNot Nothing Then
  43.             ' And we can finally harvest the information
  44.             ' about each command
  45.             Dim globName As String = cma.GlobalName
  46.             Dim locName As String = globName
  47.             Dim lid As String = cma.LocalizedNameId
  48.             ' If we have a localized command ID,
  49.             ' let's look it up in our resources
  50.             If lid IsNot Nothing Then
  51.               ' Let's put a try-catch block around this
  52.               ' Failure just means we use the global
  53.               ' name twice (the default)
  54.               Try
  55.                 locName = rm.GetString(lid)
  56.               Catch
  57.               End Try
  58.             End If
  59.             ' Add the information to our data structures
  60.             globCmds.Add(globName)
  61.             locCmds.Add(locName)
  62.             If cma.GroupName IsNot Nothing AndAlso _
  63.               Not groups.Contains(cma.GroupName) Then
  64.               groups.Add(cma.GroupName)
  65.             End If
  66.           End If
  67.         Next
  68.       Next
  69.     Next
  70.   Next
  71.   ' Let's register the application to load on demand (12)
  72.   ' if it contains commands, otherwise we will have it
  73.   ' load on AutoCAD startup (2)
  74.   Dim flags As Integer = (If(globCmds.Count > 0, 12, 2))
  75.   ' By default let's create the commands in HKCU
  76.   ' (pass false if we want to create in HKLM)
  77.   CreateDemandLoadingEntries(name, path, globCmds, locCmds, _
  78.     groups, flags, True)
  79.     End Sub
  80.     Public Shared Sub UnregisterForDemandLoading()
  81.       RemoveDemandLoadingEntries(True)
  82.     End Sub
  83.     ' Helper functions
  84.     Private Shared Sub CreateDemandLoadingEntries( _
  85.       ByVal name As String, ByVal path As String, _
  86.       ByVal globCmds As List(Of String), _
  87.       ByVal locCmds As List(Of String), _
  88.       ByVal groups As List(Of String), _
  89.       ByVal flags As Integer, _
  90.       ByVal currentUser As Boolean)
  91.       ' Choose a Registry hive based on the function input
  92.       Dim hive As RegistryKey = _
  93.         If(currentUser,Registry.CurrentUser,Registry.LocalMachine)
  94.       ' Open the main AutoCAD (or vertical) and "Applications" keys
  95.       Dim ack As RegistryKey = _
  96.         hive.OpenSubKey( _
  97.           HostApplicationServices.Current.RegistryProductRootKey)
  98.       Dim appk As RegistryKey = ack.OpenSubKey("Applications", True)
  99.       ' Already registered? Just return
  100.       Dim subKeys As String() = appk.GetSubKeyNames()
  101.       For Each subKey As String In subKeys
  102.         If subKey.Equals(name) Then
  103.           appk.Close()
  104.           Exit Sub
  105.         End If
  106.       Next
  107.       ' Create the our application's root key and its values
  108.       Dim rk As RegistryKey = appk.CreateSubKey(name)
  109.       rk.SetValue("DESCRIPTION", name, RegistryValueKind.[String])
  110.       rk.SetValue("LOADCTRLS", flags, RegistryValueKind.DWord)
  111.       rk.SetValue("LOADER", path, RegistryValueKind.[String])
  112.       rk.SetValue("MANAGED", 1, RegistryValueKind.DWord)
  113.       ' Create a subkey if there are any commands...
  114.       If (globCmds.Count = locCmds.Count) _
  115.         AndAlso globCmds.Count > 0 Then
  116.         Dim ck As RegistryKey = rk.CreateSubKey("Commands")
  117.         For i As Integer = 0 To globCmds.Count - 1
  118.           ck.SetValue(globCmds(i), locCmds(i), _
  119.             RegistryValueKind.[String])
  120.         Next
  121.       End If
  122.       ' And the command groups, if there are any
  123.       If groups.Count > 0 Then
  124.         Dim gk As RegistryKey = rk.CreateSubKey("Groups")
  125.         For Each grpName As String In groups
  126.           gk.SetValue(grpName, grpName, _
  127.             RegistryValueKind.[String])
  128.         Next
  129.       End If
  130.       appk.Close()
  131.     End Sub
  132.     Private Shared Sub RemoveDemandLoadingEntries( _
  133.       ByVal currentUser As Boolean)
  134.       Try
  135.         ' Choose a Registry hive based on the function input
  136.         Dim hive As RegistryKey = _
  137.           If(currentUser,Registry.CurrentUser,Registry.LocalMachine)
  138.         ' Open the main AutoCAD (or vertical) and "Applications" keys
  139.         Dim ack As RegistryKey = _
  140.           hive.OpenSubKey( _
  141.             HostApplicationServices.Current.RegistryProductRootKey)
  142.         Dim appk As RegistryKey = _
  143.           ack.OpenSubKey("Applications", True)
  144.         ' Delete the key with the same name as this assembly
  145.         appk.DeleteSubKeyTree( _
  146.           Assembly.GetExecutingAssembly().GetName().Name)
  147.         appk.Close()
  148.       Catch
  149.       End Try
  150.     End Sub
  151.   End Class
  152. End Namespace
That’s really all there is to it. If you have any feedback regarding the behaviour of the tool, please do send us an email.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2010-2-3 22:30 | 显示全部楼层
二、加强版本的截图
http://through-the-interface.typepad.com/through_the_interface/2009/11/novembers-plugin-of-the-month-screenshot.html
November 16, 2009
Updated version of Screenshot now available
We’ve had a few reports of issues with the Screenshot “Plugin of the Month”. They fall into two main categories:
Attempting to NETLOAD the application DLL from a network share
Within the ReadMe for each of the plugins we’ve documented that each application’s DLL module should be copied to the local file system – preferably inside the AutoCAD Program Files folder – before being loaded by NETLOAD. We recommend this because it essentially stops users from hitting a whole category of .NET Framework-related problems when loading and running the plugins.
If you didn’t heed this advice then you’d probably find that, as soon as the SCREENSHOT command was launched, you received a message such as “FATAL ERROR: Unsupported version of Windows Presentation Foundation.”
Now I don’t have an exhaustive list of reasons it’s best to place .NET DLLs in the AutoCAD root program folder, but my understanding/belief is that it’s down to two main ones:
1. Security
The .NET Framework implements security for different zones, and – up until the .NET Framework 3.5 SP1 – the default security setting for the “Local Intranet” (which affects applications being loaded from network shares) was “Medium Trust”. This level of trust means:
Programs might not be able to access most protected resources such as the registry or security policy settings, or access your local file system without user interaction. Programs will not be able to connect back to their site of origin, resolve domain names, and use all windowing resources.
Our “Plugin of the Month” applications target lower versions of the .NET Framework (we want to avoid forcing people to use the latest version of the framework and want the applications to run – where possible – at least as far back as AutoCAD 2007), and so their default level of trust will make running from a network share a problem.
Now it’s possible to use the Control Panel to configure earlier versions of the .NET Framework to be more tolerant of network-resident DLLs (you can change the trust level for the local intranet zone to be higher), but it’s still not something I’d recommend: I’m pretty sure this is only one aspect of the situation, and it would be dangerous to assume it’s all that’s needed.
2. Assembly Loading
The other main reason for putting modules in AutoCAD’s root program folder is related to the loading of .NET assemblies into AutoCAD’s AppDomain (which is basically what the NETLOAD command does, and this choice of architecture is why there’s no NETUNLOAD command).
While NETLOAD uses Assembly.LoadFrom() to load in .NET assembly DLLs – which does allow you to specify a path other than the current folder – there does appear to be some fragility overall with the location of assemblies and how they reference each other. It’s safest to place assemblies at a location beneath the calling executable (i.e. AutoCAD).
Capturing images with the “force foreground to black” option results in a completely black image
This one is definitely down to me. A big thank you to Harry Kortekaas for very diligently helping me identify the problem.
It actually came down to some poor application logic on my side: I was forgetting to check the “use white background” flag at the right place, and had also inverted the test for being in modelspace vs. paperspace. By chance – in many situations – the white background was picked up correctly from the paperspace, and so the issue wasn’t easily reproducible.
Anyway, a fix has now been integrated into version 1.0.2 of the application, which is now downloadable via Autodesk Labs. I’ve also included an updated version of the code below.

A third type of issue has been reported, but I haven’t yet been able to determine the cause: on one system (meaning: one reported, so far) the file selection dialog and the dynamic input graphics are not being refreshed away in time for the capture to take place.
This is similar to something we’ve been aware of from early on - the fact that the call to Editor.GetFileNameForSave() returns control to the application before the Operating System has had the chance to refresh the screen – so I built a delay (a call to System.Threading.Thread.Sleep()) into the original application to wait for a second (we started at 0.1s and worked our way upwards) which seemed to address it for all the systems upon which the application was tested.
At first it seemed – during the diagnosis of this particular issue – that this delay needed increasing, and so I added it to the per-user application settings (with the default value of 1.0, i.e. a second). This is now configurable via a new command – CONFIGSS – the logic being to keep rarely-used configuration options apart from the common ones accessible via the SCREENSHOT command. In this particular situation, though, no amount of delay seemed to help.
The same person reported an issue with the input box and temporary dimensions displayed during dynamic input not being repainted away on this particular system when the image is placed on the clipboard, so I’ve also introduced a configurable delay there, too. This is also set using CONFIGSS – the default is currently 0 seconds, to replicate the previous behaviour.
I suspect that both issues are actually down to a configuration problem (we’ll hopefully see whether my suspicion is valid), but I’ve left the capability to configure them in the application, in any case. People may also choose to reduce the current delay using CONFIGSS, as one second was on the high end: 0.3 seconds should be enough for the majority of systems. Just think of all the time you can get back!   :-)
If anyone else out there has seen something similar to this with the Screenshot application, please do let us know.
The CONFIGSS command has a third setting exposed (although it’s the first one it prompts for), and this was also introduced after receiving feedback from the same source: it’s sometimes preferred to have a boundary zone or buffer around the extents chosen via the “Objects” option, so that the selection isn’t so tight-fitting around the objects. The application now has a default of 10% of the screen extents’ width or height (whichever is larger) that will get added automatically. To go back to the old behaviour you can configure this percentage to zero using CONFIGSS.
Here’s the updated C# code: the compiled version of which (again, version 1.0.2) is now available:
  1. // (C) Copyright 2009 by Autodesk, Inc.
  2. //
  3. // Permission to use, copy, modify, and distribute this software in
  4. // object code form for any purpose and without fee is hereby granted,
  5. // provided that the above copyright notice appears in all copies and
  6. // that both that copyright notice and the limited warranty and
  7. // restricted rights notice below appear in all supporting
  8. // documentation.
  9. //
  10. // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
  11. // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
  12. // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
  13. // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
  14. // UNINTERRUPTED OR ERROR FREE.
  15. //
  16. // Use, duplication, or disclosure by the U.S. Government is subject to
  17. // restrictions set forth in FAR 52.227-19 (Commercial Computer
  18. // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
  19. // (Rights in Technical Data and Computer Software), as applicable.
  20. //
  21. using Autodesk.AutoCAD.ApplicationServices;
  22. using Autodesk.AutoCAD.DatabaseServices;
  23. using Autodesk.AutoCAD.EditorInput;
  24. using Autodesk.AutoCAD.Geometry;
  25. using Autodesk.AutoCAD.GraphicsInterface;
  26. using Autodesk.AutoCAD.GraphicsSystem;
  27. using Autodesk.AutoCAD.Runtime;
  28. using Autodesk.AutoCAD.Colors;
  29. using System.Drawing.Imaging;
  30. using System.Drawing.Printing;
  31. using System.Drawing.Drawing2D;
  32. using System.Drawing;
  33. using System.Runtime.InteropServices;
  34. using System.Collections;
  35. using System.Configuration;
  36. using System;
  37. using DemandLoading;
  38. namespace Screenshot
  39. {
  40.   public class ScreenshotApplication : IExtensionApplication
  41.   {
  42.     // Define a class for our custom data
  43.     public class AppData : ApplicationSettingsBase
  44.     {
  45.       [UserScopedSetting()]
  46.       [DefaultSettingValue("true")]
  47.       public bool Clipboard
  48.       {
  49.         get { return ((bool)this["Clipboard"]); }
  50.         set { this["Clipboard"] = (bool)value; }
  51.       }
  52.       [UserScopedSetting()]
  53.       [DefaultSettingValue("false")]
  54.       public bool Print
  55.       {
  56.         get { return ((bool)this["Print"]); }
  57.         set { this["Print"] = (bool)value; }
  58.       }
  59.       [UserScopedSetting()]
  60.       [DefaultSettingValue("false")]
  61.       public bool WhiteBackground
  62.       {
  63.         get { return ((bool)this["WhiteBackground"]); }
  64.         set { this["WhiteBackground"] = (bool)value; }
  65.       }
  66.       [UserScopedSetting()]
  67.       [DefaultSettingValue("false")]
  68.       public bool BlackForeground
  69.       {
  70.         get { return ((bool)this["BlackForeground"]); }
  71.         set { this["BlackForeground"] = (bool)value; }
  72.       }
  73.       [UserScopedSetting()]
  74.       [DefaultSettingValue("false")]
  75.       public bool Grayscale
  76.       {
  77.         get { return ((bool)this["Grayscale"]); }
  78.         set { this["Grayscale"] = (bool)value; }
  79.       }
  80.       [UserScopedSetting()]
  81.       [DefaultSettingValue("0.1")]
  82.       public double ExtentsScale
  83.       {
  84.         get { return ((double)this["ExtentsScale"]); }
  85.         set { this["ExtentsScale"] = (double)value; }
  86.       }
  87.       [UserScopedSetting()]
  88.       [DefaultSettingValue("1.0")]
  89.       public double FileCaptureDelay
  90.       {
  91.         get { return ((double)this["FileCaptureDelay"]); }
  92.         set { this["FileCaptureDelay"] = (double)value; }
  93.       }
  94.       [UserScopedSetting()]
  95.       [DefaultSettingValue("0.0")]
  96.       public double ClipboardCaptureDelay
  97.       {
  98.         get { return ((double)this["ClipboardCaptureDelay"]); }
  99.         set { this["ClipboardCaptureDelay"] = (double)value; }
  100.       }
  101.     }
  102.     // A struct for communicating colours to/from AutoCAD
  103.     public struct AcColorSettings
  104.     {
  105.       public UInt32 dwGfxModelBkColor;
  106.       public UInt32 dwGfxLayoutBkColor;
  107.       public UInt32 dwParallelBkColor;
  108.       public UInt32 dwBEditBkColor;
  109.       public UInt32 dwCmdLineBkColor;
  110.       public UInt32 dwPlotPrevBkColor;
  111.       public UInt32 dwSkyGradientZenithColor;
  112.       public UInt32 dwSkyGradientHorizonColor;
  113.       public UInt32 dwGroundGradientOriginColor;
  114.       public UInt32 dwGroundGradientHorizonColor;
  115.       public UInt32 dwEarthGradientAzimuthColor;
  116.       public UInt32 dwEarthGradientHorizonColor;
  117.       public UInt32 dwModelCrossHairColor;
  118.       public UInt32 dwLayoutCrossHairColor;
  119.       public UInt32 dwParallelCrossHairColor;
  120.       public UInt32 dwPerspectiveCrossHairColor;
  121.       public UInt32 dwBEditCrossHairColor;
  122.       public UInt32 dwParallelGridMajorLines;
  123.       public UInt32 dwPerspectiveGridMajorLines;
  124.       public UInt32 dwParallelGridMinorLines;
  125.       public UInt32 dwPerspectiveGridMinorLines;
  126.       public UInt32 dwParallelGridAxisLines;
  127.       public UInt32 dwPerspectiveGridAxisLines;
  128.       public UInt32 dwTextForeColor;
  129.       public UInt32 dwTextBkColor;
  130.       public UInt32 dwCmdLineForeColor;
  131.       public UInt32 dwAutoTrackingVecColor;
  132.       public UInt32 dwLayoutATrackVecColor;
  133.       public UInt32 dwParallelATrackVecColor;
  134.       public UInt32 dwPerspectiveATrackVecColor;
  135.       public UInt32 dwBEditATrackVecColor;
  136.       public UInt32 dwModelASnapMarkerColor;
  137.       public UInt32 dwLayoutASnapMarkerColor;
  138.       public UInt32 dwParallelASnapMarkerColor;
  139.       public UInt32 dwPerspectiveASnapMarkerColor;
  140.       public UInt32 dwBEditASnapMarkerColor;
  141.       public UInt32 dwModelDftingTooltipColor;
  142.       public UInt32 dwLayoutDftingTooltipColor;
  143.       public UInt32 dwParallelDftingTooltipColor;
  144.       public UInt32 dwPerspectiveDftingTooltipColor;
  145.       public UInt32 dwBEditDftingTooltipColor;
  146.       public UInt32 dwModelDftingTooltipBkColor;
  147.       public UInt32 dwLayoutDftingTooltipBkColor;
  148.       public UInt32 dwParallelDftingTooltipBkColor;
  149.       public UInt32 dwPerspectiveDftingTooltipBkColor;
  150.       public UInt32 dwBEditDftingTooltipBkColor;
  151.       public UInt32 dwModelLightGlyphs;
  152.       public UInt32 dwLayoutLightGlyphs;
  153.       public UInt32 dwParallelLightGlyphs;
  154.       public UInt32 dwPerspectiveLightGlyphs;
  155.       public UInt32 dwBEditLightGlyphs;
  156.       public UInt32 dwModelLightHotspot;
  157.       public UInt32 dwLayoutLightHotspot;
  158.       public UInt32 dwParallelLightHotspot;
  159.       public UInt32 dwPerspectiveLightHotspot;
  160.       public UInt32 dwBEditLightHotspot;
  161.       public UInt32 dwModelLightFalloff;
  162.       public UInt32 dwLayoutLightFalloff;
  163.       public UInt32 dwParallelLightFalloff;
  164.       public UInt32 dwPerspectiveLightFalloff;
  165.       public UInt32 dwBEditLightFalloff;
  166.       public UInt32 dwModelLightStartLimit;
  167.       public UInt32 dwLayoutLightStartLimit;
  168.       public UInt32 dwParallelLightStartLimit;
  169.       public UInt32 dwPerspectiveLightStartLimit;
  170.       public UInt32 dwBEditLightStartLimit;
  171.       public UInt32 dwModelLightEndLimit;
  172.       public UInt32 dwLayoutLightEndLimit;
  173.       public UInt32 dwParallelLightEndLimit;
  174.       public UInt32 dwPerspectiveLightEndLimit;
  175.       public UInt32 dwBEditLightEndLimit;
  176.       public UInt32 dwModelCameraGlyphs;
  177.       public UInt32 dwLayoutCameraGlyphs;
  178.       public UInt32 dwParallelCameraGlyphs;
  179.       public UInt32 dwPerspectiveCameraGlyphs;
  180.       public UInt32 dwModelCameraFrustrum;
  181.       public UInt32 dwLayoutCameraFrustrum;
  182.       public UInt32 dwParallelCameraFrustrum;
  183.       public UInt32 dwPerspectiveCameraFrustrum;
  184.       public UInt32 dwModelCameraClipping;
  185.       public UInt32 dwLayoutCameraClipping;
  186.       public UInt32 dwParallelCameraClipping;
  187.       public UInt32 dwPerspectiveCameraClipping;
  188.       public int nModelCrosshairUseTintXYZ;
  189.       public int nLayoutCrosshairUseTintXYZ;
  190.       public int nParallelCrosshairUseTintXYZ;
  191.       public int nPerspectiveCrosshairUseTintXYZ;
  192.       public int nBEditCrossHairUseTintXYZ;
  193.       public int nModelATrackVecUseTintXYZ;
  194.       public int nLayoutATrackVecUseTintXYZ;
  195.       public int nParallelATrackVecUseTintXYZ;
  196.       public int nPerspectiveATrackVecUseTintXYZ;
  197.       public int nBEditATrackVecUseTintXYZ;
  198.       public int nModelDftingTooltipBkUseTintXYZ;
  199.       public int nLayoutDftingTooltipBkUseTintXYZ;
  200.       public int nParallelDftingTooltipBkUseTintXYZ;
  201.       public int nPerspectiveDftingTooltipBkUseTintXYZ;
  202.       public int nBEditDftingTooltipBkUseTintXYZ;
  203.       public int nParallelGridMajorLineTintXYZ;
  204.       public int nPerspectiveGridMajorLineTintXYZ;
  205.       public int nParallelGridMinorLineTintXYZ;
  206.       public int nPerspectiveGridMinorLineTintXYZ;
  207.       public int nParallelGridAxisLineTintXYZ;
  208.       public int nPerspectiveGridAxisLineTintXYZ;
  209.     };
  210.     // For the coordinate tranformation we need...  
  211.     // A Win32 function:
  212.     [DllImport("user32.dll")]
  213.     static extern bool ClientToScreen(IntPtr hWnd, ref Point pt);
  214.     // And to access the colours in AutoCAD, we need ObjectARX...
  215.     [DllImport("acad.exe",
  216.     CallingConvention = CallingConvention.Cdecl,
  217.     EntryPoint = "?acedGetCurrentColors@@YAHPAUAcColorSettings@@@Z"
  218.     )]
  219.     static extern bool acedGetCurrentColors32(
  220.       out AcColorSettings colorSettings
  221.     );
  222.     [DllImport("acad.exe",
  223.     CallingConvention = CallingConvention.Cdecl,
  224.     EntryPoint = "?acedSetCurrentColors@@YAHPAUAcColorSettings@@@Z"
  225.     )]
  226.     static extern bool acedSetCurrentColors32(
  227.       ref AcColorSettings colorSettings
  228.     );
  229.     // 64-bit versions of these functions...
  230.     [DllImport("acad.exe",
  231.     CallingConvention = CallingConvention.Cdecl,
  232.     EntryPoint = "?acedGetCurrentColors@@YAHPEAUAcColorSettings@@@Z"
  233.     )]
  234.     static extern bool acedGetCurrentColors64(
  235.       out AcColorSettings colorSettings
  236.     );
  237.     [DllImport("acad.exe",
  238.     CallingConvention = CallingConvention.Cdecl,
  239.     EntryPoint = "?acedSetCurrentColors@@YAHPEAUAcColorSettings@@@Z"
  240.     )]
  241.     static extern bool acedSetCurrentColors64(
  242.       ref AcColorSettings colorSettings
  243.     );
  244.     // Helper functions that call automatically to 32- or 64-bit
  245.     // versions, as appropriate
  246.     static bool acedGetCurrentColors(
  247.       out AcColorSettings colorSettings
  248.     )
  249.     {
  250.       if (IntPtr.Size > 4)
  251.         return acedGetCurrentColors64(out colorSettings);
  252.       else
  253.         return acedGetCurrentColors32(out colorSettings);
  254.     }
  255.     static bool acedSetCurrentColors(
  256.       ref AcColorSettings colorSettings
  257.     )
  258.     {
  259.       if (IntPtr.Size > 4)
  260.         return acedSetCurrentColors64(ref colorSettings);
  261.       else
  262.         return acedSetCurrentColors32(ref colorSettings);
  263.     }
  264.     // IExtensionApplication protocol
  265.     public void Initialize()
  266.     {
  267.       try
  268.       {
  269.         RegistryUpdate.RegisterForDemandLoading();
  270.       }
  271.       catch
  272.       { }
  273.     }
  274.     public void Terminate()
  275.     {
  276.     }
  277.     [CommandMethod("ADNPLUGINS", "REMOVESS", CommandFlags.Modal)]
  278.     static public void RemoveScreenshot()
  279.     {
  280.       RegistryUpdate.UnregisterForDemandLoading();
  281.     }
  282.     [CommandMethod("ADNPLUGINS", "CONFIGSS", CommandFlags.Modal)]
  283.     static public void ConfigureScreenshot()
  284.     {
  285.       // An additional command for some "advanced" configuration
  286.       // options
  287.       Document doc =
  288.         Application.DocumentManager.MdiActiveDocument;
  289.       Editor ed = doc.Editor;
  290.       // Retrieve our application settings (or create new ones)
  291.       AppData ad = new AppData();
  292.       ad.Reload();
  293.       if (ad != null)
  294.       {
  295.         // Ask the user for the percentage increase to apply to
  296.         // the extents determined by the Objects option
  297.         PromptIntegerOptions pio =
  298.           new PromptIntegerOptions(
  299.             "\nPercentage increase when capturing " +
  300.             "object extents: "
  301.           );
  302.         pio.DefaultValue = (int)(ad.ExtentsScale * 100);
  303.         pio.LowerLimit = 0;
  304.         pio.UpperLimit = 100;
  305.         pio.UseDefaultValue = true;
  306.         PromptIntegerResult pir = ed.GetInteger(pio);
  307.         if (pir.Status != PromptStatus.OK)
  308.           return;
  309.         ad.ExtentsScale = pir.Value * 0.01;
  310.         // Ask the use for the delay to apply after a file
  311.         // has been selected, to allow the OS to redraw their
  312.         // screen graphics
  313.         PromptDoubleOptions pdo =
  314.           new PromptDoubleOptions(
  315.             "\nDelay in seconds to allow " +
  316.             "repaint after file selection: "
  317.           );
  318.         pdo.DefaultValue = ad.FileCaptureDelay;
  319.         pdo.AllowNegative = false;
  320.         pdo.UseDefaultValue = true;
  321.         PromptDoubleResult pdr = ed.GetDouble(pdo);
  322.         if (pdr.Status != PromptStatus.OK)
  323.           return;
  324.         ad.FileCaptureDelay = pdr.Value;
  325.         // Ask the user for the delay to apply before a
  326.         // clipboard capture to allow any dynamic
  327.         // input graphics to be undrawn
  328.         pdo.Message =
  329.           "\nDelay in seconds to allow " +
  330.           "repaint before clipboard selection: ";
  331.         pdo.DefaultValue = ad.ClipboardCaptureDelay;
  332.         pdr = ed.GetDouble(pdo);
  333.         if (pdr.Status != PromptStatus.OK)
  334.           return;
  335.         ad.ClipboardCaptureDelay = pdr.Value;
  336.         ad.Save();
  337.       }
  338.     }
  339.     // Command to capture the main and active drawing windows
  340.     // or a user-selected portion of a drawing
  341.     [CommandMethod("ADNPLUGINS", "SCREENSHOT", CommandFlags.Modal)]
  342.     static public void CaptureScreenShot()
  343.     {
  344.       Document doc =
  345.         Application.DocumentManager.MdiActiveDocument;
  346.       Editor ed = doc.Editor;
  347.       // Retrieve our application settings (or create new ones)
  348.       AppData ad = new AppData();
  349.       ad.Reload();
  350.       if (ad != null)
  351.       {
  352.         string filename = "";
  353.         bool settingschosen;
  354.         PromptPointResult ppr;
  355.         do
  356.         {
  357.           settingschosen = false;
  358.           // Ask the user for the screen window to capture
  359.           PrintSettings(ed, ad);
  360.           PromptPointOptions ppo =
  361.             new PromptPointOptions(
  362.               "\nSelect first point of capture window or " +
  363.               "[Document/Application/Objects/Settings]: ",
  364.               "Document Application Objects Settings"
  365.             );
  366.           // Get the first point of the capture window,
  367.           // or a keyword
  368.           ppr = ed.GetPoint(ppo);
  369.           if (ppr.Status == PromptStatus.Keyword)
  370.           {
  371.             if (ppr.StringResult == "Document")
  372.             {
  373.               // Capture the active document window
  374.               filename = PauseOrFilename(ed, ad);
  375.               ScreenShotToFile(
  376.                 Application.DocumentManager.
  377.                   MdiActiveDocument.Window,
  378.                 30, 26, 10, 10,
  379.                 filename,
  380.                 ad
  381.               );
  382.             }
  383.             else if (ppr.StringResult == "Application")
  384.             {
  385.               // Capture the entire application window
  386.               filename = PauseOrFilename(ed, ad);
  387.               ScreenShotToFile(
  388.                 Application.MainWindow,
  389.                 0, 0, 0, 0,
  390.                 filename,
  391.                 ad
  392.               );
  393.             }
  394.             else if (ppr.StringResult == "Objects")
  395.             {
  396.               // Ask the user to select a number of entities
  397.               PromptSelectionResult psr =
  398.                 ed.GetSelection();
  399.               if (psr.Status == PromptStatus.OK)
  400.               {
  401.                 // Regenerate to clear any selection highlighting
  402.                 ed.WriteMessage("\n");
  403.                 ed.Regen();
  404.                 // Generate screen coordinate points based on the
  405.                 // drawing points selected
  406.                 // First we get the viewport number
  407.                 short vp =
  408.                   (short)Application.GetSystemVariable("CVPORT");
  409.                 // Then the handle to the current drawing window
  410.                 IntPtr hWnd = doc.Window.Handle;
  411.                 // Get the screen extents of the selected entities
  412.                 Point pt1, pt2;
  413.                 GetExtentsOfSelection(
  414.                   ed, doc, hWnd, vp, psr.Value, out pt1, out pt2
  415.                 );
  416.                 ApplyScaleToExtents(ad.ExtentsScale,ref pt1,ref pt2);
  417.                 // Now save this portion of our screen as a raster
  418.                 // image
  419.                 filename = PauseOrFilename(ed, ad);
  420.                 ScreenShotToFile(pt1, pt2, filename, ad);
  421.               }
  422.             }
  423.             else if (ppr.StringResult == "Settings")
  424.             {
  425.               if (GetSettings(ed, ad))
  426.                 ad.Save();
  427.               settingschosen = true;
  428.             }
  429.           }
  430.         }
  431.         while (settingschosen); // Loop if settings were modified
  432.         if (ppr.Status == PromptStatus.OK)
  433.         {
  434.           // Now we're ready to select the second point
  435.           Point3d first = ppr.Value;
  436.           ppr =
  437.             ed.GetCorner(
  438.               "\nSelect second point of capture window: ",
  439.               first
  440.             );
  441.           if (ppr.Status != PromptStatus.OK)
  442.             return;
  443.           Point3d second = ppr.Value;
  444.           // Generate screen coordinate points based on the
  445.           // drawing points selected
  446.           Point pt1, pt2;
  447.           // First we get the viewport number
  448.           short vp =
  449.             (short)Application.GetSystemVariable("CVPORT");
  450.           // Then the handle to the current drawing window
  451.           IntPtr hWnd = doc.Window.Handle;
  452.           // Now calculate the selected corners in screen coordinates
  453.           pt1 = ScreenFromDrawingPoint(ed, hWnd, first, vp, true);
  454.           pt2 = ScreenFromDrawingPoint(ed, hWnd, second, vp, true);
  455.           // Now save this portion of our screen as a raster image
  456.           filename = PauseOrFilename(ed, ad);
  457.           ScreenShotToFile(pt1, pt2, filename, ad);
  458.         }
  459.       }
  460.     }
  461.     // If using the clipboard let's introduce a pause to allow
  462.     // any dynamic input graphics to be refreshed away.
  463.     // Otherwise get the filename (which will have its own delay)
  464.     private static string PauseOrFilename(Editor ed, AppData ad)
  465.     {
  466.       if (ad.Clipboard)
  467.       {
  468.         ed.UpdateScreen();
  469.         System.Threading.Thread.Sleep(
  470.           (int)(1000 * ad.ClipboardCaptureDelay)
  471.         );
  472.         return "";
  473.       }
  474.       else
  475.         return GetFileName(ed, ad);
  476.     }
  477.     // Iterate through a selection-set and get the overall extents
  478.     // of the various objects relative to the screen
  479.     // (this is imperfect: our extents in WCS may not translate to
  480.     // the extents on the screen. A more thorough approach would be
  481.     // to get a number of points from an object and check each)
  482.     private static void GetExtentsOfSelection(
  483.       Editor ed,
  484.       Document doc,
  485.       IntPtr hWnd,
  486.       short vp,
  487.       SelectionSet ss,
  488.       out Point min,
  489.       out Point max
  490.     )
  491.     {
  492.       // Create minimum and maximum points for the "on screen"
  493.       // extents of our objects
  494.       min = new Point();
  495.       max = new Point();
  496.       // Know which is the first pass through
  497.       bool first = true;
  498.       // Some variables to store transformation results
  499.       Point pt1 = new Point(), pt2 = new Point();
  500.       Transaction tr =
  501.         doc.TransactionManager.StartTransaction();
  502.       using (tr)
  503.       {
  504.         foreach (SelectedObject so in ss)
  505.         {
  506.           DBObject obj =
  507.             tr.GetObject(so.ObjectId, OpenMode.ForRead);
  508.           Entity ent = obj as Entity;
  509.           if (ent != null)
  510.           {
  511.             // Get the WCS extents of each object
  512.             Extents3d ext = ent.GeometricExtents;
  513.             // Calculate the extent corners in screen coordinates
  514.             // (this may not be the true screen extents, but we'll
  515.             // hope it's good enough)
  516.             pt1 =
  517.               ScreenFromDrawingPoint(
  518.                 ed, hWnd, ext.MinPoint, vp, false
  519.               );
  520.             pt2 =
  521.               ScreenFromDrawingPoint(
  522.                 ed, hWnd, ext.MaxPoint, vp, false
  523.               );
  524.             // The points may not be ordered, so get the min and max
  525.             // values for both X and Y from both points
  526.             int minX = Math.Min(pt1.X, pt2.X);
  527.             int minY = Math.Min(pt1.Y, pt2.Y);
  528.             int maxX = Math.Max(pt1.X, pt2.X);
  529.             int maxY = Math.Max(pt1.Y, pt2.Y);
  530.             // On the first run through, just get the points
  531.             if (first)
  532.             {
  533.               min = new Point(minX, minY);
  534.               max = new Point(maxX, maxY);
  535.               first = false;
  536.             }
  537.             else
  538.             {
  539.               // On subsequent runs through, we need to compare
  540.               if (minX < min.X) min.X = minX;
  541.               if (minY < min.Y) min.Y = minY;
  542.               if (maxX > max.X) max.X = maxX;
  543.               if (maxY > max.Y) max.Y = maxY;
  544.             }
  545.           }
  546.         }
  547.         tr.Commit();
  548.       }
  549.     }
  550.     // Apply a scale to the supplied extents
  551.     // (provided as a potion of the extents, i.e. 10% == 0.1)
  552.     private static void ApplyScaleToExtents(
  553.       double factor,
  554.       ref Point pt1,
  555.       ref Point pt2
  556.     )
  557.     {
  558.       // Get the width and height of the selected screen area
  559.       int width = Math.Abs(pt2.X - pt1.X);
  560.       int height = Math.Abs(pt2.Y - pt1.Y);
  561.       // Decide what the (uniform) border should be: use
  562.       // a portion of either the width or the height,
  563.       // whichever's larger
  564.       int border =
  565.         (int)(factor * (width > height ? width : height));
  566.       // Adjust the two screen points by this border amount
  567.       if (pt1.X < pt2.X)
  568.       {
  569.         pt1.X -= border;
  570.         pt2.X += border;
  571.       }
  572.       else
  573.       {
  574.         pt1.X += border;
  575.         pt2.X -= border;
  576.       }
  577.       if (pt1.Y < pt2.Y)
  578.       {
  579.         pt1.Y -= border;
  580.         pt2.Y += border;
  581.       }
  582.       else
  583.       {
  584.         pt1.Y += border;
  585.         pt2.Y -= border;
  586.       }
  587.     }
  588.     // Print the current application settings to the command-line
  589.     private static void PrintSettings(Editor ed, AppData ad)
  590.     {
  591.       ed.WriteMessage(
  592.         "\nCurrent settings: Output={0}, Print={1}, " +
  593.         "Background={2}, Foreground={3}, Grayscale={4}",
  594.         ad.Clipboard ? "Clipboard" : "File",
  595.         ad.Print ? "Yes" : "No",
  596.         ad.WhiteBackground ? "ForceToWhite" : "Normal",
  597.         ad.BlackForeground ? "ForceToBlack" : "Normal",
  598.         ad.Grayscale ? "On" : "Off"
  599.       );
  600.     }
  601.     // Ask the user to modify the application settings
  602.     private static bool GetSettings(Editor ed, AppData ad)
  603.     {
  604.       // At our top-level settings prompt, make the default
  605.       // to exit back up
  606.       PromptKeywordOptions pko =
  607.         new PromptKeywordOptions(
  608.           "\nSetting to change " +
  609.           "[Output/Print/Background/Foreground/Grayscale/Exit]: ",
  610.           "Output Print Background Foreground Grayscale Exit"
  611.         );
  612.       pko.Keywords.Default = "Exit";
  613.       PromptResult pr;
  614.       bool settingschanged = false;
  615.       do
  616.       {
  617.         // Start by printing the current settings
  618.         PrintSettings(ed, ad);
  619.         pr = ed.GetKeywords(pko);
  620.         if (pr.Status == PromptStatus.OK)
  621.         {
  622.           if (pr.StringResult == "Output")
  623.           {
  624.             // If Output is selected, ask whether to put the
  625.             // image on the clipboard or save to file
  626.             PromptKeywordOptions pko2 =
  627.               new PromptKeywordOptions(
  628.                 "\nSave to file or place on the clipboard " +
  629.                 "[File/Clipboard]: ",
  630.                 "File Clipboard"
  631.               );
  632.             // The default depends on our current settings
  633.             pko2.Keywords.Default =
  634.               (ad.Clipboard ? "Clipboard" : "File");
  635.             PromptResult pr2 = ed.GetKeywords(pko2);
  636.             if (pr2.Status == PromptStatus.OK)
  637.             {
  638.               // Change the settings, as needed
  639.               bool clipboard =
  640.                 (pr2.StringResult == "Clipboard");
  641.               if (ad.Clipboard != clipboard)
  642.               {
  643.                 ad.Clipboard = clipboard;
  644.                 settingschanged = true;
  645.               }
  646.             }
  647.           }
  648.           else if (pr.StringResult == "Print")
  649.           {
  650.             // If Print is different, ask whether to
  651.             // send the image to the printer
  652.             bool different =
  653.               GetYesOrNo(
  654.                 ed,
  655.                 "\nSend image to printer once captured",
  656.                 ad.Print
  657.               );
  658.             if (different)
  659.             {
  660.               ad.Print = !ad.Print;
  661.               settingschanged = true;
  662.             }
  663.           }
  664.           else if (pr.StringResult == "Background")
  665.           {
  666.             // If Background is different, ask whether to
  667.             // force the background colour to white
  668.             // (we could allow selection of a colour,
  669.             // but that's out of scope, for now)
  670.             bool different =
  671.               GetYesOrNo(
  672.                 ed,
  673.                 "\nForce background color to white",
  674.                 ad.WhiteBackground
  675.               );
  676.             if (different)
  677.             {
  678.               ad.WhiteBackground = !ad.WhiteBackground;
  679.               settingschanged = true;
  680.             }
  681.           }
  682.           else if (pr.StringResult == "Foreground")
  683.           {
  684.             // If Foreground is different, ask whether to
  685.             // force the foreground colour to black
  686.             // (we could allow selection of a colour,
  687.             // but that's out of scope, for now)
  688.             bool different =
  689.               GetYesOrNo(
  690.                 ed,
  691.                 "\nForce foreground color to black",
  692.                 ad.BlackForeground
  693.               );
  694.             if (different)
  695.             {
  696.               ad.BlackForeground = !ad.BlackForeground;
  697.               settingschanged = true;
  698.             }
  699.           }
  700.           else if (pr.StringResult == "Grayscale")
  701.           {
  702.             // If Grayscale is different, ask whether to
  703.             // force the foreground pixels to be gray
  704.             bool different =
  705.               GetYesOrNo(
  706.                 ed,
  707.                 "\nConvert image to grayscale",
  708.                 ad.Grayscale
  709.               );
  710.             if (different)
  711.             {
  712.               ad.Grayscale = !ad.Grayscale;
  713.               settingschanged = true;
  714.             }
  715.           }
  716.         }
  717.       }
  718.       while (
  719.         pr.Status == PromptStatus.OK &&
  720.         pr.StringResult != "Exit"
  721.       );  // Loop until Exit or cancel
  722.       return settingschanged;
  723.     }
  724.     // Ask the user to enter yes or no to a particular question,
  725.     // setting the default option appropriately
  726.     private static bool GetYesOrNo(
  727.       Editor ed,
  728.       string prompt,
  729.       bool defval
  730.     )
  731.     {
  732.       bool changed = false;
  733.       PromptKeywordOptions pko =
  734.         new PromptKeywordOptions(prompt + " [Yes/No]: ", "Yes No");
  735.       // The default depends on our current settings
  736.       pko.Keywords.Default =
  737.         (defval ? "Yes" : "No");
  738.       PromptResult pr = ed.GetKeywords(pko);
  739.       if (pr.Status == PromptStatus.OK)
  740.       {
  741.         // Change the settings, as needed
  742.         bool newval =
  743.           (pr.StringResult == "Yes");
  744.         if (defval != newval)
  745.         {
  746.           changed = true;
  747.         }
  748.       }
  749.       return changed;
  750.     }
  751.     // Ask the user to select a location to save our file to
  752.     private static string GetFileName(Editor ed, AppData ad)
  753.     {
  754.       string filename = "";
  755.       // The entries here will drive the behaviour of the
  756.       // GetFormatForFile() function
  757.       PromptSaveFileOptions pofo =
  758.         new PromptSaveFileOptions(
  759.           "\nSelect image location: "
  760.         );
  761.       pofo.Filter =
  762.         "Bitmap (*.bmp)|*.bmp|" +
  763.         "GIF (*.gif)|*.gif|" +
  764.         "JPEG (*.jpg)|*.jpg|" +
  765.         "PNG (*.png)|*.png|" +
  766.         "TIFF (*.tif)|*.tif";
  767.       // Set the default save location to be the current drawing
  768.       string fn = ed.Document.Database.Filename;
  769.       if (fn.Contains("."))
  770.       {
  771.         int extIdx = fn.LastIndexOf(".");
  772.         if (fn.Substring(extIdx + 1) != "dwt" &&
  773.             fn.Contains("\"))
  774.         {
  775.           int sepIdx = fn.LastIndexOf("\");
  776.           pofo.InitialDirectory =
  777.             fn.Substring(0, sepIdx);
  778.         }
  779.       }
  780.       PromptFileNameResult pfnr =
  781.         ed.GetFileNameForSave(pofo);
  782.       if (pfnr.Status == PromptStatus.OK)
  783.       {
  784.         filename = pfnr.StringResult;
  785.         // If a file was selected, wait for some time to allow
  786.         // the "file already exists" dialog to disappear.
  787.         // The delay is now a configurable option (in seconds)
  788.         ed.UpdateScreen();
  789.         System.Threading.Thread.Sleep(
  790.           (int)(1000 * ad.FileCaptureDelay)
  791.         );
  792.       }
  793.       return filename;
  794.     }
  795.     // Perform our tranformations to get from UCS
  796.     // (or WCS) to screen coordinates
  797.     private static Point ScreenFromDrawingPoint(
  798.       Editor ed,
  799.       IntPtr hWnd,
  800.       Point3d pt,
  801.       short vpNum,
  802.       bool useUcs
  803.     )
  804.     {
  805.       // Transform from UCS to WCS, if needed
  806.       Point3d wcsPt =
  807.         (useUcs ?
  808.           pt.TransformBy(ed.CurrentUserCoordinateSystem)
  809.           : pt
  810.         );
  811.       // Then get the screen coordinates within the client
  812.       // and translate these for the overall screen
  813.       Point res = ed.PointToScreen(wcsPt, vpNum);
  814.       ClientToScreen(hWnd, ref res);
  815.       return res;
  816.     }
  817.     // Save the display of an AutoCAD window as a raster file
  818.     // and/or an image on the clipboard
  819.     private static void ScreenShotToFile(
  820.       Autodesk.AutoCAD.Windows.Window wd,
  821.       int top, int bottom, int left, int right,
  822.       string filename,
  823.       AppData ad
  824.     )
  825.     {
  826.       Point pt = wd.Location;
  827.       Size sz = wd.Size;
  828.       pt.X += left;
  829.       pt.Y += top;
  830.       sz.Height -= top + bottom;
  831.       sz.Width -= left + right;
  832.       SaveScreenPortion(pt, sz, filename, ad);
  833.     }
  834.     // Save a screen window between two corners as a raster file
  835.     // and/or an image on the clipboard
  836.     private static void ScreenShotToFile(
  837.       Point pt1,
  838.       Point pt2,
  839.       string filename,
  840.       AppData ad
  841.     )
  842.     {
  843.       // Create the top left corner from the two corners
  844.       // provided (by taking the min of both X and Y values)
  845.       Point pt =
  846.         new Point(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y));
  847.       // Determine the size by subtracting X & Y values and
  848.       // taking the absolute value of each
  849.       Size sz =
  850.         new Size(Math.Abs(pt1.X - pt2.X), Math.Abs(pt1.Y - pt2.Y));
  851.       SaveScreenPortion(pt, sz, filename, ad);
  852.     }
  853.     // Save a portion of the screen display as a raster file
  854.     // and/or an image on the clipboard
  855.     private static void SaveScreenPortion(
  856.       Point pt,
  857.       Size sz,
  858.       string filename,
  859.       AppData ad
  860.     )
  861.     {
  862.       Document doc =
  863.         Application.DocumentManager.MdiActiveDocument;
  864.       Database db = doc.Database;
  865.       Editor ed = doc.Editor;
  866.       Manager gsm = doc.GraphicsManager;
  867.       Transaction tr =
  868.         db.TransactionManager.StartTransaction();
  869.       using (tr)
  870.       {
  871.         AcColorSettings ocs = new AcColorSettings();
  872.         bool gotSettings = false;
  873.         ObjectId vtrId = ObjectId.Null,
  874.                 sbId = ObjectId.Null;
  875.         bool in3DView = is3D(gsm);
  876.         bool regened = false;
  877.         if (ad.WhiteBackground)
  878.         {
  879.           if (in3DView)
  880.           {
  881.             Set3DBackground(
  882.               ed,
  883.               db,
  884.               tr,
  885.               new EntityColor(255, 255, 255),
  886.               out vtrId,
  887.               out sbId
  888.             );
  889.           }
  890.           else
  891.           {
  892.             // Get the current system colours
  893.             acedGetCurrentColors(out ocs);
  894.             gotSettings = true;
  895.             // Take a copy - we'll leave the original to reset
  896.             // the values later on, once we've finished
  897.             AcColorSettings cs = ocs;
  898.             // Make both background colours white (the 3D
  899.             // background isn't currently being picked up)
  900.             cs.dwGfxModelBkColor = 16777215;
  901.             cs.dwGfxLayoutBkColor = 16777215;
  902.             //cs.dwParallelBkColor = 16777215;
  903.             // Set the modified colours
  904.             acedSetCurrentColors(ref cs);
  905.             ed.WriteMessage("\n");
  906.             ed.Regen();
  907.             regened = true;
  908.           }
  909.           // Update the screen to reflect the changes
  910.           ed.UpdateScreen();
  911.         }
  912.         // Set the bitmap object to the size of the window
  913.         Bitmap bmp =
  914.           new Bitmap(
  915.             sz.Width,
  916.             sz.Height,
  917.             PixelFormat.Format32bppArgb
  918.           );
  919.         using (bmp)
  920.         {
  921.           // Create a graphics object from the bitmap
  922.           using (Graphics gfx = Graphics.FromImage(bmp))
  923.           {
  924.             // Take a screenshot of our window
  925.             gfx.CopyFromScreen(
  926.               pt.X, pt.Y, 0, 0, sz,
  927.               CopyPixelOperation.SourceCopy
  928.             );
  929.             Bitmap processed;
  930.             if (ad.BlackForeground || ad.Grayscale)
  931.             {
  932.               // If we're needing to convert to a grayscale,
  933.               // whether to create a black foreground or a
  934.               // true grayscale image, we need to find the
  935.               // background colour
  936.               System.Drawing.Color col;
  937.               // If we remapped it to white, it's easy
  938.               if (ad.WhiteBackground)
  939.               {
  940.                 col = System.Drawing.Color.White;
  941.               }
  942.               else
  943.               {
  944.                 // If not, then it may be more complicated
  945.                 if (!gotSettings)
  946.                 {
  947.                   acedGetCurrentColors(out ocs);
  948.                   gotSettings = true;
  949.                 }
  950.                 // Get the 3D view background colour, if appropriate
  951.                 if (in3DView)
  952.                 {
  953.                   uint bgcol = ocs.dwParallelBkColor;
  954.                   col =
  955.                     System.Drawing.Color.FromArgb((int)bgcol);
  956.                 }
  957.                 else
  958.                 {
  959.                   // If a 2D view, we need to find out whether in
  960.                   // modelspace or paperspace, and then get the
  961.                   // appropriate colour for its background
  962.                   bool inModelspace =
  963.                     ((short)Application.GetSystemVariable(
  964.                       "TILEMODE"
  965.                     ) == 1);
  966.                   uint bgcol =
  967.                     (inModelspace
  968.                       ? ocs.dwGfxModelBkColor
  969.                       : ocs.dwGfxLayoutBkColor
  970.                     );
  971.                   col =
  972.                     System.Drawing.Color.FromArgb((int)bgcol);
  973.                 }
  974.               }
  975.               // Run the conversion to a grayscale
  976.               processed =
  977.                 ConvertToGrayscale(
  978.                   bmp,
  979.                   col,
  980.                   ad.BlackForeground,
  981.                   System.Drawing.Color.Black
  982.                 );
  983.             }
  984.             else
  985.             {
  986.               processed = bmp;
  987.             }
  988.             // Take a copy of the bitmap for printing
  989.             using (Bitmap toPrint = processed)
  990.             {
  991.               // Save the screenshot to the specified location
  992.               if (!regened)
  993.                 ed.WriteMessage("\n");
  994.               if (filename != null && filename != "")
  995.               {
  996.                 processed.Save(filename, GetFormatForFile(filename));
  997.                 ed.WriteMessage(
  998.                   "Image captured and saved to "{0}".",
  999.                   filename
  1000.                 );
  1001.               }
  1002.               // Copy it to the clipboard
  1003.               if (ad.Clipboard)
  1004.               {
  1005.                 System.Windows.Forms.Clipboard.SetImage(processed);
  1006.                 ed.WriteMessage(
  1007.                   "Image captured to the clipboard."
  1008.                 );
  1009.               }
  1010.               // Send it to a printer
  1011.               if (ad.Print)
  1012.               {
  1013.                 PrintDocument pdoc = new PrintDocument();
  1014.                 pdoc.PrintPage +=
  1015.                   delegate(object sender, PrintPageEventArgs e)
  1016.                   {
  1017.                     int wid = toPrint.Width,
  1018.                         hgt = toPrint.Height;
  1019.                     // Store the ratio between width and height
  1020.                     double ratio = (double)wid / (double)hgt;
  1021.                     // If the image's width isn't the
  1022.                     // same as the page...
  1023.                     if (wid != e.MarginBounds.Width)
  1024.                     {
  1025.                       // Change the width to fit the page
  1026.                       wid = e.MarginBounds.Width;
  1027.                       // Adjust the height to maintain scale
  1028.                       // (even if bigger than the page height)
  1029.                       hgt = (int)(wid / ratio);
  1030.                     }
  1031.                     // If the image's height is bigger than the
  1032.                     // page...
  1033.                     if (hgt > e.MarginBounds.Height)
  1034.                     {
  1035.                       // Change the height to fit the paper
  1036.                       hgt = e.MarginBounds.Height;
  1037.                       // Adjust the width to maintain scale
  1038.                       wid = (int)(ratio * hgt);
  1039.                     }
  1040.                     // Set the interpolation settings to high
  1041.                     // quality
  1042.                     e.Graphics.InterpolationMode =
  1043.                       InterpolationMode.HighQualityBicubic;
  1044.                     // And send the image out to the page
  1045.                     e.Graphics.DrawImage(
  1046.                       toPrint,
  1047.                       e.MarginBounds.X,
  1048.                       e.MarginBounds.Y,
  1049.                       wid,
  1050.                       hgt
  1051.                     );
  1052.                   };
  1053.                 // Create and show the print dialog
  1054.                 System.Windows.Forms.PrintDialog pdlg =
  1055.                   new System.Windows.Forms.PrintDialog();
  1056.                 pdlg.Document = pdoc;
  1057.                 if (pdlg.ShowDialog() ==
  1058.                     System.Windows.Forms.DialogResult.OK)
  1059.                   pdoc.Print(); // Print on OK
  1060.               }
  1061.             }
  1062.           }
  1063.         }
  1064.         if (ad.WhiteBackground)
  1065.         {
  1066.           if (vtrId != ObjectId.Null || sbId != ObjectId.Null)
  1067.           {
  1068.             Remove3DBackground(db, tr, vtrId, sbId);
  1069.           }
  1070.           else
  1071.           {
  1072.             if (gotSettings)
  1073.             {
  1074.               acedSetCurrentColors(ref ocs);
  1075.               ed.WriteMessage("\n");
  1076.               ed.Regen();
  1077.             }
  1078.           }
  1079.           ed.UpdateScreen();
  1080.         }
  1081.         tr.Commit();
  1082.       }
  1083.     }
  1084.     // Check whether the active viewport is 3D
  1085.     private static bool is3D(Manager gsm)
  1086.     {
  1087.       short vp =
  1088.         (short)Application.GetSystemVariable("CVPORT");
  1089.       View v = gsm.GetGsView(vp, false);
  1090.       using (v)
  1091.       {
  1092.         return (v != null);
  1093.       }
  1094.     }
  1095.     // Return the image format to use for a particular filename
  1096.     private static ImageFormat GetFormatForFile(string filename)
  1097.     {
  1098.       // If all else fails, let's create a PNG
  1099.       // (might also choose to throw an exception)
  1100.       ImageFormat imf = ImageFormat.Png;
  1101.       if (filename.Contains("."))
  1102.       {
  1103.         // Get the filename's extension (what follows the last ".")
  1104.         string ext =
  1105.           filename.Substring(filename.LastIndexOf(".") + 1);
  1106.         // Get the first three characters of the extension
  1107.         if (ext.Length > 3)
  1108.           ext = ext.Substring(0, 3);
  1109.         // Choose the format based on the extension (in lowercase)
  1110.         switch (ext.ToLower())
  1111.         {
  1112.           case "bmp":
  1113.             imf = ImageFormat.Bmp;
  1114.             break;
  1115.           case "gif":
  1116.             imf = ImageFormat.Gif;
  1117.             break;
  1118.           case "jpg":
  1119.             imf = ImageFormat.Jpeg;
  1120.             break;
  1121.           case "tif":
  1122.             imf = ImageFormat.Tiff;
  1123.             break;
  1124.           case "wmf":
  1125.             imf = ImageFormat.Wmf;
  1126.             break;
  1127.           default:
  1128.             imf = ImageFormat.Png;
  1129.             break;
  1130.         }
  1131.       }
  1132.       return imf;
  1133.     }
  1134.     // Set the background colour of a 3D view
  1135.     private static void Set3DBackground(
  1136.       Editor ed,
  1137.       Database db,
  1138.       Transaction tr,
  1139.       EntityColor ec,
  1140.       out ObjectId vtrId,
  1141.       out ObjectId sbId
  1142.     )
  1143.     {
  1144.       // We're be returning IDs of the Viewport Table Record
  1145.       // and of the background itself
  1146.       vtrId = ObjectId.Null;
  1147.       sbId = ObjectId.Null;
  1148.       ed.UpdateTiledViewportsInDatabase();
  1149.       ViewportTable vt =
  1150.         (ViewportTable)tr.GetObject(
  1151.           db.ViewportTableId,
  1152.           OpenMode.ForRead
  1153.         );
  1154.       if (vt.Has("*Active"))
  1155.       {
  1156.         // Let's get the Viewport Table Record
  1157.         vtrId = vt["*Active"];
  1158.         DBDictionary nod =
  1159.           (DBDictionary)tr.GetObject(
  1160.             db.NamedObjectsDictionaryId,
  1161.             OpenMode.ForRead
  1162.           );
  1163.         // And create the background dictionary, if none exists
  1164.         ObjectId bkdId = ObjectId.Null;
  1165.         DBDictionary bkDict = null;
  1166.         const string dictKey = "ACAD_BACKGROUND";
  1167.         const string bkKey = "ADNPlugin_Screenshot";
  1168.         if (nod.Contains(dictKey))
  1169.         {
  1170.           bkdId = nod.GetAt(dictKey);
  1171.           bkDict =
  1172.             (DBDictionary)tr.GetObject(bkdId, OpenMode.ForWrite);
  1173.         }
  1174.         else
  1175.         {
  1176.           bkDict = new DBDictionary();
  1177.           nod.UpgradeOpen();
  1178.           bkdId = nod.SetAt(dictKey, bkDict);
  1179.           tr.AddNewlyCreatedDBObject(bkDict, true);
  1180.         }
  1181.         // Get or create our background object
  1182.         if (bkDict.Contains(bkKey))
  1183.         {
  1184.           sbId = bkDict.GetAt(bkKey);
  1185.         }
  1186.         else
  1187.         {
  1188.           SolidBackground sb = new SolidBackground();
  1189.           sb.Color = ec;
  1190.           sbId = bkDict.SetAt(bkKey, sb);
  1191.           tr.AddNewlyCreatedDBObject(sb, true);
  1192.         }
  1193.         // And set it to the viewport
  1194.         ViewportTableRecord vtr =
  1195.           (ViewportTableRecord)tr.GetObject(
  1196.             vtrId,
  1197.             OpenMode.ForWrite
  1198.           );
  1199.         vtr.Background = sbId;
  1200.       }
  1201.     }
  1202.     // Remove the previously set 3D background colour
  1203.     private static void Remove3DBackground(
  1204.       Database db,
  1205.       Transaction tr,
  1206.       ObjectId vtrId,
  1207.       ObjectId sbId
  1208.     )
  1209.     {
  1210.       // First remove it from the viewport
  1211.       if (vtrId != ObjectId.Null)
  1212.       {
  1213.         ViewportTableRecord vtr =
  1214.           (ViewportTableRecord)tr.GetObject(
  1215.             vtrId,
  1216.             OpenMode.ForWrite
  1217.           );
  1218.         vtr.Background = ObjectId.Null;
  1219.       }
  1220.       // And then erase the object itself (although
  1221.       // I suspect this is redundant)
  1222.       if (sbId != ObjectId.Null)
  1223.       {
  1224.         SolidBackground sb =
  1225.           (SolidBackground)tr.GetObject(
  1226.             sbId,
  1227.             OpenMode.ForRead,
  1228.             true
  1229.           );
  1230.         if (!sb.IsErased)
  1231.         {
  1232.           sb.UpgradeOpen();
  1233.           sb.Erase();
  1234.         }
  1235.       }
  1236.     }
  1237.     // Return a grayscale version of a provided bitmap,
  1238.     // with the option of forcing non-background pixels to
  1239.     // be black
  1240.     public static Bitmap ConvertToGrayscale(
  1241.       Bitmap src,
  1242.       System.Drawing.Color bgcol,
  1243.       bool force,
  1244.       System.Drawing.Color fgcol
  1245.     )
  1246.     {
  1247.       // From http://www.bobpowell.net/grayscale.htm
  1248.       Document doc =
  1249.         Application.DocumentManager.MdiActiveDocument;
  1250.       Editor ed = doc.Editor;
  1251.       Bitmap bmp = new Bitmap(src.Width, src.Height);
  1252.       for (int y = 0; y < bmp.Height; y++)
  1253.       {
  1254.         for (int x = 0; x < bmp.Width; x++)
  1255.         {
  1256.           System.Drawing.Color c = src.GetPixel(x, y);
  1257.           int lum =
  1258.             (force && !SameColors(c, bgcol) ?
  1259.               0 :
  1260.               (int)(c.R * 0.3 + c.G * 0.59 + c.B * 0.11)
  1261.             // 0.299R + 0.587G + 0.114B
  1262.             );
  1263.           bmp.SetPixel(
  1264.             x,
  1265.             y,
  1266.             (lum == 0 ?
  1267.               fgcol :
  1268.               System.Drawing.Color.FromArgb(lum, lum, lum))
  1269.           );
  1270.         }
  1271.       }
  1272.       return bmp;
  1273.     }
  1274.     // Return whether two colour can be considered equivalent
  1275.     // in terms of RGB values
  1276.     private static bool SameColors(
  1277.       System.Drawing.Color a,
  1278.       System.Drawing.Color b
  1279.     )
  1280.     {
  1281.       // Ignore Alpha channel, just compare RGB
  1282.       return (a.R == b.R && a.G == b.G && a.B == b.B);
  1283.     }
  1284.   }
  1285. }

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-6-2 03:26 , Processed in 0.179290 second(s), 25 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表