午後わてんのブログ

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

半透明画像とWPFのPixelFormats.Pbgra32は相性が良くないかも

半透明(アルファが255未満)のピクセルがある画像を、FormatConvertedBitmapを使ってPixelFormatをPixelFormats.Pbgra32に変更すると色が変わってしまう
なのでPixelFormatはBgra32が良さそう
 
 
 
半透明の青一色のpng形式の画像
ARGB=100,30,122,224
16進数だと#641E7AE0
イメージ 1
FormatConvertedBitmapで変換してみる
 
MainWindow.xaml

f:id:gogowaten:20191213160616p:plain

 
MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace _20190315_Pixelformats.Pbgra32で色変化
{
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();

            string imagePath = "";
            imagePath = @"D:\ブログ用\テスト用画像\半透明A100R30G122B224.png";


            byte[] pixels;

            BitmapSource bitmapPbgra32;
            (pixels, bitmapPbgra32) = MakeByteArrayAndSourceFromImageFile(imagePath, PixelFormats.Pbgra32);
            var Pbgra32 = Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);
            MyImagePbgra32.Source = bitmapPbgra32;

            BitmapSource bitmapRgra32;
            (pixels, bitmapRgra32) = MakeByteArrayAndSourceFromImageFile(imagePath, PixelFormats.Bgra32);
            var Bgra32 = Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);
            MyImageBgra32.Source = bitmapRgra32;

            BitmapSource source;
            (pixels, source) = MakeByteArrayAndSourceFromImageFile(imagePath, PixelFormats.Bgr32);
            var Bgr32 = Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);

            (pixels, source) = MakeByteArrayAndSourceFromImageFile(imagePath, PixelFormats.Rgb24);
            var Rgb24 = Color.FromRgb(pixels[0], pixels[1], pixels[2]);

            (pixels, source) = MakeByteArrayAndSourceFromImageFile(imagePath, PixelFormats.Bgr24);
            var Bgr24 = Color.FromRgb(pixels[2], pixels[1], pixels[0]);

            BitmapImage bitmapImage = new BitmapImage(new Uri(imagePath));
            var pf = bitmapImage.Format;//Bgra32
            int stride = pf.BitsPerPixel / 8 * bitmapImage.PixelWidth;
            pixels = new byte[stride * bitmapImage.PixelHeight];
            bitmapImage.CopyPixels(pixels, stride, 0);
            var auto = Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);

        }



        /// <summary>
        /// 画像ファイルからbitmapと、そのbyte配列を取得、ピクセルフォーマットを指定したものに変換
        /// </summary>
        /// <param name="filePath">画像ファイルのフルパス</param>
        /// <param name="pixelFormat">PixelFormatsを指定</param>
        /// <param name="dpiX">96が基本、指定なしなら元画像と同じにする</param>
        /// <param name="dpiY">96が基本、指定なしなら元画像と同じにする</param>
        /// <returns></returns>
        private (byte[] array, BitmapSource source) MakeByteArrayAndSourceFromImageFile(string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0)
        {
            byte[] pixels = null;
            BitmapSource source = null;
            try
            {
                using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    var bf = BitmapFrame.Create(fs);

                    var convertedBitmap = new FormatConvertedBitmap(bf, pixelFormat, null, 0);
                    int w = convertedBitmap.PixelWidth;
                    int h = convertedBitmap.PixelHeight;
                    int stride = (w * pixelFormat.BitsPerPixel + 7) / 8;
                    pixels = new byte[h * stride];
                    convertedBitmap.CopyPixels(pixels, stride, 0);
                    //dpi指定がなければ元の画像と同じdpiにする
                    if (dpiX == 0) { dpiX = bf.DpiX; }
                    if (dpiY == 0) { dpiY = bf.DpiY; }
                    //dpiを指定してBitmapSource作成
                    source = BitmapSource.Create(
                        w, h, dpiX, dpiY,
                        convertedBitmap.Format,
                        convertedBitmap.Palette, pixels, stride);
                };
            }
            catch (Exception)
            {
            }
            return (pixels, source);
        }


    }
}
 
 
					A	R	G	B
元の色	#641E7AE0	100,	30,	122,	224
Formats		色
Pbgra32	#640C3058	100,	12,	48,	88
Bgra32	#641E7AE0	100,	30,	122,	224
Bgr32	#641E7AE0	100,	30,	122,	224
Rgb24	#FF1E7AE0	255,	30,	122,	224
Bgr24	#FF1E7AE0	255,	30,	122,	224

Pbgra32だけ変色している
Rgb24とBgr24の透明度AもFFに変化しているけど
24bitカラーはAがないので完全不透明の値FFであっている、期待通り
 
イメージ 2
一覧
 

でも、これを表示してみると
イメージ 3 
なぜか正しく表示される
なので半透明画像を扱うときはPbgra32じゃなくてBgra32を使ったほうが良さそう
 
配列の中を覗いてみる
イメージ 5
Pbgra32
違う、その色じゃない
 
 
イメージ 6
Bgra32
ほー、いいじゃないか
こういうのでいいんだよ、こういうので(ご満悦)
 
そもそもBitmapImageを使って普通に読み込めば自動でBgra32になるんだけど
今までわざわざPbgra32に変換して読み込んでいた
最初はPbgra32とBgra32どちらもARGBそれぞれに8bitずつで合計32bit(bpp)と、全く同じなんだけど、どこが違うんだろうと検索していたら、Pbgra32のほうが処理が少し速いってのが英語であって、理解できなかったけど早いに越したことはないと思って使っていた
見た目は期待通りに問題なく表示されるから気が付かなかったんだよねえ

FormatConvertedBitmapとか使ってよけいなことしないで
イメージ 7
普通にBitmapImageにファイルパスを渡して普通に読み込んでいればよかった、44行目
これなら自動で選んでくれたのがBgra32になる、45行目
 
もう一回どう変化しているのか見てみると
透明度のAは変化せず、RGBの値が一律4割に減っている感じ
					A	R	G	B
元の色	#641E7AE0	100,	30,	122,	224
Formats		色
Pbgra32	#640C3058	100,	12,	48,	88
R=12/30=0.4
R=0.4、G=0.39344262、B=0.39285714
この4割ってのはAの最大値255に対するAの値と同じ割合で
100/255=0.39215686
なんだよねえ
なんか関係あるのかもしれないけどPbgra32はもう使わないからいいや
でも今まで散々使ってきたから、それらを修正したいけど気が遠くなる
 
 
ギットハブ