透明——Transparency
符号和图像都能使用透明的颜色。透明(α混合)的算法是基于栅格的。为了支持透明,矢量的图形必须首先转化为栅格。透明的对象被绘制到源位图中。那些绘制在背景上的对象必须以背景位图的形式存储。对所有的比特位使用单一的透明,或者对每单个像素使用包含了透明值的mask位图,通过这种方式把源位图混合掺入到背景位图中来实现透明。为了支持透明,IDisplay提供BackgroundDC属性来获取位图,该位图包含了发生在当前绘制过程中所有的图形。
显示透明——Display Transparency
透明算法封装在显示过滤对象TransparencyDisplayFilter中。相同的过滤器类能被要素图层,栅格图层,元素,第三方程序等使用。
通过下面的方式来绘制透明:
[C#]
ITransparencyDisplayFilter pFilter = new TransparencyDisplayFilterClass();
pFilter.Transparency = 100;
pDisplay.StartDrawing(hdc, cacheID);
pDisplay.Filter = pFilter;
DrawToDisplay();
pDisplay.Filter = 0;
pDisplay.FinishDrawing();
对于栅格图层,DrawToDisplay()意味著从显示(display)中获取目标DC,然后BitBlt栅格图像给它。
当指定了过滤器时,显示(display)会生成一个内部的过滤器缓存,这个缓存随着记录缓存为过滤器提供必要的栅格信息。Output通向过滤缓存,这样当栅格图层请求目标DC时,它能得到过滤缓存DC。当pDisplay.Filter设置为0时,显示(display)应用过滤器。应用接受当前的背景位图(记录缓存),不透明的栅格图层影像(过滤缓存)和目标(窗口)。过滤器知道透明度的值是100,这样它混合(do the blending)并且发送结果到窗口。
符号透明——Symbol Transparency
我们如何使符号和图像支持透明呢?视图对象(数据和布局)处理绘图地图(drawing maps)到输出设备(窗口和打印机)和输出文件(位图和元文件)。所有的图形元素和图层都需要通报是否它们使用了透明颜色。这样,当视图开始生成输出是,它能检查是否存在透明的颜色。如果存在,它使用下面的算法来产生输出:
- 划分显示(display)表面成不同的波段,这样使要求生成透明颜色的内存中的位图不会消耗更多的内存。
- 生成一个波段大小的BackingDisplay。BackingDisplay有一个独立位图的内部设备,它具有和输出设备相同的颜色深度和分辨率。直接绘制到位图上,在绘制完成之后,位图被复制到实际的输出设备(或文件)上。图层和符号使用BackingDisplay就像任何其它显示(display)一样,不需要特殊的编码。
- 对于每个波段:根据当前的波段来调节显示(display)的转换,绘制视图。因为转换的可见范围(bounds)等于波段的矩形,所以渲染将自动地剪切波段。
这将导致一系列的位图(波段)被复制到输出上或导出文件中。如果在地图中不存在透明的颜色,元文件将会自动生成,例如,它将包含一系列的矢量图形。
快速显示——Rapid Display
经常,有必要快速更新显示(Display)来显示活动对象的运动情况。例如,具有GPS跟踪的商业活动。存在两个方面的问题:
- 如何存储活动数据
- 如何快速地绘图到显示(Display)
设计模式——Design Patterns
在我们的应用程序中有多种方式来存储和绘制时间数据。大部分通用的方法如下:
l 必要地生成要素类,添加和删除要素。使用标准的渲染或自定义符号来绘制要素。在其它图层的上面或下面绘图。为了更好的性能,捆绑具有自己显示缓存的要素图层(例如,设置ILayer::Cached为true)。
l 生成自定义图层。使用属性数据或要素类来存储要素。对图层完成自定义渲染(例如,直接使用GDI+)。在其它图层的上面或下面绘图。为了更好的性能,对每个要素图层指定自己的显示缓存。
l 生成图形图层。以图形元素的方式来存储数据。使用标准的或自定义符号来渲染。在所有其它图层上面绘制他。
l 响应IActiveViewEvents::AfterDraw(esriViewForeground)来绘图。基于私有数据来存储数据。直接使用GDI or IDisplay来绘图。在其他图层的顶板来绘图。快!GPS扩展来使用这种方法。
有三种通用的方式来存储自定义数据:
1. GeoDatabase里的要素
2. 图形图层里的元素
3. 私有的数据结构
哪一种方式是最好的选择取决于:在绘制顺序中绘制的位置,是否使用标准的渲染对象,是否需要支持私有的数据格式。
在所有的情况下,都应该使用标准的无效绘制模型。这就是生成绘图对象(如,图层,图形元素,时间句柄),把它插入到地图中,当想要绘制的时候调用IActiveView::PartialRefresh。
动画支持——Animation Support
在ArcObjects 9的版本中,一个新的接口IviewRefresh,大大地简化了快速地刷新显示(display)来显示活动对象。客户应该使用AnimationRefresh例行程序取代PartialRefresh来使他们的自定义绘图对象无效。例如,我们可以使用一个带有自己显示缓存的图层来存储“活动”要素。动画(Animation)是通过移动要素,无效化图层(利用AnimationRefresh)和自然地重绘来实现的。当用AnimationRefresh代替PartialRefresh时,下面的optimizations / tradeoffs启用了:
文本反锯齿(Text Anti-aliasing)临时无效了。这是为了防止当动画图层无效时标注必须每次都重绘。记住,反锯齿文本利用背景作为渲染的部分,当其下面有任何改变时,文本也需要从头开始绘制。
动画图层上面的透明图层不会随着动画图层而自动无效。 这加快了重绘的速度但有其局限性:位于动画图层里的要素将不再通过其上的透明图层显示出来。
所有的图形直接绘到记录缓存HDC而不是窗口。这就使得所有的绘制在幕后发生。当绘制完成时,窗口只需要从也完成的记录缓存中更新一次。这就很少有闪烁。
为了避免在快速绘制过程中过多地占用CPU的资源,我们可以在旧的无效位置和新的无效位置调用一次UpdateWindow。
显示设计模式——Display Design Patterns
为了帮助理解各种显示对象如何一起工作,解决一般的开发需求,下面将给出了几个应用场景的详细实现。显示对象开始使用这些模式工作。
应用程序窗口——The application window
在应用程序窗口的客户区域绘制地图是大部分普通任务之一,这些窗口支持滚动和后备存储。显示对象通过如下方式来实现这些任务。
初始化——Initialization
当窗口创建时先生成ScreenDisplay。我们也需要生成一个或多个符号来绘制图形。转发应用程序的hWnd给pScreenDisplay.hWnd。从ScreenDisplay中获取其IDisplayTransformation接口,使用pTransformation.Bounds和pDisplayTransform.VisibleBounds设置地图的全部范围和可见范围。可见范围决定了当前缩放的等级。ScreenDisplay负责更新显示转换的DeviceFrame。ScreenDisplay监测窗口消息,自动地处理通用的事件,如窗口大小的改变或滚动。
[C#]
private IScreenDisplay m_pScreenDisplay;
private ISimpleFillSymbol m_pFillSymbol;
private void Form_Load(object sender, System.EventArgs e)
{
m_pScreenDisplay = new ScreenDisplayClass();
m_pScreenDisplay.hWnd = Picture1.hWnd;
m_pFillSymbol = new SimpleFillSymbolClass();
IEnvelope pEnv = new EnvelopeClass();
pEnv.PutCoords(0, 0, 50, 50);
m_pScreenDisplay.DisplayTransformation.bounds = pEnv;
m_pScreenDisplay.DisplayTransformation.VisibleBounds = pEnv;
}
绘制——Drawing
显示对象(the display object)定义了一个通用的接口IDraw,这个接口使得我们绘制图形到任何显示(display)上变得容易。只要我们使用IDraw 或IDisplay来实现绘制代码,就不必担心所绘制到的设备类型。绘制的过程以StartDrawing开始到FinishDrawing结束。例如,编写这样一个程序,在屏幕中央建立一个多边形,然后绘制出来。这个图形使用默认的符号来绘制。例子如下:
[C#]
private IPolygon GetPolygon()
{
IPolygon GetPolygon;
GetPolygon = new PolygonClass();
IPointCollection pPointCollection;
pPointCollection = GetPolygon as IPointCollection;
IPoint pPoint = new PointClass();
pPoint.PutCoords(20, 20);
pPointCollection.AddPoints(1, ref pPoint);
pPoint.PutCoords(30, 20);
pPointCollection.AddPoints(1, ref pPoint);
pPoint.PutCoords(30, 30);
pPointCollection.AddPoints(1, ref pPoint);
pPoint.PutCoords(20, 30);
pPointCollection.AddPoints(1, ref pPoint);
GetPolygon.Close();
return GetPolygon;
}
private void MyDraw(IDisplay pDisplay, esriSystem.OLE_HANDLE hDC)
{
// Draw from Scratch
IDraw pDraw;
pDraw = pDisplay;
pDraw.StartDrawing(hDC, esriNoScreenCache);
IPolygon pPoly;
pPoly = GetPolygon();
pDraw.SetSymbol(m_pFillSymbol);
pDraw.Draw(pPoly);
pDraw.FinishDrawing();
}
这个程序能用来绘制多边形到任何设备环境中。然而,我们需要绘制的第一个地方是窗口。为了处理这种情况,需要在Picture Box的Paint方法里写些代码,这个方法传递应用程序的ScreenDisplay指针和Picture Box HDC到MyDraw程序(routine)中。注意这个程序接受显示指针(a display pointer)和窗口设备环境。
[C#]
private void Picture1_Paint()
{
MyDraw(m_pScreenDisplay, Picture1.hDC);
}
转发DC让显示(display)来授权这个剪切范围,Windows把它设置到paint的HDC中。(Forwarding the DC allows the display to honor the clipping regions that Windows sets into the paint HDC.)
添加显示缓存——Adding Display Caching
许多绘制过程需要花费一定时间来完成。优化我们应用程序的一个简单的办法就是开启显示缓存。这就涉及到ScreenDisplay's的能力,它可以把绘图过程记录到一个位图中,之后当Paint方法调用的时候,使用这个位图来刷新这个图片窗口。直到我们的数据变化了和我们调用IScreenDisplay::Invalidate来使缓存无效时,缓存才被使用。有两种类型的缓存:记录缓存和用户分配的缓存。在这个例子中,在应用程序的Paint方法使用记录缓存来实现显示缓存。
[C#]
private void Picture1_Paint()
{
if ((m_pScreenDisplay.IsCacheDirty(esriScreenRecording)))
{
m_pScreenDisplay.StartRecording();
MyDraw(m_pScreenDisplay, Picture1.hDC);
m_pScreenDisplay.StopRecording();
}
else
{
tagRECT rect;
m_pScreenDisplay.DrawCache(Picture1.hDC, esriScreenRecording, rect, rect);
}
}
当你执行这段代码是,将发现屏幕上没什么绘制出来。这是因为ScreenRecording缓存没有设置dirty标识。为了确保当第一个绘图消息传来时MyDraw函数被调用,我们必须是缓存无效。在方法的结尾处添加下面一行代码:
[C#]
m_pScreenDisplay.Invalidate(null, true, esriScreenRecording);
许多应用程序,例如ArcMap,可能需要多个显示缓存。为了利用多缓存,照下面的步骤来做:
1. 使用IScreenDisplay::AddCache来添加新的缓存。保存它返回的缓存ID。
2. 为了绘制缓存,给StartDrawing指定缓存ID。
3. 为了使缓存无效,给Invalidate指定缓存ID。
4. 为了从缓存里绘图,给DrawCache指定缓存ID。
为了使这个例子程序支持它自己的缓存,作如下改变:
- 添加一个成员变量来保存新的缓存。
[C#]
private long m_lCacheID;
- 在Form_Load方法里生成这个缓存。
[C#]
m_lCacheID = m_pScreenDisplay.AddCache;
- 使用变量来改变恰当的调用,从Paint方法里清除开始和停止的记录。
漫游,缩放和旋转——Pan, zoom, and rotate
显示对象(The display object)一个强大的功能(feature)是对我们的图形进行放大和缩小。很容易实现这个工具让用户缩放或漫游。滚动能自动地操作。为了对我们的图形进行缩放,只需简单地设置显示(display)的可见范围。例如,添加一个命令按钮到form上,在这个按钮的事件里通过合适的数量来缩放屏幕,代码如下:
[C#]
private void Command1_Click()
{
IEnvelope pEnv = m_pScreenDisplay.DisplayTransformation.VisibleBounds;
pEnv.Expand(0.75, 0.75, true);
m_pScreenDisplay.DisplayTransformation.VisibleBounds = pEnv;
m_pScreenDisplay.Invalidate(null, true, esriAllScreenCaches);
}
ScreenDisplay实现了TrackPan,它在响应mouse down的事件里被调用,让用户在显示(display)上漫游。通过设置DisplayTransformation旋转属性为非零值,我们可以以屏幕为中心来旋转整个图形。旋转以度数的形式被指定。ScreenDisplay实现了TrackRotate,它在响应mouse down的事件里被调用,让用户在显示(display)上交互旋转。
打印——Printing
打印与绘图到屏幕上非常相似。因为绘图到打印机上时不必担心缓存或滚动,所以可以使用SimpleDisplay。生成一个SimpleDisplay对象,通过拷贝ScreenDisplay的转换来初始化它的转换。设置打印机转换的DeviceFrame到打印机页面的像素范围。最后,利用SimpleDisplay和打印机的HDC从头开始绘制。
输出到元文件——Output to a metafile
GDIDisplay对象用来代表一个元文件。生成一个元文件和生成一个打印机没有什么不同。如果我们指定CreateEnhMetaFile的lpBounds参数为0,MyDraw程序可以被使用。只是hPrinterDC把替换为hMetafileDC。如果你想为CreateEnhMetafFile(以HIMETRIC为单位)指定一个范围,设置DisplayTransformation的DeviceFrame为相同矩形的像素版本。
Print to a frame
许多工程可能要求直接打印输出到输出设备的某个子矩形中。这容易处理,通过设置DisplayTransformation的设备框架给一个像素范围,这个范围要比整个设备的范围要小。
过滤器——Filters
使用显示过滤器(display filters),非常先进的绘图效果,如颜色透明,都能够被实现。过滤器(Filter)同显示缓存一起工作,可以操纵栅格版本的图形。当为显示(使用IDisplay::putref_DisplayFilte)指定了一个过滤器时,显示生成了一个内部的过滤缓存,这个过滤缓存同记录缓存一起为过滤器提供了栅格信息。输出都会经过过滤缓存直到这个过滤器被清除(这就是putref_DisplayFilter(0))。在这时,display调用IDisplayFilter::Apply。Apply接受当前的背景位图(记录缓存),绘制缓存(包括在绘制中发生的所有的,因为过滤器被指定了),和目标HDC。透明过滤器对这些位图进行透明混合处理,然后把它们绘制到目标HDC来达到颜色的透明。能生产新的过滤器能来实现其他的效果。