diff options
| author | Valentin Popov <valentin@popov.link> | 2026-11-01 08:45:59 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-11-01 08:45:59 +0300 |
| commit | f771a62298dd935a1ec6f746b738a2a82aab352d (patch) | |
| tree | 313d5f4dab44fa2dfdf4f0d90f0917f00e9be7e3 | |
| parent | 6c603976d4e368708935ab80e152b495a5c5e47a (diff) | |
| download | fparkan-f771a62298dd935a1ec6f746b738a2a82aab352d.tar.xz fparkan-f771a62298dd935a1ec6f746b738a2a82aab352d.zip | |
fix(tools): preserve opaque framebuffer RGB in captures
| -rw-r--r-- | docs/reference/render-frame.md | 6 | ||||
| -rw-r--r-- | tools/native-frame-capture/README.md | 6 | ||||
| -rw-r--r-- | tools/native-frame-capture/compare-frames.ps1 | 26 | ||||
| -rw-r--r-- | tools/native-frame-capture/convert-viewer-readback.ps1 | 14 |
4 files changed, 40 insertions, 12 deletions
diff --git a/docs/reference/render-frame.md b/docs/reference/render-frame.md index df3f248..80796a3 100644 --- a/docs/reference/render-frame.md +++ b/docs/reference/render-frame.md @@ -348,3 +348,9 @@ D3D7 в `fparkan-render`, а затем один раз меняет знак в каналов не определяет. Последовательность не содержит структурированных ID видимых объектов, списка элементов отрисовки, ключей конвейера, матриц или хэшей промежуточных буферов. + +При преобразовании кадра в PNG сохраняется исходный RGB, а прозрачность +выключается: окно показывает готовое непрозрачное изображение. Альфа-канал +GPU может содержать результат смешивания поверхностей; повторное применение +его к готовому цвету затемнило бы кадр ещё раз. Поэтому конвертер и сборщик +сравнительных пар игнорируют эту альфу, а raw-файл сохраняют без изменений. diff --git a/tools/native-frame-capture/README.md b/tools/native-frame-capture/README.md index d8f621c..1f7166b 100644 --- a/tools/native-frame-capture/README.md +++ b/tools/native-frame-capture/README.md @@ -117,6 +117,12 @@ the verified value for the run above, not a universal surface format. The converter uses the installed `System.Drawing` PNG encoder and adds no package dependency. +PNG exports treat the captured frame as opaque and preserve its stored RGB. +Framebuffer alpha is a rendering result, not image transparency; applying it +again when displaying the PNG would darken surfaces a second time. Raw files +retain all four original channels. The comparison script also ignores alpha +in older PNG exports, without changing the measured RGB values. + ## Сравнение областей кадра `compare-frames.ps1` собирает пару «оригинал / FParkan» без масштабирования и diff --git a/tools/native-frame-capture/compare-frames.ps1 b/tools/native-frame-capture/compare-frames.ps1 index 2a83bf0..b8d3855 100644 --- a/tools/native-frame-capture/compare-frames.ps1 +++ b/tools/native-frame-capture/compare-frames.ps1 @@ -41,7 +41,21 @@ if ($SelfTest) { try { Get-MeanRgb $test ([System.Drawing.Rectangle]::new(1, 0, 2, 1)) | Out-Null } catch { $rejected = $true } if (-not $rejected) { throw 'Out-of-bounds region was accepted.' } - Write-Output 'self-test: exact region RGB means and bounds rejection OK' + $test.SetPixel(0, 0, [System.Drawing.Color]::FromArgb(0, 31, 16, 24)) + $test.SetPixel(1, 0, [System.Drawing.Color]::FromArgb(64, 31, 16, 24)) + $opaque = $test.Clone([System.Drawing.Rectangle]::new(0, 0, 2, 1), [System.Drawing.Imaging.PixelFormat]::Format32bppRgb) + $pair = [System.Drawing.Bitmap]::new(2, 1) + $graphics = [System.Drawing.Graphics]::FromImage($pair) + try { + $graphics.Clear([System.Drawing.Color]::Black) + $graphics.DrawImageUnscaled($opaque, 0, 0) + foreach ($x in @(0, 1)) { + if ($pair.GetPixel($x, 0).ToArgb() -ne [System.Drawing.Color]::FromArgb(31, 16, 24).ToArgb()) { + throw 'Comparison applied framebuffer alpha to stored RGB.' + } + } + } finally { $graphics.Dispose(); $pair.Dispose(); $opaque.Dispose() } + Write-Output 'self-test: exact region RGB means, opaque presentation and bounds rejection OK' } finally { $test.Dispose() } return } @@ -63,19 +77,21 @@ foreach ($value in $viewport) { } $crop = [System.Drawing.Rectangle]::new($viewport[0], $viewport[1], $viewport[2] - $viewport[0], $viewport[3] - $viewport[1]) $native = [System.Drawing.Bitmap]::new((Resolve-Path -LiteralPath $NativePng).Path) -$viewer = $nativeCrop = $pair = $graphics = $null +$viewer = $viewerOpaque = $nativeCrop = $pair = $graphics = $null try { $viewer = [System.Drawing.Bitmap]::new((Resolve-Path -LiteralPath $ViewerPng).Path) if ($crop.Width -le 0 -or $crop.Height -le 0 -or $crop.Right -gt $native.Width -or $crop.Bottom -gt $native.Height -or $viewer.Width -ne $crop.Width -or $viewer.Height -ne $crop.Height) { throw 'Native crop and viewer dimensions must match without scaling.' } - $nativeCrop = $native.Clone($crop, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb) + # Captured frames are opaque presentations; framebuffer alpha is not PNG opacity. + $nativeCrop = $native.Clone($crop, [System.Drawing.Imaging.PixelFormat]::Format32bppRgb) + $viewerOpaque = $viewer.Clone([System.Drawing.Rectangle]::new(0, 0, $viewer.Width, $viewer.Height), [System.Drawing.Imaging.PixelFormat]::Format32bppRgb) $pair = [System.Drawing.Bitmap]::new($viewer.Width * 2, $viewer.Height + 24) $graphics = [System.Drawing.Graphics]::FromImage($pair) $graphics.Clear([System.Drawing.Color]::Black) $graphics.DrawImageUnscaled($nativeCrop, 0, 24) - $graphics.DrawImageUnscaled($viewer, $viewer.Width, 24) + $graphics.DrawImageUnscaled($viewerOpaque, $viewer.Width, 24) $font = [System.Drawing.SystemFonts]::DefaultFont $graphics.DrawString('Original', $font, [System.Drawing.Brushes]::White, 4, 4) $graphics.DrawString('FParkan', $font, [System.Drawing.Brushes]::White, $viewer.Width + 4, 4) @@ -100,7 +116,7 @@ try { ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $jsonPath -Encoding UTF8 Write-Output "Wrote $pngPath and $jsonPath" } finally { - foreach ($resource in @($graphics, $pair, $nativeCrop, $viewer, $native)) { + foreach ($resource in @($graphics, $pair, $nativeCrop, $viewerOpaque, $viewer, $native)) { if ($null -ne $resource) { $resource.Dispose() } } } diff --git a/tools/native-frame-capture/convert-viewer-readback.ps1 b/tools/native-frame-capture/convert-viewer-readback.ps1 index 3bafe14..17c136a 100644 --- a/tools/native-frame-capture/convert-viewer-readback.ps1 +++ b/tools/native-frame-capture/convert-viewer-readback.ps1 @@ -118,7 +118,7 @@ function Save-BgraPngToStream { $bitmap = New-Object System.Drawing.Bitmap( $Width, $Height, - [System.Drawing.Imaging.PixelFormat]::Format32bppArgb + [System.Drawing.Imaging.PixelFormat]::Format32bppRgb ) $rectangle = New-Object System.Drawing.Rectangle(0, 0, $Width, $Height) $bitmapData = $null @@ -126,7 +126,7 @@ function Save-BgraPngToStream { $bitmapData = $bitmap.LockBits( $rectangle, [System.Drawing.Imaging.ImageLockMode]::WriteOnly, - [System.Drawing.Imaging.PixelFormat]::Format32bppArgb + [System.Drawing.Imaging.PixelFormat]::Format32bppRgb ) $rowBytes = $Width * 4 @@ -201,12 +201,12 @@ function Get-CameraExtent { function Invoke-SelfTest { $rgba = [byte[]]@( - 255, 0, 0, 255, 0, 255, 0, 255, - 0, 0, 255, 255, 255, 255, 255, 255 + 255, 0, 0, 0, 0, 255, 0, 64, + 0, 0, 255, 128, 255, 255, 255, 255 ) $expectedBgra = [byte[]]@( - 0, 0, 255, 255, 0, 255, 0, 255, - 255, 0, 0, 255, 255, 255, 255, 255 + 0, 0, 255, 0, 0, 255, 0, 64, + 255, 0, 0, 128, 255, 255, 255, 255 ) $rgbaResult = Convert-ReadbackToBgra -Source $rgba -Format 37 -Width 2 -Height 2 @@ -303,7 +303,7 @@ function Invoke-SelfTest { $stream.Dispose() } - 'Self-check passed (2x2 RGBA/BGRA PNG, byte-length/64 MiB limits, viewport-coordinate bounds, and cropped pixel_capture bounds).' + 'Self-check passed (opaque 2x2 RGBA/BGRA PNG, byte-length/64 MiB limits, viewport-coordinate bounds, and cropped pixel_capture bounds).' } if ($SelfTest) { |
