/************************************************************************** * * Copyright (c) 2000 Microsoft Corporation * * Module Name: * * * * Abstract: * * * * Notes: * * * * 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 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( reinterpret_cast(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; iX = 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 }