WindowsXP-SP1/windows/advcore/gdiplus/engine/entry/cachedbitmap.cpp
2020-09-30 16:53:49 +02:00

608 lines
18 KiB
C++

/**************************************************************************
*
* 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
}