C#のWinformsでグレースケールの画像を作成しフォームに表示する。

コンピュータ

グレスケールの画像をピクセル単位で操作するプログラムを作成する予定がありまして、目的のプログラムを作成する前に動作確認用にフォームに画像を表示するプログラムを作成します。

using System.Data.SqlTypes;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace GrayForm01;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        const int width = 300;
        const int height = 256;
        const int channel = 1;

        const int stride = (width * (8 * channel) + 31) / 32 * 4;
        Debug.Print("width:{0} stride:{1}", width, stride);

        byte[] pixels = new byte[stride * height];

        for(int y=0;y < height;y++)
        {
            byte c = (byte)((y > 255) ? y - 255 : y); // 黒から白へグラデーション
            for(int x=0; x < width;x++)
            {
                int i = (y * stride) + (x * channel);
                pixels[i] = c;
            }
        }

        Bitmap bmp = new(width, height, PixelFormat.Format8bppIndexed);
        Rectangle rect = new (0, 0, bmp.Width, bmp.Height);
        BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);
        Marshal.Copy(pixels, 0, bmpData.Scan0, pixels.Length);
        bmp.UnlockBits(bmpData);

        // グレースケール用パレットの設定
        ColorPalette palette = bmp.Palette;
        for(int i = 0; i < 256; i++)
        {
            palette.Entries[i] = Color.FromArgb(i, i, i);
        }
        bmp.Palette = palette;

        this.BackgroundImage = bmp;
        this.BackgroundImageLayout = ImageLayout.Center;
    }
}

実行します。

いつもはPicuteBoxを用意してそちらに画像を表示していますが、今回はFormの背景に画像をセットしています。

まずpixelをセットする空のbyte配列を用意し、上から下へ色の値を増やすことで黒から白へグラデーションするようにプログラムを組みました。

strideはメモリ上データの配置が4の倍数になるように調整しているようです。
ちなみにwidthが300の場合strideも300ですが、widthを297にした場合でもstrideは300になります。
stride * heightが確保するメモリの容量になります。
widthが4の倍数で無い場合、メモリに無駄な隙間が出来ますが、多分4バイトだとコンピュータにとって都合が良い何か(パフォーマンスが向上するとか)があると思われます。

y座標とx座標の数値をループで加算しながら、各ピクセルにアクセスしますが、xy座標からメモリのインデックスを算出する場合、以下の様な計算をしています。

int i = (y * stride) + (x * channel);

yに掛ける値は座標であればwidthですがメモリですのでstrideになります。またx座標にchannelを掛けていますが、グレスケールの場合channelが1ですので省略しても良いです。

このあとLinqを使ってbyte配列を処理しようかと思ったのですが、筆者には難しそうな気がしてきました。

コメント