{"id":110224,"date":"2024-09-05T07:00:00","date_gmt":"2024-09-05T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110224"},"modified":"2024-09-05T12:16:09","modified_gmt":"2024-09-05T19:16:09","slug":"20240905-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240905-00\/?p=110224","title":{"rendered":"The case of the image that came out horribly slanted: Taking the pitch into account"},"content":{"rendered":"<p>Last time, <a title=\"The case of the image that came out horribly slanted: Diagnosis\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240904-00\/?p=110216\"> we recognized from the specific corruption in a bitmap that the problem was a mismatched stride<\/a>. Here&#8217;s the code that they used to convert from an <code>ID2D1Bitmap1<\/code> to a GDI <code>HBITMAP<\/code>. Their strategy was to use the <code>ID2D1Bitmap1::Map<\/code> method to get access to the D2D bitmap&#8217;s pixel memory, and then use <code>SetDIBits<\/code> to write those pixels into the GDI <code>HBITMAP<\/code>.<\/p>\n<pre>HRESULT D2D1BitmapToGdiBitmap(\r\n    ID2D1Bitmap1* d2dBitmap,\r\n    HDC hdc,\r\n    HBITMAP* result)\r\n{\r\n    *result = nullptr;\r\n\r\n    \/\/ Verify that it's in PARGB format\r\n    assert(d2dBitmap-&gt;GetPixelFormat().format ==\r\n            DXGI_FORMAT_B8G8R8A8_UNORM &amp;&amp;\r\n           d2dBitmap-&gt;GetPixelFormat().alphaMode ==\r\n            DXGI_ALPHA_MODE_PREMULTIPLIED);\r\n\r\n    auto size = d2dBitmap-&gt;GetPixelSize();\r\n\r\n    \/\/ Get a pointer to the pixel memory for reading.\r\n    D2D1_MAPPED_RECT rect;\r\n    RETURN_IF_FAILED(d2dBitmap-&gt;Map(D2D1_MAP_OPTIONS_READ, &amp;rect);\r\n\r\n    \/\/ Make sure we unmap even on early exit.\r\n    auto unmap = wil::ScopeExit([&amp;]() { d2dBitmap-&gt;Unmap(); });\r\n\r\n    \/\/ Create a GDI HBITMAP of matching size.\r\n    wil::unique_hbitmap gdiBitmap(\r\n        CreateCompatibleBitmap(hdc, size.width, size.height));\r\n    RETURN_IF_NULL_ALLOC(gdiBitmap);\r\n\r\n    \/\/ D2D bitmaps are top-down, but GDI defaults to bottom-up.\r\n    \/\/ A negative height indicates that the pixels are top-down.\r\n    BITMAPINFO bmi{};\r\n    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);\r\n    bmi.bmiHeader.biPlanes = 1;\r\n    bmi.bmiHeader.biBitCount = 32; \/\/ B8G8R8A8_UNORM is 32bpp\r\n    bmi.bmiHeader.biCompression = BI_RGB;\r\n    bmi.bmiHeader.biHeight = -static_cast&lt;LONG&gt;(size.height);\r\n    <span style=\"border: solid 1px currentcolor;\">bmi.bmiHeader.biWidth = size.width;<\/span>\r\n\r\n    RETURN_IF_WIN32_BOOL_FALSE(SetDIBits(hdc, gdiBitmap.get(),\r\n        0, size.height, rect.bits, &amp;bmi; DIB_RGB_COLORS));\r\n\r\n    *result = gdiBitmap.release();\r\n    return S_OK;\r\n}\r\n<\/pre>\n<p>Notice that this code never uses the <code>rect.pitch<\/code>. It assumes that the stride of the D2D bitmap is the same as the stride of the GDI bitmap.<\/p>\n<p>GDI&#8217;s requirement for bitmap stride is that the stride is always a multiple of four, specifically, the smallest multiple of four that can hold all the bytes of a single row of pixels. This code assumed that GDI and D2D agreed on the bitmap stride, and when it was run on a system whose video driver wanted stricter alignment, you got the stretched-and-slanted distorted result that is a signature of a stride bitmap.<\/p>\n<p>We can fix the problem here by telling GDI that we have an artificially wide source bitmap, so that the extra source pixels occupy the padding bytes in the D2D bitmap.<\/p>\n<table style=\"border-collapse: collapse; text-align: center;\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td style=\"width: 50px; text-align: left;\">\u2190<\/td>\n<td style=\"width: 350px; text-align: center;\">Fake width<\/td>\n<td style=\"width: 50px; text-align: right;\">\u2192<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 370px; border: solid 1px currentcolor;\" colspan=\"2\"><code>width<\/code> pixels<\/td>\n<td style=\"width: 80px; border: solid 1px currentcolor;\">Padding<\/td>\n<td style=\"padding-left: 1em;\">D2D bitmap<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 370px;\" colspan=\"2\">\u2193<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 370px; border: solid 1px currentcolor;\" colspan=\"2\"><code>width<\/code> pixels<\/td>\n<td>&nbsp;<\/td>\n<td style=\"padding-left: 1em;\">GDI bitmap<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The output bitmap only has room for <code>width<\/code> pixels, so the pixels represented by the padding get clipped out. And now that we got the strides to match, each row of pixels in the D2D bitmap starts where GDI expects them to be.<\/p>\n<pre>HRESULT D2D1BitmapToGdiBitmap(\r\n    ID2D1Bitmap1* d2dBitmap,\r\n    HDC hdc,\r\n    HBITMAP* result)\r\n{\r\n    *result = nullptr;\r\n\r\n    \/\/ Verify that it's in PARGB format\r\n    assert(d2dBitmap-&gt;GetPixelFormat().format ==\r\n            DXGI_FORMAT_B8G8R8A8_UNORM &amp;&amp;\r\n           d2dBitmap-&gt;GetPixelFormat().alphaMode ==\r\n            DXGI_ALPHA_MODE_PREMULTIPLIED);\r\n\r\n    auto size = d2dBitmap-&gt;GetPixelSize();\r\n\r\n    \/\/ Get a pointer to the pixel memory for reading.\r\n    D2D1_MAPPED_RECT rect;\r\n    RETURN_IF_FAILED(d2dBitmap-&gt;Map(D2D1_MAP_OPTIONS_READ, &amp;rect);\r\n\r\n    \/\/ Make sure we unmap even on early exit.\r\n    auto unmap = wil::ScopeExit([&amp;]() { d2dBitmap-&gt;Unmap(); });\r\n\r\n    \/\/ Create a GDI HBITMAP of matching size.\r\n    wil::unique_hbitmap gdiBitmap(\r\n        CreateCompatibleBitmap(hdc, size.width, size.height));\r\n    RETURN_IF_NULL_ALLOC(gdiBitmap);\r\n\r\n    \/\/ D2D bitmaps are top-down, but GDI defaults to bottom-up.\r\n    \/\/ A negative height indicates that the pixels are top-down.\r\n    BITMAPINFO bmi{};\r\n    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);\r\n    bmi.bmiHeader.biPlanes = 1;\r\n    bmi.bmiHeader.biBitCount = 32; \/\/ B8G8R8A8_UNORM is 32bpp\r\n    bmi.bmiHeader.biCompression = BI_RGB;\r\n    bmi.bmiHeader.biHeight = -static_cast&lt;LONG&gt;(size.height);\r\n    <span style=\"border: solid 1px currentcolor;\">bmi.bmiHeader.biWidth = rect.pitch \/ 4;<\/span> \/\/ 4 bytes per pixel\r\n\r\n    RETURN_IF_WIN32_BOOL_FALSE(SetDIBits(hdc, gdiBitmap.get(),\r\n        0, size.height, rect.bits, &amp;bmi; DIB_RGB_COLORS));\r\n\r\n    *result = gdiBitmap.release();\r\n    return S_OK;\r\n}\r\n<\/pre>\n<p>The math turned out easy because a 32bpp bitmap has 4 bytes per pixel, and GDI&#8217;s stride is a multiple of 4, so reversing <a href=\"https:\/\/learn.microsoft.com\/windows\/win32\/api\/wingdi\/ns-wingdi-bitmapinfoheader#calculating-surface-stride\"> the formula for GDI stride<\/a> comes out easy:<\/p>\n<p><var>stride<\/var> = ROUNDUP(<var>biWidth<\/var> \u00d7 <var>biBitCount<\/var>, 32) \/ 8<br \/>\n<var>stride<\/var> = ROUNDUP(<var>biWidth<\/var> \u00d7 32, 32) \/ 8<br \/>\n<var>stride<\/var> = <var>biWidth<\/var> \u00d7 32 \/ 8<br \/>\n<var>stride<\/var> = <var>biWidth<\/var> \u00d7 4<br \/>\n<var>biWidth<\/var> = <var>stride<\/var> \/ 4<\/p>\n<p>More generally, then the math tells us that the GDI bitmap width to produce a specific stride is \u230a<var>stride<\/var> \u00d7 8 \/ <var>biBitCount<\/var>\u230b (This works only if the stride is a multiple of four. If it&#8217;s not, then you&#8217;ll never get it to match GDI, since GDI stride is always a multiple of four.)<\/p>\n<p>But wait, what if the stride is negative?<\/p>\n<p>Whaaaaaat? \ud83e\udd2f<\/p>\n<p>We&#8217;ll look at negative stride next time.<\/p>\n<p><b>Bonus chatter<\/b>: A member of the Direct2D team suggested that instead of transferring the bits manually, create a GDI-compatible render target, say via <code>CreateDCRenderTarget<\/code> (with <code>D2D1_<wbr \/>RENDER_<wbr \/>TARGET_<wbr \/>USAGE_<wbr \/>GDI_<wbr \/>COMPATIBLE<\/code> in addition to the existing <code>DXGI_<wbr \/>FORMAT_<wbr \/>B8G8R8A8_<wbr \/>UNORM<\/code> and <code>DXGI_<wbr \/>ALPHA_<wbr \/>MODE_<wbr \/>PREMULTIPLIED<\/code>), render to that render target, and then use <code>ID2D1Gdi\u00adInterop\u00adRender\u00adTarget::<wbr \/>Get\u00adDC<\/code> to get an <code>HDC<\/code> to <code>BitBlt<\/code> from. Now you don&#8217;t have to worry about stride. Let D2D deal with it for you.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Getting the pieces to line up.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-110224","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Getting the pieces to line up.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110224","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=110224"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110224\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=110224"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110224"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110224"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}