午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPF PngBitmapEncoderを使ってpng形式で画像保存するとdpiが変化してしまう

MemoryStreamにPngBitmapEncoderでSaveしたのを、BitmapFrame.CreateでBitmapに復元して確認した

結果

変化しているのを確認

sourceDpiが元BitmapSourceのDpiで96
sourcePFが元BitmapSourceのピクセルフォーマット
png、bmp, tiff, gifがそれぞれのEncoderによる結果のピクセルフォーマットとDpi

まとめると

まとめ
Dpiが変化してしまっているエンコーダはpngとBmp
Dpiが変化していないのはTiffとGifだけど、Gifは96固定みたいで96以外でもすべて96に変化してた。ってことはDpiに関してはTiffでエンコードすれば良い

ピクセルフォーマットは変化していないほうが少ないけど、ここでもTiffが一番いい
Pbgra32はWPF独自っぽいから全滅なのかも、Pbgra32がBgra32に変換されると半透明の色が誤差程度だけど変化してしまうのが痛い
それ以外の変換は上位互換みたいなものだから問題なさそう




環境

  • Windows 10 Home バージョン 22H2
  • Visual Studio Community 2026 Version 18.7.2
  • WPF
  • C#
  • .NET 10.0




テストアプリのコード

2026WPF/20260628_PngDpi at master · gogowaten/2026WPF · GitHub

github.com



using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace _20260628_PngDpi
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DpiTest();
        }

        private void DpiTest()
        {
            // 1x1ピクセルのBitmapSource作成
            byte[] pixels = [200, 1, 1, 1];
            //BitmapSource source = BitmapSource.Create(1, 1, 96.0, 96.0, PixelFormats.Pbgra32, null, pixels, 4);
            //BitmapSource source = BitmapSource.Create(1, 1, 96.0, 96.0, PixelFormats.Bgra32, null, pixels, 4);
            //BitmapSource source = BitmapSource.Create(1, 1, 96.0, 96.0, PixelFormats.Rgb24, null, pixels, 3);
            BitmapSource source = BitmapSource.Create(1, 1, 96.0, 96.0, PixelFormats.Gray8, null, pixels, 4);

            double sourceDpi = source.DpiX;
            var sourcePF = source.Format;
            var png = GetFormatAndDpi(source, new PngBitmapEncoder());
            var bmp = GetFormatAndDpi(source, new BmpBitmapEncoder());
            var tiff = GetFormatAndDpi(source, new TiffBitmapEncoder());
            var gif = GetFormatAndDpi(source, new GifBitmapEncoder());
            //var jpegDpi = GetDpi(source, new JpegBitmapEncoder()); // デコード時にヘッダーエラーとかになる
        }

        private (PixelFormat, double) GetFormatAndDpi(BitmapSource source, BitmapEncoder encoder)
        {
            // 指定エンコーダで変換した後、復元したBitmapSourceのピクセルフォーマットとdpiを返す
            // 変換
            using MemoryStream stream = new();
            encoder.Frames.Add(BitmapFrame.Create(source));
            encoder.Save(stream);

            // 復元
            BitmapFrame bmp = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
            var format = bmp.Format;
            return (bmp.Format, bmp.DpiX);
        }

        //private double GetDpi(BitmapSource source, BitmapEncoder encoder)
        //{
        //    encoder.Frames.Add(BitmapFrame.Create(source));
        //    using MemoryStream stream = new();
        //    encoder.Save(stream);

        //    BitmapDecoder decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
        //    return decoder.Frames[0].DpiX;
        //}

    }
}




感想

個人的に画像は写真画像以外はpngを使うことが多いから、png形式での保存でDpiが変化してしまうのが残念、回避方法は検索しても見つからず、WPFだけでは無理みたい
変化すると言っても誤差みたいなもので、アプリによっては96dpiって表示されるし、実際の画像の表示も問題ないけど、気になる
どこかに説明しているとこないかなあ




関連記事

143日前
gogowaten.hatenablog.com

7年前
半透明画像とWPFのPixelFormats.Pbgra32は相性が良くないかも - 午後わてんのブログ
gogowaten.hatenablog.com