次のように、.JPGファイルから読み込んだWPF BitmapImageがあります。
this.m_image1.Source = new BitmapImage(new Uri(path));
特定のポイントで色が何であるかを照会したい。たとえば、ピクセル(65,32)のRGB値は何ですか?
これについてどうすればいいですか?私はこのアプローチを取っていました:
ImageSource ims = m_image1.Source;
BitmapImage bitmapImage = (BitmapImage)ims;
int height = bitmapImage.PixelHeight;
int width = bitmapImage.PixelWidth;
int nStride = (bitmapImage.PixelWidth * bitmapImage.Format.BitsPerPixel + 7) / 8;
byte[] pixelByteArray = new byte[bitmapImage.PixelHeight * nStride];
bitmapImage.CopyPixels(pixelByteArray, nStride, 0);
私は少し猿を見ていると告白しますが、猿はこのコードを続けています。とにかく、このバイト配列を処理してRGB値に変換する簡単な方法はありますか?
結果のバイト配列の解釈は、ソースビットマップのピクセル形式に依存しますが、32ビットのARGB画像の最も単純な場合、各ピクセルはバイト配列の4バイトで構成されます。したがって、最初のピクセルは次のように解釈されます。
alpha = pixelByteArray[0];
red = pixelByteArray[1];
green = pixelByteArray[2];
blue = pixelByteArray[3];
画像内の各ピクセルを処理するには、ネストされたループを作成して行と列をウォークし、各ピクセルのバイト数だけインデックス変数を増やしたいと思うでしょう。
一部のビットマップタイプは、複数のピクセルを1バイトに結合します。たとえば、モノクロイメージでは、各バイトに8ピクセルがパックされます。 24/32ビット/ピクセル(単純なもの)以外の画像を扱う必要がある場合は、ビットマップの基礎となるバイナリ構造をカバーする優れた本を見つけることをお勧めします。
多次元配列を使用してC#でピクセルを操作する方法を次に示します。
[StructLayout(LayoutKind.Sequential)]
public struct PixelColor
{
public byte Blue;
public byte Green;
public byte Red;
public byte Alpha;
}
public PixelColor[,] GetPixels(BitmapSource source)
{
if(source.Format!=PixelFormats.Bgra32)
source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
int width = source.PixelWidth;
int height = source.PixelHeight;
PixelColor[,] result = new PixelColor[width, height];
source.CopyPixels(result, width * 4, 0);
return result;
}
使用法:
var pixels = GetPixels(image);
if(pixels[7, 3].Red > 4)
...
ピクセルを更新する場合、WritableBitmap
を作成してこれを使用することを除いて、非常に類似したコードが機能します。
public void PutPixels(WritableBitmap bitmap, PixelColor[,] pixels, int x, int y)
{
int width = pixels.GetLength(0);
int height = pixels.GetLength(1);
bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, width*4, x, y);
}
したがって:
var pixels = new PixelColor[4, 3];
pixel[2,2] = new PixelColor { Red=128, Blue=0, Green=255, Alpha=255 };
PutPixels(bitmap, pixels, 7, 7);
このコードは、ビットマップが異なる形式で到着した場合、Bgra32に変換することに注意してください。これは一般に高速ですが、場合によってはパフォーマンスのボトルネックになる可能性があります。その場合、この手法は、基礎となる入力形式により一致するように変更されます。
更新
BitmapSource.CopyPixels
は2次元配列を受け入れません。1次元配列と2次元配列を変換する必要があります。次の拡張メソッドでトリックを行う必要があります。
public static class BitmapSourceHelper
{
#if UNSAFE
public unsafe static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset)
{
fixed(PixelColor* buffer = &pixels[0, 0])
source.CopyPixels(
new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight),
(IntPtr)(buffer + offset),
pixels.GetLength(0) * pixels.GetLength(1) * sizeof(PixelColor),
stride);
}
#else
public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset)
{
var height = source.PixelHeight;
var width = source.PixelWidth;
var pixelBytes = new byte[height * width * 4];
source.CopyPixels(pixelBytes, stride, 0);
int y0 = offset / width;
int x0 = offset - width * y0;
for(int y=0; y<height; y++)
for(int x=0; x<width; x++)
pixels[x+x0, y+y0] = new PixelColor
{
Blue = pixelBytes[(y*width + x) * 4 + 0],
Green = pixelBytes[(y*width + x) * 4 + 1],
Red = pixelBytes[(y*width + x) * 4 + 2],
Alpha = pixelBytes[(y*width + x) * 4 + 3],
};
}
#endif
}
ここには2つの実装があります。最初の実装は高速ですが、安全でないコードを使用してIntPtrを配列に取得します(/ unsafeオプションでコンパイルする必要があります)。 2番目は遅いですが、安全でないコードを必要としません。私はコードで安全でないバージョンを使用しています。
WritePixelsは2次元配列を受け入れるため、拡張メソッドは不要です。
Rayの答えに加えて、PixelColor構造体を共用体として宣言することもできます。
[StructLayout(LayoutKind.Explicit)]
public struct PixelColor
{
// 32 bit BGRA
[FieldOffset(0)] public UInt32 ColorBGRA;
// 8 bit components
[FieldOffset(0)] public byte Blue;
[FieldOffset(1)] public byte Green;
[FieldOffset(2)] public byte Red;
[FieldOffset(3)] public byte Alpha;
}
また、個々のバイトコンポーネントに加えて、UInit32 BGRA(高速ピクセルアクセスまたはコピー用)にもアクセスできます。
レイの答えを改善したい-コメントするのに十分な担当者がいない。 > :(このバージョンは、安全/管理されたバージョンと、安全でないバージョンの効率の両方のベストを持っています。また、CopyPixelsの.Net文書ではビットマップのストライドではなくPixelColor配列はビットマップと同じストライドでなければならないので(単一のコピー呼び出しとして実行できるように)、新しいものを作成するだけの意味があります。関数内の配列も、パイのように簡単です。
public static PixelColor[,] CopyPixels(this BitmapSource source)
{
if (source.Format != PixelFormats.Bgra32)
source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
PixelColor[,] pixels = new PixelColor[source.PixelWidth, source.PixelHeight];
int stride = source.PixelWidth * ((source.Format.BitsPerPixel + 7) / 8);
GCHandle pinnedPixels = GCHandle.Alloc(pixels, GCHandleType.Pinned);
source.CopyPixels(
new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight),
pinnedPixels.AddrOfPinnedObject(),
pixels.GetLength(0) * pixels.GetLength(1) * 4,
stride);
pinnedPixels.Free();
return pixels;
}
私はすべての例を取り上げ、わずかに良い例を作成しました-それもテストしました
(唯一の欠陥は、DPIとしての魔法96で、本当に私を悩ませました)
また、このWPFの戦術と以下を比較しました。
私のサプライズに、
これは、GDIよりも10倍高速で、Interopの約15倍高速です。
したがって、WPFを使用している場合、これを使用してピクセルカラーを取得する方がはるかに優れています。
public static class GraphicsHelpers
{
public static readonly float DpiX;
public static readonly float DpiY;
static GraphicsHelpers()
{
using (var g = Graphics.FromHwnd(IntPtr.Zero))
{
DpiX = g.DpiX;
DpiY = g.DpiY;
}
}
public static Color WpfGetPixel(double x, double y, FrameworkElement AssociatedObject)
{
var renderTargetBitmap = new RenderTargetBitmap(
(int)AssociatedObject.ActualWidth,
(int)AssociatedObject.ActualHeight,
DpiX, DpiY, PixelFormats.Default);
renderTargetBitmap.Render(AssociatedObject);
if (x <= renderTargetBitmap.PixelWidth && y <= renderTargetBitmap.PixelHeight)
{
var croppedBitmap = new CroppedBitmap(
renderTargetBitmap, new Int32Rect((int)x, (int)y, 1, 1));
var pixels = new byte[4];
croppedBitmap.CopyPixels(pixels, 4, 0);
return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);
}
return Colors.Transparent;
}
}
ピクセルの色を1つだけにする場合:
using System.Windows.Media;
using System.Windows.Media.Imaging;
...
public static Color GetPixelColor(BitmapSource source, int x, int y)
{
Color c = Colors.White;
if (source != null)
{
try
{
CroppedBitmap cb = new CroppedBitmap(source, new Int32Rect(x, y, 1, 1));
var pixels = new byte[4];
cb.CopyPixels(pixels, 4, 0);
c = Color.FromRgb(pixels[2], pixels[1], pixels[0]);
}
catch (Exception) { }
}
return c;
}
ちょっとした発言:
このコード(編集:Ray Burns提供)を使用しようとしているが、配列のランクに関するエラーが発生する場合は、拡張メソッドを次のように編集してみてください。
public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset, bool dummy)
次のようにCopyPixels
メソッドを呼び出します:
source.CopyPixels(result, width * 4, 0, false);
問題は、拡張メソッドが元のメソッドと変わらない場合、元のメソッドが呼び出されることです。これは、PixelColor[,]
はArray
にも一致します。
同じ問題が発生した場合に役立つことを願っています。
バイト配列の色成分を取得できます。最初に32ビットのピクセルを配列にコピーし、4倍のサイズの8ビット配列に変換します
int[] pixelArray = new int[stride * source.PixelHeight];
source.CopyPixels(pixelArray, stride, 0);
// byte[] colorArray = new byte[pixelArray.Length];
// EDIT:
byte[] colorArray = new byte[pixelArray.Length * 4];
for (int i = 0; i < colorArray.Length; i += 4)
{
int pixel = pixelArray[i / 4];
colorArray[i] = (byte)(pixel >> 24); // alpha
colorArray[i + 1] = (byte)(pixel >> 16); // red
colorArray[i + 2] = (byte)(pixel >> 8); // green
colorArray[i + 3] = (byte)(pixel); // blue
}
// colorArray is an array of length 4 times more than the actual number of pixels
// in the order of [(ALPHA, RED, GREEN, BLUE), (ALPHA, RED...]