J’ai un BitmapImage WPF que j’ai chargé à partir d’un fichier .JPG, comme suit:
this.m_image1.Source = new BitmapImage(new Uri(path));
Je veux demander quelle est la couleur à des points spécifiques. Par exemple, quelle est la valeur RVB au pixel (65,32)?
Comment puis-je m’y prendre? Je suivais cette approche:
ImageSource ims = m_image1.Source; BitmapImage bitmapImage = (BitmapImage)ims; int height = bitmapImage.PixelHeight; int width = bitmapImage.PixelWidth; int nSsortingde = (bitmapImage.PixelWidth * bitmapImage.Format.BitsPerPixel + 7) / 8; byte[] pixelByteArray = new byte[bitmapImage.PixelHeight * nSsortingde]; bitmapImage.CopyPixels(pixelByteArray, nSsortingde, 0);
Bien que je vous avoue qu’il y a un peu de singe-voir, le singe continue avec ce code. Quoi qu’il en soit, existe-t-il un moyen simple de traiter ce tableau d’octets à convertir en valeurs RVB?
L’interprétation du tableau d’octets résultant dépend du format de pixel du bitmap source, mais dans le cas le plus simple d’une image ARGB 32 bits, chaque pixel sera composé de quatre octets dans le tableau d’octets. Le premier pixel serait interprété ainsi:
alpha = pixelByteArray[0]; red = pixelByteArray[1]; green = pixelByteArray[2]; blue = pixelByteArray[3];
Pour traiter chaque pixel de l’image, vous souhaiterez probablement créer des boucles nestedes pour parcourir les lignes et les colonnes, en incrémentant une variable d’index du nombre d’octets de chaque pixel.
Certains types de bitmap combinent plusieurs pixels en un seul octet. Par exemple, une image monochrome contient huit pixels dans chaque octet. Si vous avez besoin de traiter des images autres que 24/32 bits par pixel (les simples), alors je suggère de trouver un bon livre qui couvre la structure binary sous-jacente des bitmaps.
Voici comment je manipulerais les pixels en C # en utilisant des tableaux multidimensionnels:
[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; }
usage:
var pixels = GetPixels(image); if(pixels[7, 3].Red > 4) ...
Si vous souhaitez mettre à jour les pixels, un code très similaire fonctionne, sauf que vous allez créer un WritableBitmap
et l’utiliser:
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); }
ainsi:
var pixels = new PixelColor[4, 3]; pixel[2,2] = new PixelColor { Red=128, Blue=0, Green=255, Alpha=255 }; PutPixels(bitmap, pixels, 7, 7);
Notez que ce code convertit les bitmaps en Bgra32 s’ils arrivent dans un format différent. Ceci est généralement rapide, mais dans certains cas, il peut s’agir d’un goulot d’étranglement, auquel cas cette technique serait modifiée pour mieux correspondre au format d’entrée sous-jacent.
Mettre à jour
Comme BitmapSource.CopyPixels
n’accepte pas un tableau à deux dimensions, il est nécessaire de convertir le tableau en une dimension et en deux dimensions. La méthode d’extension suivante devrait faire l’affaire:
public static class BitmapSourceHelper { #if UNSAFE public unsafe static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int ssortingde, 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), ssortingde); } #else public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int ssortingde, int offset) { var height = source.PixelHeight; var width = source.PixelWidth; var pixelBytes = new byte[height * width * 4]; source.CopyPixels(pixelBytes, ssortingde, 0); int y0 = offset / width; int x0 = offset - width * y0; for(int y=0; y
Il y a deux implémentations ici: la première est rapide mais utilise un code non sécurisé pour obtenir un IntPtr dans un tableau (doit comstackr avec l'option / unsafe). Le second est plus lent mais ne nécessite pas de code dangereux. J'utilise la version non sécurisée dans mon code.
WritePixels accepte les tableaux à deux dimensions, de sorte qu'aucune méthode d'extension n'est requirejse.
J’aimerais append à la réponse de Ray que vous pouvez également déclarer struct PixelColor comme une union:
[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; }
De cette façon, vous aurez également access à l’UInit32 BGRA (pour un access ou une copie rapide des pixels), en plus des composants d’octets individuels.
Je voudrais améliorer la réponse de Ray – pas assez de rep pour commenter. > 🙁 Cette version a le meilleur des deux, sécurisée / gérée, et l’efficacité de la version non sécurisée. En outre, la documentation .Net pour CopyPixels dit que c’est le bitmap, pas Comme le tableau PixelColor doit avoir le même rythme que le bitmap (pour pouvoir le faire en un seul appel de copie), il est logique de créer un nouveau tableau dans la fonction aussi facile que tarte.
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 ssortingde = 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, ssortingde); pinnedPixels.Free(); return pixels; }
J’ai pris tous les exemples et en ai créé un meilleur – je l’ai aussi testé
(le seul défaut était cette magie 96 comme DPI qui m’a vraiment interpellé)
J’ai également comparé cette tactique WPF à:
À mon supprise,
Cela fonctionne x10 plus rapidement que GDI, et environ x15 fois plus vite que Interop.
Donc, si vous utilisez WPF, mieux vaut travailler avec cela pour obtenir votre couleur de pixel.
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; } }
Si vous voulez juste une couleur de pixel:
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; }
Une petite remarque:
Si vous essayez d’utiliser ce code (Edit: fourni par Ray Burns), mais obtenez l’erreur concernant le classement du tableau, essayez de modifier les méthodes d’extension comme suit:
public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int ssortingde, int offset, bool dummy)
puis appelez la méthode CopyPixels
comme ceci:
source.CopyPixels(result, width * 4, 0, false);
Le problème est que, lorsque la méthode d’extension ne diffère pas de l’original, celle d’origine est appelée. Je suppose que c’est parce que PixelColor[,]
correspond également à Array
.
J’espère que cela vous aidera si vous avez le même problème.
Vous pouvez obtenir des composants de couleur dans un tableau d’octets. Copiez d’abord les pixels en 32 bits dans un tableau et convertissez-les en un tableau à 8 bits avec une taille 4 fois supérieure
int[] pixelArray = new int[ssortingde * source.PixelHeight]; source.CopyPixels(pixelArray, ssortingde, 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...]