How to improve image quality when painting images with Gdiplus::TextureBrush
I am currently having a problem with broken image drawing
when I use Gdiplus::TextureBrush.
I know that when drawing images with Graphics::DrawImage, the image still maintains the image quality.
But I need to draw images with Gdiplus::TextureBrush because it can be customized to draw images in more ways (eg: repeat images, rotate images, draw according to matrix....). But the problem here is that when drawing images with Gdiplus::TextureBrush, the image quality will be low. Can you help me improve the image quality when drawing with Gdiplus::TextureBrush? Thank you
Windows API - Win32
-
Junjie Zhu - MSFT • 20,696 Reputation points • Microsoft External Staff
2025-03-04T06:13:13.78+00:00 Hello @Chau Dang Ba ,
Welcome to Microsoft Q&A!
I used TextureBrush to render images and did not observe deterioration in image quality. Could you please provide your code about TextureBrush to help us further check the problem for you?
Thank you
-
Chau Dang Ba • 20 Reputation points
2025-03-04T07:26:41.6866667+00:00 HRESULT ContentWriter::CreateBrush(
IN IXpsOMImageBrush* pInBrush, IN Gdiplus::TextureBrush** pOutBrush)
{
HRESULT hr = S_FALSE; IXpsOMImageResource* pImageResource = NULL; XPS_IMAGE_TYPE imageType = XPS_IMAGE_TYPE_JPEG; XpsString decodedImageFilePath; IStream* pImageStream = NULL; IXpsOMMatrixTransform* pBrushTransform = NULL; XpsMatrix brushMatrix; XPS_RECT viewBox; XPS_RECT viewPort; XPS_TILE_MODE tileMode = XPS_TILE_MODE_NONE; do { hr = pInBrush->GetImageResource(&pImageResource); if (!SUCCEEDED(hr)) { break; } hr = pImageResource->GetImageType(&imageType); if (!SUCCEEDED(hr)) { break; } hr = pImageResource->GetStream(&pImageStream); if (!SUCCEEDED(hr) || !pImageStream) { break; } if (imageType == XPS_IMAGE_TYPE_JXR || imageType == XPS_IMAGE_TYPE_WDP) { // convert to normal jpeg IStream* pJpegStream = NULL; if (SUCCEEDED(ConvertJpegXrToJpeg(pImageStream, &pJpegStream, decodedImageFilePath))) { pImageStream->Release(); pImageStream = pJpegStream; } } hr = pInBrush->GetTransformLocal(&pBrushTransform); if (!pBrushTransform) { hr = pInBrush->GetTransform(&pBrushTransform); } hr = pInBrush->GetViewbox(&viewBox); if (!SUCCEEDED(hr)) { break; } hr = pInBrush->GetViewport(&viewPort); if (!SUCCEEDED(hr)) { break; } hr = pInBrush->GetTileMode(&tileMode); if (pBrushTransform) { XPS_MATRIX localMat; hr = pBrushTransform->GetMatrix(&localMat); if (SUCCEEDED(hr)) { brushMatrix = XpsMatrix(localMat); } } Gdiplus::Image* pImage = Gdiplus::Image::FromStream(pImageStream, TRUE); if (pImage) { UINT32 w = pImage->GetWidth(); UINT32 h = pImage->GetHeight(); if (w != UINT32(viewBox.width) || h != UINT32(viewBox.height)) { // crop pImage Gdiplus::Bitmap* bmp = new Gdiplus::Bitmap(viewBox.width, viewBox.height); XpsGraphics g(bmp); g.DrawImage(pImage, 0, 0); delete pImage; pImage = bmp; } } if (pImage) { Gdiplus::TextureBrush* brush = new Gdiplus::TextureBrush(pImage); Gdiplus::Matrix m; brushMatrix.Translate(viewPort.x, viewPort.y); brushMatrix.GetGdiplusMatrix(&m); brush->MultiplyTransform(&m, Gdiplus::MatrixOrderAppend); brush->ScaleTransform(viewPort.width / viewBox.width, viewPort.height / viewBox.height); if (tileMode == XPS_TILE_MODE_TILE) { brush->SetWrapMode(Gdiplus::WrapModeTile); } else if (tileMode == XPS_TILE_MODE_FLIPX) { brush->SetWrapMode(Gdiplus::WrapModeTileFlipX); } else if (tileMode == XPS_TILE_MODE_FLIPY) { brush->SetWrapMode(Gdiplus::WrapModeTileFlipY); } else if (tileMode == XPS_TILE_MODE_FLIPXY) { brush->SetWrapMode(Gdiplus::WrapModeTileFlipXY); } else { brush->SetWrapMode(Gdiplus::WrapModeClamp); } *pOutBrush = brush; delete pImage; } } while (0); if (pImageResource) { pImageResource->Release(); pImageResource = NULL; } if (pImageStream) { pImageStream->Release(); pImageStream = NULL; } if (pBrushTransform) { pBrushTransform->Release(); pBrushTransform = NULL; } if (PathUtils::FileExists(decodedImageFilePath)) { PathUtils::RemoveFile(decodedImageFilePath); } return hr;
}
I am currently converting from XPS to EMF. In this CreateBrush function, when I draw an Image using Gdiplus::TextureBrush, when I open the XPS File and enlarge it, the image quality is low. -
Junjie Zhu - MSFT • 20,696 Reputation points • Microsoft External Staff
2025-03-04T09:02:23.2866667+00:00 Hi @Chau Dang Ba ,
You can export pImageStream both before and after ConvertJpegXrToJpeg to see if it is the image details that are lost in the conversion process.
HRESULT SaveStreamToImage(IStream* pStream, const wchar_t* outputFilePath) { HRESULT hr = S_OK; IWICImagingFactory* pFactory = NULL; IWICBitmapDecoder* pDecoder = NULL; IWICBitmapFrameDecode* pFrame = NULL; IWICStream* pOutputStream = NULL; // Initialize COM CoInitialize(NULL); // Create WIC factory hr = CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFactory) ); if (SUCCEEDED(hr)) { hr = pFactory->CreateDecoderFromStream( pStream, NULL, WICDecodeMetadataCacheOnDemand, &pDecoder ); } if (SUCCEEDED(hr)) { hr = pDecoder->GetFrame(0, &pFrame); } if (SUCCEEDED(hr)) { hr = pFactory->CreateStream(&pOutputStream); } if (SUCCEEDED(hr)) { hr = pOutputStream->InitializeFromFilename(outputFilePath, GENERIC_WRITE); } if (SUCCEEDED(hr)) { IWICBitmapEncoder* pEncoder = NULL; hr = pFactory->CreateEncoder(GUID_ContainerFormatPng, NULL, &pEncoder); if (SUCCEEDED(hr)) { hr = pEncoder->Initialize(pOutputStream, WICBitmapEncoderNoCache); } if (SUCCEEDED(hr)) { IWICBitmapFrameEncode* pFrameEncode = NULL; hr = pEncoder->CreateNewFrame(&pFrameEncode, NULL); if (SUCCEEDED(hr)) { hr = pFrameEncode->Initialize(NULL); if (SUCCEEDED(hr)) { hr = pFrameEncode->WriteSource(pFrame, NULL); } if (SUCCEEDED(hr)) { hr = pFrameEncode->Commit(); } pFrameEncode->Release(); } if (SUCCEEDED(hr)) { hr = pEncoder->Commit(); } pEncoder->Release(); } pOutputStream->Release(); } if (pFrame) { pFrame->Release(); } if (pDecoder) { pDecoder->Release(); } if (pFactory) { pFactory->Release(); } CoUninitialize(); return hr; }
-
Chau Dang Ba • 20 Reputation points
2025-03-05T03:00:00.2233333+00:00 I only use this ConvertJpegXrToJpeg for special cases (imageType == XPS_IMAGE_TYPE_JXR || imageType == XPS_IMAGE_TYPE_WDP)
but I still use pImageStream as the main one. I confirm that the problem is in Gdiplus::TextureBrush
if I use Gdiplus::Graphics::DrawImage directly, the image quality is very good. but when I use Gdiplus::TextureBrush, the image quality will be low.
Here is the image I used Gdiplus::Graphics::DrawImage
Here is the image I used Gdiplus::TextureBrush
-
Chau Dang Ba • 20 Reputation points
2025-03-05T03:10:52.5766667+00:00 ConvertJpegXrToJpeg case I only use for special image case (imageType == XPS_IMAGE_TYPE_JXR || imageType == XPS_IMAGE_TYPE_WDP)I may not need to use this ConvertJpegXrToJpeg function.
I confirm that the problem lies in Gdiplus::TextureBrush. because when I use Gdiplus::Graphics::DrawImage the image is high quality but
Gdiplus::TextureBrush the image is low quality
this is the image that I use Gdiplus::Graphics::DrawImage
this is the image that I use Gdiplus::TextureBrush
-
Junjie Zhu - MSFT • 20,696 Reputation points • Microsoft External Staff
2025-03-05T08:26:46.02+00:00 Hello @Chau Dang Ba ,
TextureBrush does not directly make the image blurry. I used TextureBrush to draw an image in a blank project and it did not lose clarity. It is recommended that you save pImageStream to file multiple times in your code to find which code causes the image to be blurry.
#include <windows.h> #include <gdiplus.h> #include <xpsobjectmodel.h> #include <wincodec.h> using namespace Gdiplus; #pragma comment (lib,"Gdiplus.lib") LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); Graphics graphics(hdc); // Load the image Image image(L"Test.png"); // Create a TextureBrush TextureBrush textureBrush(&image, WrapModeTile); // Set high-quality interpolation mode graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); // Set anti-aliasing graphics.SetSmoothingMode(SmoothingModeAntiAlias); // Define the rectangle to paint Rect rect(0, 0, 2000, 1800); // Paint the image using TextureBrush graphics.FillRectangle(&textureBrush, rect); EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, INT iCmdShow) { GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); // Register the window class const wchar_t CLASS_NAME[] = L"GDI+ TextureBrush Example"; WNDCLASS wc = {}; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = CLASS_NAME; RegisterClass(&wc); // Create the window HWND hwnd = CreateWindowEx(0, CLASS_NAME, L"GDI+ TextureBrush Example", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 2000, 1800, NULL, NULL, hInstance, NULL); if (hwnd == NULL) { return 0; } ShowWindow(hwnd, iCmdShow); MSG msg = {}; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } GdiplusShutdown(gdiplusToken); return 0; }
Sign in to comment