web-dev-qa-db-fra.com

Recherche de couleurs de pixels spécifiques d'un BitmapImage

J'ai un WPF BitmapImage 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 prenais cette approche:

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);

Bien que je vous avoue qu'il y a un peu de monkey-see, monkey continue avec ce code . Quoi qu'il en soit, y a-t-il un moyen simple de traiter ce tableau d'octets à convertir en valeurs RVB?

39
Andrew Shepherd

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 de 32 bits, chaque pixel sera composé de quatre octets dans le tableau d'octets. Le premier pixel serait interprété comme suit:

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 imbriquées pour parcourir les lignes et les colonnes, en incrémentant une variable d'index du nombre d'octets dans chaque pixel.

Certains types de bitmap combinent plusieurs pixels dans 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 plus simples), je vous suggérerais alors de trouver un bon livre qui couvre la structure binaire sous-jacente des images bitmap.

13

Voici comment je manipulerais des pixels en C # à l'aide de 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 des pixels, un code très similaire fonctionne, sauf que vous allez créer une WritableBitmap et utiliser ceci:

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 si elles arrivent dans un format différent. Cela est généralement rapide, mais peut parfois constituer un goulot d'étranglement au niveau des performances. Dans ce cas, cette technique serait modifiée pour correspondre davantage au format d'entrée sous-jacent.

Mettre à jour

Étant donné que BitmapSource.CopyPixels n'accepte pas un tableau à deux dimensions, il est nécessaire de convertir le tableau en un à 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 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
}

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 être compilé avec l'option/unsafe). La seconde est plus lente mais ne nécessite pas de code non sécurisé. J'utilise la version non sécurisée dans mon code.

WritePixels accepte les tableaux à deux dimensions, aucune méthode d’extension n’est donc requise.

55
Ray Burns

J'aimerais ajouter à la réponse de Ray que vous pouvez également déclarer la structure 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;
}

Et ainsi, vous aurez également accès à la UInit32 BGRA (pour un accès ou une copie rapide de pixels), en plus des composants d'octet individuels.

14
Habitante

Je voudrais améliorer la réponse de Ray - pas assez de représentant pour commenter. > :( Cette version a le meilleur des deux: sûr/géré et efficacité de la version non sécurisée. En outre, je me suis abstenu de passer à la foulée, car la documentation .Net de CopyPixels indique que c’est la foulée de la bitmap, pas Cela est trompeur et peut quand même être calculé à l'intérieur de la fonction. Comme le tableau PixelColor doit avoir la même foulée que le bitmap (pour pouvoir le faire en un seul appel copie), il est logique de créer un nouveau tableau dans la fonction aussi bien. Facile comme bonjour.

    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;
    }
4
Brent

J'ai pris tous les exemples et en ai créé un légèrement meilleur - testé aussi 
(le seul défaut était cette magie 96 en tant que DPI qui m'a vraiment dérangé)

J'ai aussi comparé cette tactique de WPF à celle de:

  • GDI en utilisant Graphics (system.drawing)
  • Interopérez en appelant directement GetPixel à partir de GDI32.Dll

À mon supprise,
Cela fonctionne x10 plus vite que GDI et environ x15 fois plus vite qu'Interop. 
Donc, si vous utilisez WPF, mieux vaut travailler avec cela pour obtenir la couleur de votre 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;
    }
}
3
G.Y

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;
    }
2
CZahrobsky

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 stride, 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 la méthode d'origine, celle-ci est appelée. Je suppose que cela est dû au fait que PixelColor[,] correspond également à Array.

J'espère que cela vous aidera si vous rencontrez le même problème.

0
Dänu

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-le en tableau de 8 bits avec une taille 4 fois supérieure

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...]
0
Iqaan Ullah