How can I save an IWICBitmap to an in-memory buffer, and load one from a buffer, using Direct2D?

simonx 126 Reputation points
2024-11-29T22:59:51.9333333+00:00

I am using Direct2D. I sometimes need to work with IWICBitmap objects (Direct2D's device-independent bitmaps). I need to be able to save them to an in-memory buffer, and re-create them from a buffer. I have asked Github Copilot how to do this and it has come up with numerous suggestions but none of them work. I feel like this should be easy, but I have not found a way to do it. Can someone either give me the code to do this, or point me to a code sample, please. All help very much appreciated. Thank you.

Visual Studio
Visual Studio
A family of Microsoft suites of integrated development tools for building applications for Windows, the web and mobile devices.
5,261 questions
C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,785 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Castorix31 86,311 Reputation points
    2024-11-30T09:21:53.49+00:00

    A way is with IWICBitmap::Lock

    then

    IWICImagingFactory::CreateBitmapFromMemory

    A test by graying a WIC Bitmap (should use Direct2D Effects, this is just for a test with IWICBitmap pixels) :

    void ProcessImage(IWICImagingFactory* pWICFactory, IWICBitmap* pSourceWICBitmap)
    {
    	IWICBitmapLock* pLock = nullptr;
    	WICRect rect = { 0, 0, 0, 0 };
    	pSourceWICBitmap->GetSize((UINT*)&rect.Width, (UINT*)&rect.Height);
    	HRESULT hr = pSourceWICBitmap->Lock(&rect, WICBitmapLockRead, &pLock);
    	if (SUCCEEDED(hr))
    	{
    		UINT nStride = 0;
    		UINT nBufferSize = 0;
    		BYTE* pBuffer = nullptr;		
    		pLock->GetStride(&nStride);
    		pLock->GetDataPointer(&nBufferSize, &pBuffer);
    
    		// Test by modifying pixels (graying)
    		ConvertToGrayscale(pBuffer, rect.Width, rect.Height, nStride);
    
    		pLock->Release();
    
    		// Create new IWICBitmap from modified pixels
    		IWICBitmap* pNewWICBitmap = nullptr;
    		hr = pWICFactory->CreateBitmapFromMemory(rect.Width, rect.Height,
    			GUID_WICPixelFormat32bppBGRA,
    			nStride,
    			rect.Height * nStride,
    			pBuffer,
    			&pNewWICBitmap
    		);
    		if (SUCCEEDED(hr))
    		{
    			// Could return pNewWICBitmap, code to convert to ID2D2Bitmap1 (m_pD2DBitmap)	 which is rendered
    			IWICFormatConverter* pFormatConverter = nullptr;
    			HRESULT hr = pWICFactory->CreateFormatConverter(&pFormatConverter);
    			if (SUCCEEDED(hr))
    			{				
    				hr = pFormatConverter->Initialize(
    					pNewWICBitmap,
    					GUID_WICPixelFormat32bppPBGRA, // Compatible format for ID2D2Bitmap1
    					WICBitmapDitherTypeNone,
    					nullptr,
    					0.0,
    					WICBitmapPaletteTypeCustom);
    				if (SUCCEEDED(hr)) 
    				{					
    					hr = m_pD2DDeviceContext->CreateBitmapFromWicBitmap(pFormatConverter, nullptr, &m_pD2DBitmap);
    				}
    				pFormatConverter->Release();	
    			}
    			pNewWICBitmap->Release();
    		}
    	}
    }
    
    void ConvertToGrayscale(BYTE* pPixels, UINT nWidth, UINT nHeight, UINT nStride)
    {
    	for (UINT nY = 0; nY < nHeight; ++nY)
    	{
    		BYTE* row = pPixels + nY * nStride;
    		for (UINT nX = 0; nX < nWidth; ++nX)
    		{
    			BYTE* pixel = row + nX * 4; // Assuming 32bppBGRA
    			BYTE r = pixel[2];
    			BYTE g = pixel[1];
    			BYTE b = pixel[0];
    			BYTE gray = static_cast<BYTE>(0.299 * r + 0.587 * g + 0.114 * b);
    			pixel[0] = pixel[1] = pixel[2] = gray;
    		}
    	}
    }
    
    

  2. Darran Rowe 1,046 Reputation points
    2024-12-02T15:02:25.8466667+00:00

    Here is a quick and dirty example of showing how to create an ID2D1Bitmap from an IWICBitmapSource using CopyPixels. This method will work for IWICBitmap because the IWICBitmap interface inherits from IWICBitmapSource. However, it shouldn't be that hard to change the code loading the D2D bitmap from IWICBitmalLock.

    	winrt::com_ptr<ID2D1Bitmap> create_from_wic_buffer(IWICBitmapSource *source, IWICImagingFactory *factory, ID2D1DeviceContext *device_context)
    	{
    		UINT width{};
    		UINT height{};
    		//Since we are aiming to create a D2D bitmap with the format DXGI_FORMAT_B8G8R8A8_UNORM
    		//then we know in advance that this is a 32 bits per pixel/4 bytes per pixel format.
    		const uint32_t bytes_per_pixel = 4;
    		uint32_t stride{};
    		uint32_t buffer_size{};
    		WICPixelFormatGUID format{};
    
    		winrt::check_hresult(source->GetSize(&width, &height));
    		winrt::check_hresult(source->GetPixelFormat(&format));
    
    		winrt::com_ptr<IWICFormatConverter> format_converter;
    		winrt::check_hresult(factory->CreateFormatConverter(format_converter.put()));
    		//This the image needs to be in a format that corresponds to one of the DXGI_FORMAT_* BGR formats.
    		//Just blindly convert here, but an optimisation could be to check the format to see if it needs
    		//converting.
    		BOOL convertable{};
    		winrt::check_hresult(format_converter->CanConvert(format, GUID_WICPixelFormat32bppBGRA, &convertable));
    		if (!convertable)
    		{
    			throw winrt::hresult_invalid_argument(L"Image format not convertable to B8G8R8A8");
    		}
    
    		winrt::check_hresult(format_converter->Initialize(source, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, nullptr, 0.f, WICBitmapPaletteTypeCustom));
    
    		//Since this is going into memory and there is no special requirements, just set the stride to width * bytes_per_pixel.
    		stride = width * bytes_per_pixel;
    		buffer_size = width * height * bytes_per_pixel;
    		std::unique_ptr<BYTE[]> pixel_buffer = std::make_unique<BYTE[]>(buffer_size);
    
    		winrt::check_hresult(format_converter->CopyPixels(nullptr, stride, buffer_size, pixel_buffer.get()));
    
    		winrt::com_ptr<ID2D1Bitmap> d2d_bitmap;
    		winrt::check_hresult(device_context->CreateBitmap(D2D1::SizeU(width, height), pixel_buffer.get(), stride, D2D1::BitmapProperties(D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)), d2d_bitmap.put()));
    
    		return d2d_bitmap;
    	}
    

    This is using C++/WinRT for the COM utilities since the smart pointers and error checking are convenient.

    As a side note, if the question was more of a going backwards from an ID2D1Bitmap to an IWICBitmap, then using ID2D1Bitmap1 will make things easier.

    	winrt::com_ptr<IWICBitmap> create_from_d2d1_bitmap(IWICBitmapSource *source, IWICImagingFactory *factory, ID2D1DeviceContext *device_context)
    	{
    		WICPixelFormatGUID format{};
    		winrt::check_hresult(source->GetPixelFormat(&format));
    
    		winrt::com_ptr<IWICFormatConverter> format_converter;
    		winrt::check_hresult(factory->CreateFormatConverter(format_converter.put()));
    		//This the image needs to be in a format that corresponds to one of the DXGI_FORMAT_* BGR formats.
    		//Just blindly convert here, but an optimisation could be to check the format to see if it needs
    		//converting.
    		BOOL convertable{};
    		winrt::check_hresult(format_converter->CanConvert(format, GUID_WICPixelFormat32bppBGRA, &convertable));
    		if (!convertable)
    		{
    			throw winrt::hresult_invalid_argument(L"Image format not convertable to B8G8R8A8");
    		}
    
    		winrt::check_hresult(format_converter->Initialize(source, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, nullptr, 0.f, WICBitmapPaletteTypeCustom));
    
    		winrt::com_ptr<ID2D1Bitmap1> bitmap;
    		winrt::check_hresult(device_context->CreateBitmapFromWicBitmap(source, D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_CPU_READ | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)), bitmap.put()));
    
    		auto bitmap_size = bitmap->GetSize();
    		D2D1_MAPPED_RECT mapped_rect{};
    		
    		uint32_t uwidth = static_cast<uint32_t>(bitmap_size.width);
    		uint32_t uheight = static_cast<uint32_t>(bitmap_size.height);
    		winrt::com_ptr<IWICBitmap> memory_bitmap;
    
    		winrt::check_hresult(bitmap->Map(D2D1_MAP_OPTIONS_READ, &mapped_rect));
    		
    		//The stride/pitch is (bitmap width * bytes per pixel) + additional space.
    		//Since we don't control this, use the bitmap supplied stride. Since
    		//the stride is width * bytes per pixel, we just need to multiply it
    		//by height.
    		uint32_t buffer_size = uheight * mapped_rect.pitch;
    
    		winrt::check_hresult(factory->CreateBitmapFromMemory(uwidth, uheight, GUID_WICPixelFormat32bppBGRA, mapped_rect.pitch, buffer_size, mapped_rect.bits, memory_bitmap.put()));
    
    		winrt::check_hresult(bitmap->Unmap());
    		return memory_bitmap;
    	}
    

    The biggest thing to remember is that the bitmap must be ID2D1Bitmap1 and it must be created as ID2D1Bitmap1 since the D2D1_BITMAP_OPTIONS_CPU_READ flag must be used. However, ID2D1Bitmap1 inherits from the ID2D1Bitmap interface, so it is possible to copy from an ID2D1Bitmap using CopyFromBitmap.This is just one example, since there are other options available, like ID2D1Factory::CreateWicBitmapRenderTarget. In the end, it is usually the pixel format or stride which causes issues.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.