Bitmap Surfaces in Trident
Date Last Updated:
Author: Michael Winser
Status: Draft
Version: 0.3
Bitmap surfaces are used in Trident in a variety of forms: DIBs, DIB sections, HBITMAPs and DCs. Although GDI handles each of these in a slightly different manner, they are all essentially the same thing: an array of bits called a bitmap. The goal is to define a common set of interfaces that encapsulates all existing uses of bitmaps in Trident as well as providing for future use of bitmaps from DirectDraw.
A bitmap will be represented by an IBitmapSurface object. This object may also be QI'd for IGdiSurface and IDDSurface. These interfaces provide a way to get at the the GDI and DirectDraw objects associated with a particular surface. Bitmaps surface are generally created by bitmap surface factories. The main interface for this is IBitmapSurfaceFactory. This object may also be QI'd for IGdiSurfaceFactory and IDDSurfaceFactory.
Direct access to bitmap bits will result in new features and performance improvements. For example, while transparent blts can be accomplished via GDI, they typically require three passes and an intermediate mask. With direct access to the surface bits, the blt can be done in a single pass with no mask. Alpha channel bitmaps are an example of functionality not currently supported by GDI that is almost trivial given direct access to the surface bits.
The IBitmapSurface interface has uses beyond its immediate use in Trident.
This is particularly apparent with the use of an IBounds object instead of a
simple RECT for coordinate information. By using an interface pointer instead of
a structure it becomes possible to QI the object for extra bounds information
such as time. This is particularly important when this information must
"pass through" a component that doesn't know about or check for the
new bounds information.
Centralized allocation and creation of off-screen surfaces
Trident currently has two off-screen DC classes and allocates DIBs in several places. By centralizing all surface allocation, it will be possible to more efficiently allocate and free surfaces, especially ephemeral surfaces used only during paint.
A central surface factory will enable a per-process or per-thread cache of discardable surfaces. Not only will this cache be used by internal Trident components, it will be available to external controls. This means that smart controls can participate in the application level cache intead of competing with the main application for working set.
There are several factory interfaces that will be provided via IServiceProvider. IBitmapSurfaceFactory is the preferred way to create arbitrary surfaces, IGdiSurfaceFactory is used to create surfaces compatible with an HDC.
While the initial implementation of the allocator will probably just use
CreateDIBSection, a central allocator makes it very easy to add support for
DirectDraw later on.
Allow controls to get at surface bits
The primary drawing interface for controls is still IViewObject::Draw. In order to allow controls to get at the destination surface bits, the IGdiSurfaceFactory interface has a GetSurfaceFromDC function. This solution was prefered over adding yet another control interface for which the container would have to check. The control would typically get the IGdiSurfaceFactory interface pointer once (at creation) and then map the DC to a surface when asked to paint.
DirectX provides driver level access to the graphics hardware. DirectDraw provides frame buffer and bltter access and Direct3D provides 3d rendering. Some of the benefits of DirectDraw include hardware transparent blt, page flipping, vertical retrace timing, tear free blts. While DirectDraw uses COM, it is unfortunately not aggregateable. Furthermore, several DirectDraw operations will grab the Win16 mutex. The IBitmapSurface implemention in Trident will therefore delegate to DirectDraw as necessary.
However, controls can and should be able to use DirectDraw when it is available. The IDDSurface interface (QI'd from an IBitmapSurface object) allows the caller to get the underlying IDirectDraw and IDirectDrawSurface objects. Not only does this allow controls direct access, it also allows them to coordinate this with other DirectX support such as Direct3D.
An IBounds interface is used to describe and compare the size of an object. The initial IBounds interface describes pixel bounds only but the concept can be extended to support other dimensions (e.g. time, frames, metric size). These other dimensions are obtained by QI'ing the IBounds object for new interface (e.g. ITimeBounds, IFrameBounds, IMetricBounds).
This extensibility is why an interface is used instead of a simple structure. Components can be written to know about only the dimensions they need to accomplish a task and still work with more complex objects. For example, a transparent blt function might be declared as
HRESULT TransparentBlt(IBitmapSurface *pSurfaceSrc, IBounds *pBoundsSrc, IBitmapSurface *pSurfaceDst, IBounds *pBoundsDst);
The function obviously needs to know about the pixel
coordinates of the operation but has no interest or knowledge of time. It simply
calls LockBits on the surface parameters and does it's work. And yet, if the
surface parameters are multi-framed surfaces, they could QI for IFrameBounds or
ITimeBounds on the bounds parameter in order to determine which sub-surface to
actually lock.
Some thought was given to making IBounds a dimensionless
interface and then requiring components to QI for something like IPixelBounds
that would return rectangles in pixel coordinates. While we may define a base
interface IGenericBounds in the future, there is little point in doing so for a
set of interfaces where pixels are the common denominator. The extra QI wouldn't
be very expensive but can easily be avoided.
interface IBounds : IUnknown { HRESULT Clone(IBounds **ppBounds); HRESULT GetRect(int *pLeft, int *pTop, int *pRight, int *pBottom); HRESULT SetRect(int left, int top, int right, int bottom); HRESULT IBounds::CopyFromBounds(IBounds *pBounds); HRESULT IsEqual(IBounds *pBounds); HRESULT IntersectBounds(IBounds *pBoundsSrc, IBounds **ppBoundsResult); HRESULT UnionBounds(IBounds *pBoundsSrc, IBounds **ppBoundsResult); }
Makes a copy of the bounds object. By using this function instead of explicitly copying information, the caller ensures that all other information (time, metric size, etc.) is also copied.
Description:
The bounds objects takes on the values of all common dimensions from
the source object in pBounds. This is not a copy. This does not add new
dimensions to the object.
The benefit of this function is that the bounds object may
know of dimensions that the caller does not. For instance, two bounds objects
may both contain time information. Even if the caller is unaware of this
dimension, this function ensures that all common dimensions are copied. While this is better than having the caller simply copy the rectangle
information, callers should be aware that source dimensions not present in the
destination will not be copied.
Parameters:
- [in] IBounds *pBounds - the bounds object against which the object is compared
- [in] BOOL fMustMatchAllDimensions - determines how the comparison is performed
Return values:
- S_OK
- S_FALSE
Description:
Compares the bounds object to the parameter pBounds. The
object will compare itself to pBounds on all its dimensions. If
fMustMatchAllDimensions is TRUE then pBounds the the object will QI pBounds for
all of it's dimensions. If pBounds does not support a dimension, the function
will return S_FALSE. When fMustMatchAllDimensions is FALSE then only the common
dimensions are compared.
Should we call this CompareBounds to make it a bit more
clear that this isn't a true test of equality. Perhaps we should have
CompareBounds as described above and IsEqual (possibly as a helper function)
that does the compare in both directions.
pBounds1->CompareBounds(pBounds2) == S_OK && pBounds2->CompareBounds(pBounds1) == S_OK)
Gets the rectangular bounds of the object.
Sets the rectangular bounds of the object.
The source bounds object is checked for intersection on all the dimensions of the bounds object. The result is returned in ppBoundsResult. The semantics of this function are identical to those of the IntersectRect call in the Win32 API.
The source bounds object unioned on all the dimensions of the bounds object. The result is returned in ppBoundsResult. The semantics of this function are identical to those of the IntersectRect call in the Win32 API.
The IBitmapSurface encapsulate a bitmap. Through this interface, callers can get at the underlying image bits. Bitmaps come in all widths, heights, and most importantly formats. Instead of describing the various attributes of a bitmap format in a structure, each bitmap format is uniquely identified by a GUID.
typedef GUID BFID;
The BFID embodies, bit depth, color space, how the bits are arranged in memory. Essentially everything about a format except the width, height and pitch of the bitmap. This will allow components to recognize formats with impunity, it isn't possible to forget to check some aspect of the description structure. Because BFIDs are GUIDs, ISVs can define their own formats with no danger of collision with other formats. The following formats will be defined:
BITMAPINFOHEADER equivalents | ||||
Format name | biPlanes | biBitCount | biCompression | Pixel format |
BFID_MONOCHROME | 1 | 1 | BI_RGB | same as DIB |
BFID_GRAYSCALE | 1 | 8 |
BI_RG B |
same as DIB |
BFID_RGB_1 | 1 | 1 | BI_RGB | same as DIB |
BFID_RGB_4 | 1 | 4 | BI_RGB | same as DIB |
BFID_RGB_8 | 1 | 8 | BI_RGB | same as DIB |
BFID_RGB_555 | 1 | 16 | BI_RGB | same as DIB |
BFID_RGB_565 |
1 | 16 | BI_RGB | same as DIB |
BFID_RGB_665 | 1 | 16 | BI_RGB | same as DIB |
BFID_RGB_24 | 1 | 24 | BI_RGB | same as DIB |
BFID_RGB_32 | 1 | 32 | BI_GB | same as DIB |
BFID_RGB_A_32 | 1 | 32 |
For most of the above formats, the layout and meaning of the bits corresponds to a standard format in Windows DIBs. Nonetheless, each format will be fully described in a separate document. Not all of these formats will be implemented in Trident.
interface IBitmapSurface : IUnknown { HRESULT Clone(IBitmapSurface **ppBitmapSurface); HRESULT GetFormat(BFID *pBFID); HRESULT GetFlags(DWORD *pdwFlags); HRESULT GetFactory(IBitmapSurfaceFactory **ppBitmapSurfaceFactory); HRESULT GetBounds(IBounds **ppBounds); HRESULT GetPitch(int *pPitch); HRESULT LockBits(IBounds *pBounds, DWORD dwLockFlags, void **ppBits); HRESULT UnlockBits(IBounds *pBounds, void *pBits); HRESULT Clone(IBitmapSurface **ppBitmapSurface); }Parameters:
Creates a clone of the bitmap surface. This includes any extra information that may be available through other interfaces (e.g. color profile).
Returns the format ID of the bitmap surface.
Returns a pointer to the surface factory.
Parameters:
Return values:
Description:
Returns flags that describe some attributes of the surface. These flags are the same set passed into CreateBitmapSurface but not necessarily the same value.
Is this really needed? We need to identify scenarios in
which this is actually used. I can't help but feel that this could be eliminated
if the semantics of the flags was more precisely defined in CreateBitmapSurface.
Are these flags hints or requirements? If I don't specify discardable, does that
mean that the surface will never be discardable?
The only flag that I can't infer from QI'ing for another interface is
SURFACE_DISCARDABLE. I've been thinking that we may want some kind of cache
control interface which may be just the ticket.
In all likelyhood these questions will be resolved during development. Some
flux should be expected.
Returns a pointer to the bounds object for this surface. This is a tearoff interface. The returned IBounds object has a
lifetime that is independent of the bitmap surface. The top left corner of the
rectangle of this bounds object is always left == 0 and top == 0.
HRESULT GetPitch(int *pPitch);
Parameters:
The pitch is the amount to add to the address of pixel (x,y) to address pixel (x, y + 1)
Return values:
LockBits is used to get a pointer to the actual bitmap bits. It is assumed
that the caller has checked the BFID and can actually interpret the format. All
calls to LockBits must be matched with an equivalent call to UnlockBits. If pBounds is NULL then the entire
surface is locked.
Although LockBits must be matched with UnlockBits, these
calls do not AddRef the object. The lifetime of the object is independent of the
lock count. This means that if an object is released while locked, the caller
may still have a pointer to the bits. That pointer is invalid and must not be
used.
The flags in dwLockFlags are independent bits and are defined as
follows: SURFACE_LOCK_EXCLUSIVE means that the caller wants exclusive
access to the bounds specified. There can only be one exclusive lock on a given
part of the bitmap. The granularity with which this is enforced is
implementation dependent. For some implementations, it will apply to the entire
object. Some implementations will track bounds rects more precisely and allow
multiple non overlapping exclusive locks.
SURFACE_LOCK_ALLOW_DISCARD means that the caller doesn't care if the surface
was discarded, the implementation can provide new bits and should return
S_SURFACE_DISCARDED. If this flag is not specified and the surface was
discarded, no surface pointer will be returned and E_SURFACE_DISCARDED will be
returned.
Return values:
UnlockBits unlocks the bits. The pointer in pBits must be considered invalid
after this call. The pBounds, pBits pair must
be exactly the same pointers that were passed to the matching LockBits call.
This allows the implementation to use the pBounds, pBits pair as a unique cookie
identifying a particular lock.
As with LockBits, pBounds == NULL is the entire surface.
UnlockBits may also be called without a matching LockBits,
if and only if pBits is NULL. This is a hint to the implementation that the bits
may be discarded (see IBitmapSurfaceFactory::CreateSurface).
interface IGdiSurface { HRESULT GetDC(HDC *pHDC, DWORD dwLockFlags); HRESULT ReleaseDC(HDC hdc); }
Gets or creates an HDC onto the bitmap surface. The lock flags have the same semantics as the IBitmapSurface::LockBits call. All calls to GetDC must be matched by a call to IGdiSurface::ReleaseDC.
interface IDDSurface { HRESULT GetDirectDraw(IDirectDraw **ppDirectDraw); HRESULT GetDirectDrawSurface(IDirectDrawSurface **ppDirectDrawSurface); HRESULT ReleaseDirectDrawSurface(IDirectDrawSurface **ppDirectDrawSurface); }
Returns the Direct Draw object used to create the surface. The interface pointer returned has a COM identity and lifetime of it's own.
Returns the Direct Draw surface used to create the surface. The interface pointer returned has a COM identity and lifetime of
it's own. However, the caller will be sharing the
IDirectDrawSurface interface with the IDDSurface object. For this reason, the
caller should not use AddRef and Release on the obtained object. Instead,
ReleaseDirectDrawSurface should be called.
Note, we may remove this requirement should it proove to be totally
unecessary but we're leaving it in for now.
Releases the IDirectDrawSurface object. Callers must use this instead of simply Releasing the object.
interface IBitmapSurfaceFactory { HRESULT CreateBitmapSurface(int width, int height, BFID *pBFID, DWORD dwHintFlags, IBitmapSurface **ppBitmapSurface); HRESULT GetSupportedFormatsCount(unsigned *pcFormats); HRESULT GetSupportedFormats(unsigned cFormats, BFID *pBFIDs); }
Creates a bitmap surface. The dwFlags parameter is a hint to the implementation as to the desired use of the surface. The possible values for dwFlags are:
SURFACE_GRAPHICS - the caller will want an OS dependent drawing object on the surface
SURFACE_HARDWARE - the caller would like the surface to be allocated in video hardware memory
SURFACE_DISCARDABLE - the surface is cache and can be discarded when unlocked
SURFACE_ATTRIBUTES_REQUIRED - tells the implementation to fail if the hint flags cannot be supported.
If SURFACE_ATTRIBUTES_REQUIRED is specified and the implementation cannot fulfill the requested hints (notably SURFACE_GRAPHICS and SURFACE_HARDWARE), E_SURFACE_UNKNOWN_FORMAT will be returned. This is to allow components that require hardware access to either get it or fail early on.
Parameters:
- [out] unsigned *pcFormats - address in to which the total number of supported formats is returned.
Return values:
- S_OK
- E_POINTER
Description:
Returns the number of bitmap formats supported by this factory. While this number cannot change for a given IBitmapSurfaceFactory instance, it can change from instance to instance of the IBitmapSurfaceFactories, even from the same implementation.
Return values:
Returns an array of BFIDs that the factory supports
interface IGdiBitmapSurfaceFactory { HRESULT CreateCompatibleBitmapSurface(HDC hdc, DWORD dwHintFlags, IBitmapSurface **ppBitmapSurface) HRESULT GetFormatFromDC(HDC hdc, BFID *pBFID); HRESULT GetSurfaceFromDC(HDC hdc, IBitmapSurface **ppBitmapSurface); }
This is more or less equivalent to CreateCompatibleDC and CreateDIBSection. Like IBitmapSurfaceFactory::CreateBitmapSurface, the width and height must be > 0.
Returns the format of a compatible surface for this HDC. If the HDC does not have an surface format (e.g. a postscript printer), E_SURFACE_NOSURFACE is returned.
This function is used to find the surface associated with an HDC. When the DC is a screen DC, the surface can only be returned if DirectDraw is available. The surface object returned may change from call to call. This is because the surface object may be dynamically created (especially with DirectDraw) at the time of the call.
interface IDDSurfaceFactory { HRESULT CreateBitmapSurface(IDirectDrawSurface *pDDSurface, IBitmapSurface **ppBitmapSurface) HRESULT GetFormatFromDDSurface(IDirectDrawSurface *pDDSurface, BFID *pBFID); HRESULT GetSurfaceFromDD(IDirectDrawSurface *pDDSurface, IBitmapSurface **ppBitmapSurface) }
Creates a BitmapSurface from a DirectDraw surface. This interface is used by components that have acquired a DirectDraw surface through other means and wish to wrap it as an IBitmapSurface. The IDirectDrawSurface object will be AddRef'd, to be released only when the created IBitmapSurface object is itself destroyed. This function will return E_SURFACE_UNKNOWN_FORMAT if the format IDirectDrawSurface is not supported by the surface factory.
Returns the format of the Direct Draw surface
Returns the IBitmapSurface object associated with a Direct Draw surface. Much like the GetSurfaceFromDC call of IGdiSurface, this function attempts to find the associated IBitmapSurface object for a given IDirectDrawSurface. If the IDirectDrawSurface object was not created by the IDDSurfaceFactory implementation, E_SURFACE_NOSURFACE will be returned.
A color
table is used when the bitmap format is indexed. In this case, the pixel data
does not contain the actual RGB values of the pixel. Instead it contains an
index into an color table. When supported, this interface is available via QI
from an IBitmapSurface. The following formats should always implement
IRGBColorTable: BFID_RGB_1, BFID_RGB_4, BFID_RGB_8.
The transparent color index is used to indicate what is
also known as a chromakey. Although the exact interpretation of this color
depends on the appliation, this color should be skipped over during blts and
ignored when dithering.
A special color index COLOR_NO_TRANSPARENT (0xffffffff) is
used to indicate no colors indices are transparent.
interface IRGBColorTable { HRESULT GetCount(unsigned *pCount); HRESULT SetCount(unsigned count); HRESULT GetColors(unsigned iFirst, unsigned count, RGBQUAD *pColors); HRESULT SetColors(unsigned iFirst, unsigned count, RGBQUAD *pColors); HRESULT GetTransparentIndex(unsigned *pIndex); HRESULT SetTransparentIndex(unsigned index); }
Returns the number of colors in the color table.
Parameters:
Return values:
Description:
This function changes the size of the color table. Callers should not that this call does not change the image bits in any way. Notably, it is quite possible to set the count such that pixels in the bitmap refer to invalid colors. The interpretation of those pixels is implementation dependent and could cause some implementations to crash.
Copies the RGBQUAD of the color table into the array. If iFirst + count exceeds the size of the color table, no colors are copied and E_FAIL is returned. This is specifically to prevent callers from assuming that color tables are the usual size (256).
Sets the color table from an array of RGBQUADs. This will never change the underlying object's bits. If iFirst + count exceeds the size of the color table, E_FAIL is returned an no colors are copied.
Description:
Returns the index of the transparent color. If there are no transparent color indices, *pIndex will contain the special value: COLOR_NO_TRANSPARENT.
Sets the index of the transparent color. Use COLOR_NO_TRANSPARENT to indicate that there is no transparent index.
Most of the above interfaces are mostly automation compatible. While it is
not expected that high level automation clients (like VB) will actually
manipulate bitmap surface bits, it is quite reasonable to assume they will be
used to coordinate between multiple lower level components that do in fact
access the bits.
DirectDraw controls
Some controls require DirectDraw or other DirectX
capabilities in order to work. Unfortunately, DirectX functionality is parcelled
up into tiny caps flags that make it almost impossible for the container to
determine if the control can be used or not. Nonetheless, we hope to define (or
see defined) some broad component categories that define common areas of
DirectX. This will help the container determine how to create surfaces for a
given control.
Should we rename GetPitch to GetStride for naming compatibility with
DirectDraw? Obviously this is a cosmetic and documentation issue only.
This document has only touched on the possibility of caching and discardable
surfaces. The actual implementation and behaviour of the cache will be described
in a separate document. That document must also describe how caching decisions
are made and controlled.
We need to define the exact semantics of the various IRGBColorTable members.
Is the transparent color index considered to be one of the color table? Can the
color table actually be resized or are we simply changing the number of colors
used?
Pitch or Stride?
Cache control
Color tables and transparency
Although palettes and color tables are closely related, this
document does not address any palette issues. This includes output to a
palettized device or palette animation.
Although not needed for Trident, it is not unreasonable to imagine an
interface called IDIBSurfaceFactory that looks something like this:
The BITMAPINO would contain the bitmapinfoheader and the color table. If
pbits is NULL the surface would be allocated, if not then pbits itself would be
used as the surface.
IDIBSurfaceFactory
interface IDIBSurfaceFactory
{
HRESULT CreateBitmapSurface(BITMAPINFO *pbmi, void *pbits);
}
This contains the compatibility issues with Front Page, Internet Explorer and anything else we need to document.
This section discusses things that are flags for User Ed or PSS e.g. user can remove bullet formatting by using outdent (not intuitive) etc. Not every spec will have items in this section.
PaulWu will fill in this section. Note any potential localization issues you think of when writing the spec.
Version 0.2
Added a few clarifications based on feedback from BrendanD and WHsu.
Version 0.3
Many changes from mmpowwow feedback:
- Explained why IBounds isn't a generic interface and clarified it's usage
- Clarified the semantics of Intersect/UnionBounds and IsEqual, suggested CompareBounds as an alternative name
- Added GetFlags call to IBitmapSurface, still not sure if this is needed or even a good idea
- Clarified that GetBounds returns a tearoff interface that is independent of the surface object
- Added SURFACE_ATTRIBUTES_REQUIRED flag to the dwFlags parameter of CreateBitmapSurface
- Added SetCount to IRGBColorTable interface
- The unsigned width and height on CreateBitmapSurface were changed to int for compatibility with rectangle coordinates everwhere.