雪山飞狐_lzh 发表于 2009-9-12 20:08:00

八、从主窗口和绘图窗口截图September 09, 2009
Taking screenshots of AutoCAD’s main and drawing windows using .NET
I received a comment on this previous post:
    Hi Kean, I tried to develop a routine based on this post but I found 2 things that I'd like to solve if possible. Your routine is just not usable for larger drawings (takes way to long). Also, your routine does not work properly in paper space. Please, don't get me wrong, I appreciate all the work you've put into this blog. I just desperately need to come up with a solution for snapshots that work on large drawings, that work in paper/model space and that use the current viewstyle settings (except the background color). Isn't there a way to somehow mimic the GetGsView function that is fast and works in paper space as well but has a downfall of creating a 3D view in the actual drawing whenever a 2D Wireframe visual style is set? Any help is really appreciated.
The previous post was really focused on using AutoCAD’s graphics system to generate a bitmap of the contained model. Something that may work better for certain specific scenarios is to capture graphics at the operating system level: essentially taking a screenshot (as opposed to what I previously called a snapshot – although frankly the difference is really which graphics system you ask to generate the graphics). The code I’ve adapted for use within AutoCAD is shown here.
Here’s the C# code:
using acApp = Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using System.Drawing.Imaging;
using System.Drawing;

namespace ScreenshotTest
{
public class Commands
{
   
    static public void CaptureScreenShot()
    {
      ScreenShotToFile(
      acApp.Application.MainWindow,
      "c:\\main-window.png",
      0, 0, 0, 0
      );
      ScreenShotToFile(
      acApp.Application.DocumentManager.MdiActiveDocument.Window,
      "c:\\doc-window.png",
      30, 26, 10, 10
      );
    }

    private static void ScreenShotToFile(
      Autodesk.AutoCAD.Windows.Window wd,
      string filename,
      int top, int bottom, int left, int right
    )
    {
      Point pt = wd.Location;
      Size sz = wd.Size;

      pt.X += left;
      pt.Y += top;
      sz.Height -= top + bottom;
      sz.Width -= left + right;

      // Set the bitmap object to the size of the screen

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

          // Save the screenshot to the specified location

          bmp.Save(filename, ImageFormat.Png);
      }
      }
    }
}
}
The code is pretty simple: it defines a command called CSS which creates two screenshots – one of the entire application window and one of the active document. The function that does the hard work – ScreenShotToFile() takes some arguments to allow cropping of the created image, as – for instance - the drawing window also contains some graphics that most people wouldn’t consider to be part of the drawing. If you want to see the reason for adding these four parameters, try passing zero to each of them in second call to the function (just as we do for the first), and look at the resulting image in c:\doc-window.png.
You’ll need to add references to a few additional .NET assemblies: System.Drawing and probably PresentationCore if you’re working with AutoCAD 2010.
Now let’s see what we get when we run our CSS command in an editing session. Two files get created – c:\main-window.png:

And c:\doc-window.png:

While this approach may not work for every situation, it’s another option to consider, depending on your requirements.

雪山飞狐_lzh 发表于 2009-9-24 14:21:00

九、在文档中按选择区域截图
September 23, 2009
Taking a screenshot of a user-selected portion of a drawing using .NET
Following on from this recent post – and inspired by a question we received recently from a developer – I decided to extend the previous code to allow a user to select a portion of a drawing they would like to save to a file or place on the clipboard. This is actually a really useful tool for me when I’m writing this blog, so there was certainly a degree of self-interest involved. :-)
The technique shown in today’s post is actually pretty handy for other situations: it’s quite common to want to transform a point from drawing coordinates (which may well be in a specific User Coordinate System (UCS)) into screen coordinates, especially when wanting to display a window or a custom context menu at the cursor location.
To transform from UCS to screen coordinates we actually need three steps (and thanks to Adam Nagy, from DevTech EMEA, for creating code I borrowed from to get this working):
   1. Transform the selected point by the current UCS matrix to get WCS
          * This can be achieved using the .NET interface
   2. Transform the WCS point into Windows client coordinates
          * For this we need to P/Invoke the ObjectARX function acedCoordFromWorldToPixel()
   3. Transform the client coordinates into screen coordinates
          * For this we need to P/Invoke the Win32 API function ClientToScreen()
We need to do this for both corners of our user-selected window, of course. Once we have the corners in screen coordinates, the code from last time can be used: with a few modifications to support selection of top-right/bottom-left corners, rather than the assumed top-left/bottom-right, as well as adding support for placing the bitmap on the clipboard, rather than saving it to a file.
Here’s the updated C# code with our new WSS command:
using acApp = Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

using System.Drawing.Imaging;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System;

namespace ScreenshotTest
{
public class Commands
{
    // For the coordinate tranformation we need...

    // An ObjectARX function:

    [DllImport(
      "acad.exe",
      EntryPoint="?acedCoordFromWorldToPixel@@YAHHQBNAAVCPoint@@@Z"
    )]
    static extern int acedCoordFromWorldToPixel(
      int windnum,
      double[] ptIn,
      out Point ptOut
    );

    // And a Win32 function:

   
    static extern bool ClientToScreen(IntPtr hWnd, ref Point pt);

    // Command to capture the main and active drawing windows

   
    static public void CaptureScreenShot()
    {
      ScreenShotToFile(
      acApp.Application.MainWindow,
      0, 0, 0, 0,
      "c:\\main-window.png",
      false
      );
      ScreenShotToFile(
      acApp.Application.DocumentManager.MdiActiveDocument.Window,
      30, 26, 10, 10,
      "c:\\doc-window.png",
      false
      );
    }

    // Command to capture a user-selected portion of a drawing

   
    static public void WindowScreenShot()
    {
      acApp.Document doc =
      acApp.Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      // Ask the user for the screen window to capture

      PromptPointResult ppr =
      ed.GetPoint("\nSelect first point of capture window: ");
      if (ppr.Status != PromptStatus.OK)
      return;
      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)acApp.Application.GetSystemVariable("CVPORT");

      // Get the current UCS matrix (would be indentify if in WCS)

      Matrix3d ucsMat =
      ed.CurrentUserCoordinateSystem;

      // And then the handle to the current drawing window

      IntPtr hWnd = doc.Window.Handle;

      // Now calculate the selected corners in screen coordinates

      pt1 = ScreenFromDrawingPoint(ucsMat, hWnd, first, vp);
      pt2 = ScreenFromDrawingPoint(ucsMat, hWnd, second, vp);

      // Now save this portion of our screen as a raster image

      ScreenShotToFile(pt1, pt2, null, true);
    }

    // Perform our three tranformations to get from UCS (or WCS)
    // to screen coordinates

    private static Point ScreenFromDrawingPoint(
      Matrix3d ucsMat,
      IntPtr hWnd,
      Point3d ucsPt,
      short vpNum
    )
    {
      Point res;
      Point3d wcsPt = ucsPt.TransformBy(ucsMat);
      acedCoordFromWorldToPixel(vpNum, wcsPt.ToArray(), out res);
      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,
      bool clipboard
    )
    {
      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, clipboard);
    }

    // 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,
      bool clipboard
    )
    {
      // 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, clipboard);
    }

    // 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,
      bool clipboard
    )
    {
      // 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
          );

          // Save the screenshot to the specified location

          if (filename != null && filename != "")
            bmp.Save(filename, ImageFormat.Png);

          // Copy it to the clipboard

          if (clipboard)
            System.Windows.Forms.Clipboard.SetImage(bmp);
      }
      }
    }
}
}
To get the code working, you may need to add an additional assembly reference to System.Windows.Forms.dll, which gives us access to the Clipboard. I should also mention that I’m running this code on AutoCAD 2010 on a 32-bit OS: if the DllImport statement to P/Invoke our ObjectARX function, you may need to find and declare the appropriate function signature.
To put our WSS command through its paces, let’s load a drawing and set a random UCS (I do this by changing the view using 3DORBIT and using the UCS command, setting it to the current View) and on top of that changing the view to be from a another random angle not aligned with this new UCS:

Now we NETLOAD our assembly and run the WSS command, selecting the two corners of our window:

And we can see that the resultant image is on the clipboard by pasting it somewhere (we could very easily adjust the code to save to a file, if we so chose) :

Update:
Paavo pointed out in a comment that I had missed a managed API method to replace the P/Invoke of acedCoordFromWorldToPixel(), which makes the code much cleaner and more portable across versions: Editor.PointToScreen(). We still have to P/Invoke ClientToScreen(), but at least that’s undecorated/unmangled.
Here’s the revised C# code, which I’ve included in its entirety because it has ended up meaning a change both to the ScreenFromDrawingPoint() function and the code that calls it: we need the editor to perform our transformation so we might as well use it within the function to retrieve the matrix representing the current UCS. Which simplifies things somewhat.
using acApp = Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;

using System.Drawing.Imaging;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System;

namespace ScreenshotTest
{
public class Commands
{
    // For the coordinate tranformation we need...

    // A Win32 function:

   
    static extern bool ClientToScreen(IntPtr hWnd, ref Point pt);

    // Command to capture the main and active drawing windows

   
    static public void CaptureScreenShot()
    {
      ScreenShotToFile(
      acApp.Application.MainWindow,
      0, 0, 0, 0,
      "c:\\main-window.png",
      false
      );
      ScreenShotToFile(
      acApp.Application.DocumentManager.MdiActiveDocument.Window,
      30, 26, 10, 10,
      "c:\\doc-window.png",
      false
      );
    }

    // Command to capture a user-selected portion of a drawing

   
    static public void WindowScreenShot()
    {
      acApp.Document doc =
      acApp.Application.DocumentManager.MdiActiveDocument;
      Editor ed = doc.Editor;

      // Ask the user for the screen window to capture

      PromptPointResult ppr =
      ed.GetPoint("\nSelect first point of capture window: ");
      if (ppr.Status != PromptStatus.OK)
      return;
      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)acApp.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);
      pt2 = ScreenFromDrawingPoint(ed, hWnd, second, vp);

      // Now save this portion of our screen as a raster image

      ScreenShotToFile(pt1, pt2, null, true);
    }

    // Perform our three tranformations to get from UCS (or WCS)
    // to screen coordinates

    private static Point ScreenFromDrawingPoint(
      Editor ed,
      IntPtr hWnd,
      Point3d ucsPt,
      short vpNum
    )
    {   
      Point3d wcsPt =
      ucsPt.TransformBy(
          ed.CurrentUserCoordinateSystem
      );
      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,
      bool clipboard
    )
    {
      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, clipboard);
    }

    // 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,
      bool clipboard
    )
    {
      // 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, clipboard);
    }

    // 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,
      bool clipboard
    )
    {
      // 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
          );

          // Save the screenshot to the specified location

          if (filename != null && filename != "")
            bmp.Save(filename, ImageFormat.Png);

          // Copy it to the clipboard

          if (clipboard)
            System.Windows.Forms.Clipboard.SetImage(bmp);
      }
      }
    }
}
}
页: 1 [2]
查看完整版本: Kean专题(3)—Graphics_System