Repository: dotnet/winforms (cs)

Issue:
Broken rendering with BackgroundImage and transparent BackColor
### .NET version

.NET 5.0 - .NET 9.0

### Did it work in .NET Framework?

Yes

### Did it work in any of the earlier releases of .NET Core or .NET 5+?

Works in .NET Framework and .NET Core 3.x

### Issue description

Set a background image and transparent background for a non-root level container object, such as a `GroupBox` or a `Panel`, and add child controls to it. While the background image offset is applied correctly to the child controls, this is not true for the 'foreground' content of the parent, which is reflected in every child control with an incorrect origin.

It apparently affects system rendering only (i.e. when `FlatStyle` is `System`), or when a control has no `FlatStyle` (e.g. a `Panel`).

Consider the example below:

Designer:
<img width="890" height="281" alt="Image" src="https://github.com/user-attachments/assets/ce5a6964-1671-49f3-9c63-9376493f93be" />

Rendering in .NET Framework:
<img width="365" height="235" alt="Image" src="https://github.com/user-attachments/assets/45549f09-d3ee-40ed-97f8-209dda8965cd" />

Rendering in .NET Core/.NET:
<img width="426" height="266" alt="Image" src="https://github.com/user-attachments/assets/1bd693a9-07b9-4072-a01d-402433d905c3" />
(Note the "ghost" group boxes appearing in the panel and other controls. The image itself is rendered correctly in the children even if they are over a more colorful area.)

### Steps to reproduce

See the attachment:

[BackgroundImageIssue.zip](https://github.com/user-attachments/files/21613643/BackgroundImageIssue.zip)


Patch:
diff --git a/src/System.Private.Windows.Core/src/NativeMethods.txt b/src/System.Private.Windows.Core/src/NativeMethods.txt
index 56722d01258..87aeebe5d0c 100644
--- a/src/System.Private.Windows.Core/src/NativeMethods.txt
+++ b/src/System.Private.Windows.Core/src/NativeMethods.txt
@@ -118,10 +118,12 @@ GetSystemMetrics
 GetThreadLocale
 GetViewportExtEx
 GetViewportOrgEx
+GetWindowOrgEx
 GetWindowRect
 GetWindowText
 GetWindowTextLength
 GetWindowThreadProcessId
+GetWorldTransform
 GlobalAlloc
 GlobalFree
 GlobalLock
diff --git a/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HDC.cs b/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HDC.cs
index 6c5b27b507d..819e03a250e 100644
--- a/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HDC.cs
+++ b/src/System.Private.Windows.Core/src/Windows/Win32/Graphics/Gdi/HDC.cs
@@ -1,10 +1,31 @@
 ﻿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Drawing;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
 namespace Windows.Win32.Graphics.Gdi;
 
 internal readonly partial struct HDC : IHandle<HDC>
 {
     HDC IHandle<HDC>.Handle => this;
     object? IHandle<HDC>.Wrapper => null;
+
+    internal Point GetViewPointOrigin() =>
+        PInvokeCore.GetViewportOrgEx(this, out Point point) ? point : Point.Empty;
+
+    internal Point GetWindowOrigin() =>
+        PInvokeCore.GetWindowOrgEx(this, out Point point) ? point : Point.Empty;
+
+    internal Matrix3x2 GetWorldTransform()
+    {
+        if (PInvokeCore.GetWorldTransform(this, out XFORM matrix))
+        {
+            return Unsafe.As<XFORM, Matrix3x2>(ref matrix);
+        }
+
+        // If we can't get the transform, return the identity matrix.
+        return Matrix3x2.Identity;
+    }
 }
diff --git a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt
index dec3b0588de..180585d643a 100644
--- a/src/System.Windows.Forms.Primitives/src/NativeMethods.txt
+++ b/src/System.Windows.Forms.Primitives/src/NativeMethods.txt
@@ -224,7 +224,6 @@ GetWindow
 GetWindowDisplayAffinity
 GetWindowDpiAwarenessContext
 GetWindowPlacement
-GetWorldTransform
 GMR_*
 HC_*
 HDHITTESTINFO
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Rendering/ControlPaint.cs b/src/System.Windows.Forms/System/Windows/Forms/Rendering/ControlPaint.cs
index 2c9c7bfbdae..925ac0abd43 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Rendering/ControlPaint.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Rendering/ControlPaint.cs
@@ -462,6 +462,13 @@ internal static void DrawBackgroundImage(
             }
 
             g.FillRectangle(textureBrush, clipRect);
+
+            // If the Graphics backing HDC has an offset origin (SetViewportOrgEx), drawing with a texture brush will
+            // reset it. Getting the HDC and releasing it will restore the offset.
+            //
+            // See https://github.com/dotnet/winforms/issues/13784 for a repro.
+            g.GetHdc();
+            g.ReleaseHdc();
         }
         else
         {
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Rendering/ScreenDcCache.cs b/src/System.Windows.Forms/System/Windows/Forms/Rendering/ScreenDcCache.cs
index f7912c6bcba..20e75dfadd0 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Rendering/ScreenDcCache.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Rendering/ScreenDcCache.cs
@@ -111,7 +111,7 @@ private static unsafe void ValidateHdc(HDC hdc)
         Debug.Assert(PInvoke.GetBkMode(hdc) == BACKGROUND_MODE.OPAQUE);
 
         Matrix3x2 matrix = default;
-        Debug.Assert(PInvoke.GetWorldTransform(hdc, (XFORM*)(void*)&matrix));
+        Debug.Assert(PInvokeCore.GetWorldTransform(hdc, (XFORM*)(void*)&matrix));
         Debug.Assert(matrix.IsIdentity);
     }
 }


Test patch:
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/DeviceContextState.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/DeviceContextState.cs
index 160c6d891a6..93e22893e85 100644
--- a/src/System.Windows.Forms.Primitives/tests/TestUtilities/DeviceContextState.cs
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/DeviceContextState.cs
@@ -32,9 +32,7 @@ public DeviceContextState(HDC hdc)
         TextAlign = PInvoke.GetTextAlign(hdc);
         BackgroundMode = PInvoke.GetBkMode(hdc);
 
-        Matrix3x2 transform = default;
-        PInvoke.GetWorldTransform(hdc, (XFORM*)(void*)&transform);
-        Transform = transform;
+        Transform = hdc.GetWorldTransform();
 
         Point point = default;
         PInvoke.GetBrushOrgEx(hdc, &point);