You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
607 lines
18 KiB
607 lines
18 KiB
/**************************************************************************
|
|
*
|
|
* Copyright (c) 2000 Microsoft Corporation
|
|
*
|
|
* Module Name:
|
|
*
|
|
* <an unabbreviated name for the module (not the filename)>
|
|
*
|
|
* Abstract:
|
|
*
|
|
* <Description of what this module does>
|
|
*
|
|
* Notes:
|
|
*
|
|
* <optional>
|
|
*
|
|
* Created:
|
|
*
|
|
* 04/23/2000 asecchia
|
|
* Created it.
|
|
*
|
|
**************************************************************************/
|
|
|
|
#include "precomp.hpp"
|
|
|
|
/**************************************************************************
|
|
*
|
|
* Function Description:
|
|
*
|
|
* This function renders the GpCachedBitmap on the GpGraphics.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* inputCachedBitmap - the input data.
|
|
* x, y - destination offset.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* Returns Ok if successful
|
|
* Returs WrongState if the GpGraphics and GpCachedBitmap have
|
|
* different pixel formats.
|
|
*
|
|
* Created:
|
|
*
|
|
* 04/23/2000 asecchia
|
|
* Created it.
|
|
*
|
|
**************************************************************************/
|
|
GpStatus
|
|
GpGraphics::DrvDrawCachedBitmap(
|
|
GpCachedBitmap *inputCachedBitmap,
|
|
INT x,
|
|
INT y
|
|
)
|
|
{
|
|
// Internally we must be called with a valid object.
|
|
|
|
ASSERT(inputCachedBitmap->IsValid());
|
|
|
|
// Don't attempt to record a cached bitmap to a metafile
|
|
if (IsRecording())
|
|
return WrongState;
|
|
|
|
// First grab the device lock so that we can protect all the
|
|
// non-reentrant code in the DpScanBuffer class.
|
|
|
|
Devlock devlock(Device);
|
|
|
|
// Check the world transform
|
|
|
|
if(!(Context->WorldToDevice.IsTranslate()))
|
|
{
|
|
// There is a complex transform selected into the graphics.
|
|
// fail the call.
|
|
return WrongState;
|
|
}
|
|
|
|
// Can't check the pixel format here because of the possibility of
|
|
// MultiMon - we'd be checking against the meta device surface
|
|
// -- although maybe that's the correct behaviour.
|
|
|
|
// Set up the world to device translation offset.
|
|
|
|
INT xOffset = x+GpRound(Context->WorldToDevice.GetDx());
|
|
INT yOffset = y+GpRound(Context->WorldToDevice.GetDy());
|
|
|
|
// Store the rendering origin so we can restore it later.
|
|
|
|
INT renderX, renderY;
|
|
GetRenderingOrigin(&renderX, &renderY);
|
|
|
|
// Set the rendering origin to the origin of the CachedBitmap drawing
|
|
// so that the dither matrix offset of the semi-transparent pixels
|
|
// matches that of the already dithered (stored) native pixels.
|
|
|
|
SetRenderingOrigin(xOffset, yOffset);
|
|
|
|
// Call the driver to draw the cached bitmap.
|
|
// Note: the driver does not respect the world to device transform
|
|
// it expects device coordinates for this API.
|
|
|
|
GpStatus status = Driver->DrawCachedBitmap(
|
|
Context,
|
|
&inputCachedBitmap->DeviceCachedBitmap,
|
|
Surface,
|
|
xOffset,
|
|
yOffset
|
|
);
|
|
|
|
// Restore the rendering origin.
|
|
|
|
SetRenderingOrigin(renderX, renderY);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**************************************************************************
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Constructs the GpCachedBitmap based on the pixel format and
|
|
* rendering quality derived from the GpGraphics and the bits
|
|
* from the GpBitmap.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* graphics - input graphics to be compatible with
|
|
* bitmap - data to be cached.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* Created:
|
|
*
|
|
* 04/23/2000 asecchia
|
|
* Created it.
|
|
*
|
|
**************************************************************************/
|
|
|
|
|
|
enum TransparencyState {
|
|
Transparent,
|
|
SemiTransparent,
|
|
Opaque
|
|
};
|
|
|
|
inline TransparencyState GetTransparency(ARGB pixel)
|
|
{
|
|
if((pixel & 0xff000000) == 0xff000000) {return Opaque;}
|
|
if((pixel & 0xff000000) == 0x00000000) {return Transparent;}
|
|
return SemiTransparent;
|
|
}
|
|
|
|
// Intermetiate record used to parse the transparency information
|
|
// in the bitmap.
|
|
|
|
struct ScanRecordTemp
|
|
{
|
|
// Pointer to the start and end of the run.
|
|
|
|
ARGB *pStart;
|
|
ARGB *pEnd; // exclusive end.
|
|
|
|
// Transparency
|
|
|
|
TransparencyState tsTransparent;
|
|
|
|
// Position
|
|
|
|
INT x, y;
|
|
INT width; // in pixels
|
|
};
|
|
|
|
// Simple macro to make the failure cases easier to read.
|
|
|
|
#define FAIL() \
|
|
SetValid(FALSE); \
|
|
return
|
|
|
|
GpCachedBitmap::GpCachedBitmap(GpBitmap *bitmap, GpGraphics *graphics)
|
|
{
|
|
BitmapData bmpDataSrc;
|
|
|
|
// Lock the bits.
|
|
// we need to convert the bits to the appropriate format.
|
|
|
|
// Note - we're assuming that the knowledgeable user will be
|
|
// initializing their CachedBitmap with a 32bppPARGB surface.
|
|
// This is important for performance at creation time, because
|
|
// the LockBits below can avoid a costly clone & convert format.
|
|
|
|
if (bitmap == NULL ||
|
|
!bitmap->IsValid() ||
|
|
bitmap->LockBits(
|
|
NULL,
|
|
IMGLOCK_READ,
|
|
PIXFMT_32BPP_PARGB,
|
|
&bmpDataSrc
|
|
)
|
|
!= Ok)
|
|
{
|
|
FAIL();
|
|
}
|
|
|
|
// copy the dimensions.
|
|
|
|
DeviceCachedBitmap.Width = bmpDataSrc.Width;
|
|
DeviceCachedBitmap.Height = bmpDataSrc.Height;
|
|
|
|
// Create a dynamic array to store the transparency transition points.
|
|
// The initial allocation size is based on an estimate of 3
|
|
// transition events per scanline. Overestimate.
|
|
|
|
DynArray<ScanRecordTemp> RunData;
|
|
|
|
RunData.ReserveSpace(4*DeviceCachedBitmap.Height);
|
|
|
|
// Scan through the bits adding an item to the dynamic list every
|
|
// time a new run would be required in the output - i.e. when the
|
|
// transparency changes to one of opaque or semi-transparent.
|
|
// Before the beginning of a scanline is considered to be transparent.
|
|
// While scanning through the bits, keep a running total of the
|
|
// size of the final RLE bitmap - so we know how much space to allocate.
|
|
|
|
// Size of the final RLE bitmap in bytes;
|
|
|
|
// This is not actually a pointer to an EpScanRecord - it is simply
|
|
// a mechanism to work out how big to allocate the buffer
|
|
|
|
EpScanRecord *RLESize = (EpScanRecord *)0;
|
|
|
|
// Pointer to the current position in the source.
|
|
|
|
ARGB *src;
|
|
ARGB *runStart;
|
|
|
|
// Temporary ScanRecord used to accumulate the runs.
|
|
|
|
ScanRecordTemp sctTmp;
|
|
|
|
TransparencyState tsThisPixel;
|
|
TransparencyState tsCurrentRun;
|
|
|
|
|
|
DpBitmap *Surface = graphics->GetSurface();
|
|
void *Buffers[5];
|
|
|
|
// Create the blending buffers for the alpha blender.
|
|
|
|
// !!! [asecchia] While we don't currently depend on this behaviour,
|
|
// it seems like a bug that a graphics wrapped around a bitmap uses
|
|
// the desktop display device and driver. This leads to weirdness
|
|
// if we try use GetScanBuffers to derive the pixel format of the
|
|
// destination. For example a graphics around a PixelFormat24bppRGB
|
|
// bitmap would return PixelFormat16bppRGB565 if the screen is in
|
|
// 16bpp mode !?
|
|
// However, the Buffers[] returned will be allocated as 64bpp buffers
|
|
// and therefore will have the correct properties under all circumstances.
|
|
|
|
if (!graphics->GetDriver()->Device->GetScanBuffers(
|
|
Surface->Width,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
Buffers)
|
|
)
|
|
{
|
|
FAIL();
|
|
}
|
|
|
|
// Compute the destination pixel format.
|
|
|
|
PixelFormatID dstFormat = Surface->PixelFormat;
|
|
|
|
if(dstFormat == PixelFormatUndefined) { FAIL(); }
|
|
|
|
// The following destination formats are not supported.
|
|
// In particular, no palettized modes are supported because
|
|
// tracking the cached opaque data in native format across
|
|
// palette changes would be a nightmare.
|
|
// Instead we set the opaque format to be 32bppRGB and force
|
|
// the EpAlphaBlender to do the work at render time - slower
|
|
// but it will work.
|
|
|
|
if( !EpAlphaBlender::IsSupportedPixelFormat(dstFormat) ||
|
|
IsIndexedPixelFormat(dstFormat) ||
|
|
|
|
// If the graphics is for a multi format device such as
|
|
// a multimon meta-screen.
|
|
|
|
dstFormat == PixelFormatMulti
|
|
)
|
|
{
|
|
// We don't want to silently fail if the input
|
|
// is undefined.
|
|
|
|
ASSERT(dstFormat != PixelFormatUndefined);
|
|
|
|
// Opaque pixels - we don't support palettized modes
|
|
// and some of the other weird ones
|
|
|
|
dstFormat = PixelFormat32bppRGB;
|
|
}
|
|
|
|
// Size of a destination pixel in bytes.
|
|
|
|
INT dstFormatSize = GetPixelFormatSize(dstFormat) >> 3;
|
|
|
|
|
|
// Run through each scanline.
|
|
|
|
for(INT y = 0; y < DeviceCachedBitmap.Height; y++)
|
|
{
|
|
// Point to the beginning of this scanline.
|
|
|
|
src = reinterpret_cast<ARGB*>(
|
|
reinterpret_cast<BYTE*>(bmpDataSrc.Scan0) + y * bmpDataSrc.Stride
|
|
);
|
|
|
|
// Start the run off with transparency.
|
|
|
|
tsCurrentRun = Transparent;
|
|
runStart = src;
|
|
|
|
// Run through all the pixels in this scanline.
|
|
|
|
for(INT x = 0; x < DeviceCachedBitmap.Width; x++)
|
|
{
|
|
|
|
// Compute the transparency state for the current pixel.
|
|
|
|
tsThisPixel = GetTransparency(*src);
|
|
|
|
// If a transparency transition occurs,
|
|
|
|
if(tsThisPixel != tsCurrentRun)
|
|
{
|
|
// Close off the last transition and store a record if it wasn't
|
|
// a transparent run.
|
|
|
|
if(tsCurrentRun != Transparent)
|
|
{
|
|
sctTmp.pStart = runStart;
|
|
sctTmp.pEnd = src;
|
|
sctTmp.tsTransparent = tsCurrentRun;
|
|
sctTmp.y = y;
|
|
|
|
// src is in PixelFormat32bppPARGB so we can divide the
|
|
// pointer difference by 4 to figure out the number of
|
|
// pixels in this run.
|
|
|
|
sctTmp.width = (INT)(((INT_PTR)src - (INT_PTR)runStart)/sizeof(ARGB));
|
|
sctTmp.x = x - sctTmp.width;
|
|
|
|
if(RunData.Add(sctTmp) != Ok) { FAIL(); }
|
|
|
|
// Add the size of the record
|
|
|
|
if (tsCurrentRun == SemiTransparent)
|
|
{
|
|
// This is the semi-transparent case.
|
|
|
|
RLESize = EpScanRecord::CalculateNextScanRecord(
|
|
RLESize,
|
|
EpScanTypeBlend,
|
|
sctTmp.width,
|
|
sizeof(ARGB)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// This is the opaque case.
|
|
|
|
ASSERT(tsCurrentRun == Opaque);
|
|
|
|
RLESize = EpScanRecord::CalculateNextScanRecord(
|
|
RLESize,
|
|
EpScanTypeOpaque,
|
|
sctTmp.width,
|
|
dstFormatSize
|
|
);
|
|
}
|
|
}
|
|
|
|
// Update the run tracking variables.
|
|
|
|
runStart = src;
|
|
tsCurrentRun = tsThisPixel;
|
|
}
|
|
|
|
// Look at the next pixel.
|
|
|
|
src++;
|
|
}
|
|
|
|
// Close off the last run for this scanline (if it's not transparent).
|
|
|
|
if(tsCurrentRun != Transparent)
|
|
{
|
|
sctTmp.pStart = runStart;
|
|
sctTmp.pEnd = src;
|
|
sctTmp.tsTransparent = tsCurrentRun;
|
|
sctTmp.y = y;
|
|
|
|
// Size of the source is 32bits (PARGB).
|
|
|
|
sctTmp.width = (INT)(((INT_PTR)src - (INT_PTR)runStart)/sizeof(ARGB));
|
|
sctTmp.x = x - sctTmp.width;
|
|
if(RunData.Add(sctTmp)!=Ok) { FAIL(); }
|
|
|
|
// Add the size of the record
|
|
|
|
if (tsCurrentRun == SemiTransparent)
|
|
{
|
|
// This is the semi-transparent case.
|
|
|
|
RLESize = EpScanRecord::CalculateNextScanRecord(
|
|
RLESize,
|
|
EpScanTypeBlend,
|
|
sctTmp.width,
|
|
sizeof(ARGB)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// This is the opaque case.
|
|
|
|
ASSERT(tsCurrentRun == Opaque);
|
|
|
|
RLESize = EpScanRecord::CalculateNextScanRecord(
|
|
RLESize,
|
|
EpScanTypeOpaque,
|
|
sctTmp.width,
|
|
dstFormatSize
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(RLESize >= 0);
|
|
|
|
// Allocate space for the RLE bitmap.
|
|
// This should be the exact size required for the RLE bitmap.
|
|
// Add 8 bytes to handle the fact that GpMalloc may not return
|
|
// a 64bit aligned allocation.
|
|
|
|
void *RLEBits = GpMalloc((INT)(INT_PTR)(RLESize)+8);
|
|
if(RLEBits == NULL) { FAIL(); } // Out of memory.
|
|
|
|
// QWORD-align the result
|
|
|
|
EpScanRecord *recordStart = MAKE_QWORD_ALIGNED(EpScanRecord *, RLEBits);
|
|
|
|
// Scan through the dynamic array and add each record to the RLE bitmap
|
|
// followed by its bits (pixels).
|
|
// For native format pixels (opaque) use the EpAlphaBlender to
|
|
// convert to native format.
|
|
|
|
// Query for the number of records in the RunData
|
|
|
|
INT nRecords = RunData.GetCount();
|
|
|
|
EpScanRecord *rec = recordStart;
|
|
ScanRecordTemp *pscTmp;
|
|
|
|
// Store the rendering origin so we can modify it.
|
|
// The graphics must be locked at the API.
|
|
|
|
INT renderX, renderY;
|
|
graphics->GetRenderingOrigin(&renderX, &renderY);
|
|
|
|
// Set the rendering origin to the top left corner of the CachedBitmap
|
|
// so that the dither pattern for the native pixels will match
|
|
// the dither pattern for the semi-transparent pixels (rendered later
|
|
// in DrawCachedBitmap).
|
|
|
|
graphics->SetRenderingOrigin(0,0);
|
|
|
|
|
|
// Make a 32bppPARGB->Native conversion blender.
|
|
// This will be used for converting the 32bppPARGB
|
|
// source data into the native pixel format (dstFormat)
|
|
// of the destination.
|
|
// For palettized modes, dstFormat is 32bppRGB
|
|
|
|
EpAlphaBlender alphaBlender;
|
|
|
|
alphaBlender.Initialize(
|
|
EpScanTypeOpaque,
|
|
dstFormat,
|
|
PixelFormat32bppPARGB,
|
|
graphics->Context,
|
|
NULL,
|
|
Buffers,
|
|
TRUE,
|
|
FALSE,
|
|
0
|
|
);
|
|
|
|
// For each record ...
|
|
|
|
for(INT i=0; i<nRecords; i++)
|
|
{
|
|
// Make sure we don't overrun our buffer...
|
|
|
|
ASSERT((INT_PTR)rec < (INT_PTR)recordStart + (INT_PTR)RLESize);
|
|
|
|
// Copy the data into the destination record.
|
|
|
|
pscTmp = &RunData[i];
|
|
rec->X = pscTmp->x;
|
|
rec->Y = pscTmp->y;
|
|
rec->Width = rec->OrgWidth = pscTmp->width;
|
|
|
|
// We should never store a transparent run.
|
|
|
|
ASSERT(pscTmp->tsTransparent != Transparent);
|
|
|
|
if(pscTmp->tsTransparent == Opaque)
|
|
{
|
|
// Use the native pixel format for the destination.
|
|
|
|
rec->BlenderNum = 1;
|
|
rec->ScanType = EpScanTypeOpaque;
|
|
|
|
// Find the start of the new pixel run.
|
|
|
|
VOID *dst = rec->GetColorBuffer();
|
|
|
|
// Compute the number of bytes per pixel.
|
|
|
|
INT pixelFormatSize = GetPixelFormatSize(dstFormat) >> 3;
|
|
|
|
// This should perform a source over blend from 32bppPARGB
|
|
// to the native format into the destination.
|
|
|
|
// For CachedBitmaps, the dither origin is always the top
|
|
// left corner of the CachedBitmap (i.e. 0, 0).
|
|
// In 8bpp we dither down while rendering and get the correct
|
|
// origin at that time.
|
|
|
|
alphaBlender.Blend(
|
|
dst,
|
|
pscTmp->pStart,
|
|
pscTmp->width,
|
|
pscTmp->x,
|
|
pscTmp->y,
|
|
NULL
|
|
);
|
|
|
|
// Increment position to the next record.
|
|
|
|
rec = rec->NextScanRecord(pixelFormatSize);
|
|
}
|
|
else
|
|
{
|
|
rec->BlenderNum = 0;
|
|
rec->ScanType = EpScanTypeBlend;
|
|
|
|
// Find the start of the new pixel run.
|
|
|
|
VOID *dst = rec->GetColorBuffer();
|
|
|
|
// Semi-Transparent pixels are stored in 32bpp PARGB, so
|
|
// we can simply copy the pixels.
|
|
|
|
GpMemcpy(dst, pscTmp->pStart, pscTmp->width*sizeof(ARGB));
|
|
|
|
// Increment position to the next record.
|
|
|
|
rec = rec->NextScanRecord(sizeof(ARGB));
|
|
}
|
|
|
|
}
|
|
|
|
// Restore the rendering origin.
|
|
|
|
graphics->SetRenderingOrigin(renderX, renderY);
|
|
|
|
// Finally store the pointer to the RLE bits in the DeviceCachedBitmap
|
|
|
|
DeviceCachedBitmap.Bits = RLEBits;
|
|
DeviceCachedBitmap.RecordStart = recordStart;
|
|
DeviceCachedBitmap.RecordEnd = rec;
|
|
DeviceCachedBitmap.OpaqueFormat = dstFormat;
|
|
DeviceCachedBitmap.SemiTransparentFormat = PixelFormat32bppPARGB;
|
|
|
|
bitmap->UnlockBits(&bmpDataSrc);
|
|
|
|
// Everything is golden - set Valid to TRUE
|
|
// all the error paths early out with Valid set to FALSE
|
|
|
|
SetValid(TRUE);
|
|
}
|
|
|
|
#undef FAIL
|
|
|
|
GpCachedBitmap::~GpCachedBitmap()
|
|
{
|
|
// throw away the cached bitmap storage.
|
|
|
|
GpFree(DeviceCachedBitmap.Bits);
|
|
|
|
SetValid(FALSE); // so we don't use a deleted object
|
|
}
|
|
|