2010-03-31 7 views
20

Me pregunto si hay una forma supereficiente de confirmar que un objeto Imagen hace referencia a una imagen totalmente negra, por lo que cada píxel dentro del mapa de bits es ARGB (255, 0, 0, 0).¿Cuál es una manera eficiente de saber si un mapa de bits es completamente negro?

¿Qué recomendarías? La mayoría de estos mapas de bits tendrán 1024 x 6000 píxeles (aunque no es seguro suponer que siempre serán de ese tamaño).

Necesito esto porque estamos teniendo problemas con la API PrintWindow. Encontramos que casi el 20% del tiempo, al menos una parte de la imagen será un cuadrado negro (una captura posterior tendrá éxito). Mi idea para solucionar esto fue llamar a PrintWindow o WM_PRINT con cada ventana secundaria, luego volver a unir toda la imagen de la ventana. Si puedo encontrar una manera eficiente de detectar que PrintWindow devolvió una imagen negra para una ventana secundaria particular, entonces puedo llamar rápidamente a PrintWindow nuevamente en esa captura. Apesta, pero PrintWindow es el único método para capturar una ventana que funciona en todas las ventanas (que yo quiero, de todos modos) y admite la captura de ventanas que están ocultas y/o fuera de la pantalla.

Cuando PrintWindow falla, no establece un código de error ni devuelve nada que indique que falló. Cuando tiene este problema de cuadrado negro, siempre es una ventana entera o una ventana secundaria que se vuelve negra. Así que al capturar cada ventana secundaria por separado, puedo estar seguro de que cada una de mis capturas habrá funcionado, siempre que contenga al menos un píxel no negro.

PrintWindow es mejor en Vista y por encima de, al parecer, pero en este caso estamos limitados a Server 2003.

+11

¡Míralo! :-) – brendan

+0

¿Quiere decir otra cosa que no sea verificar cada píxel? – jlew

+3

Elija un número de píxeles aleatorio pero bien distribuido y si son todos negros suponga que todo es negro. – Will

Respuesta

20

Te recomiendo que bloquees el mapa de bits en la memoria utilizando el método LockBits del tipo System.Drawing.Bitmap. Este método devuelve el tipo BitmapData, desde el que puede recibir un puntero a la región de memoria bloqueada. Luego itere a través de la memoria, buscando los bytes que no sean cero (realmente, más rápido escaneando los valores Int32 o incluso Int64, dependiendo de la plataforma que use). Código se verá así:

// Lock the bitmap's bits. 
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); 
BitmapData bmpData =bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat); 

// Get the address of the first line. 
IntPtr ptr = bmpData.Scan0; 

// Declare an array to hold the bytes of the bitmap. 
int bytes = bmpData.Stride * bmp.Height; 
byte[] rgbValues = new byte[bytes]; 

// Copy the RGB values into the array. 
Marshal.Copy(ptr, rgbValues, 0, bytes); 

// Scanning for non-zero bytes 
bool allBlack = true; 
for (int index = 0; index < rgbValues.Length; index++) 
    if (rgbValues[index] != 0) 
    { 
     allBlack = false; 
     break; 
    } 
// Unlock the bits. 
bmp.UnlockBits(bmpData); 

considerar el uso del código no seguro y acceso directo a la memoria (el uso de punteros) para mejorar el rendimiento.

+3

si es posible, fundir como int [] y luego valor | = 0xFF000000 para la mayoría de los píxeles también lo acelerarían. Marshal.Copy es probablemente lento ... –

+0

Absolutamente de acuerdo. Solo use la propiedad IntPtr recibida por Scan0 y conviértala en (uint *). Luego itere usando este puntero. El siguiente paso es usar un ensamblado de código no administrado donde puede usar instrucciones SIMD (bloque __asm ​​en el C++) – leonard

+3

Sí, verificar el valor 0xFF000000 en grupos de DWORD es mucho mejor que consultar un solo byte a la vez. –

7

Si supiera más sobre las condiciones en que la imagen sería no negro, sería más fácil. Por ejemplo, ¿cómo se ven los bordes o el centro de la imagen cuando no es negra? Esencialmente, lo que crea es heurístico para adivinar una imagen que no sea negra y muestrear las áreas que le darán la lectura más rápida. Si su heurística indica una imagen totalmente negra, puede decidir si es completamente negra o hacer una comprobación completa de todos los píxeles. Eso es muy dependiente de tus imágenes, sin embargo. Si tiene que poder distinguir entre una imagen totalmente negra y una que contiene un único píxel no negro en una ubicación aleatoria, deberá verificarlas todas.

1

Para ser completamente seguro de la negrura de la imagen, tendrá que comprobar cada píxel, y acceder a los datos de píxeles en un bloque inseguro es probablemente la forma más rápida de hacerlo. Por supuesto, es posible optimizar el caso que no sea negro e intentar encontrarlo antes, pero en el peor de los casos, siempre tendrá que verificar cada píxel.

1

Sólo algunos pensamientos al azar:

  • tal vez usted podría apply a ColorMatrix al original mapa de bits (para apagar completamente a negro). Luego compare el resultado con el original.
  • O cree un mapa de bits del mismo tamaño (lleno de negro puro) y luego compare con el mapa de bits original.
2

Dibuje el mapa de bits con un ColorMatrix que tiene 3 x 255 en la diagonal, que hará sonar cualquier píxel no negro a blanco puro. A continuación, dibuje ese mapa de bits a uno más pequeño cuyo ancho es un múltiplo de 4 y tiene el formato Format24bppRgb. Eso elimina el alfa, reduce el tamaño y solo deja ceros si el mapa de bits es realmente negro.

Tendrá que experimentar para ver qué tan pequeño puede hacer el mapa de bits, use un ejemplo que tenga solo un píxel blanco para ver cuando el interpolador lo haga desaparecer. Supongo que puedes llegar muy lejos.

1

Tengo una idea que está fuera de la caja.

¿Qué tal un CRC checksum? Primero puede verificar las dimensiones de la imagen, luego calcular la suma de comprobación y compararla con las sumas de comprobación conocidas (precalculadas) de una imagen totalmente negra de las mismas dimensiones.

EDIT: duda que esto sea más rápido que el método de @ leonard. La única razón por la que podría ser es si el archivo original no era un mapa de bits, sino un formato de imagen comprimida. De esta forma, el algoritmo de suma de verificación CRC no tendría que descomprimir la imagen antes de ejecutarla.

1

Un método razonablemente confiable sería verificar el tamaño del archivo de la imagen. Es decir, si las imágenes que no son todas negras tienen una distribución de colores relativamente normal.

Si conoce el tipo de archivo, sabe algunas cosas básicas sobre las relaciones de compresión promedio. Y puede determinar las dimensiones del archivo con bastante facilidad sin pasar por todo el archivo.

Una imagen en negro, de cualquier dimensión, utilizando un formato de archivo comprimido, tendrá un tamaño de archivo muy pequeño en comparación con una imagen de dimensiones idénticas con una distribución de colores bastante normal.

Este método tomaría un poco de tiempo para probar y construir una base de conocimiento de lo que el tamaño del archivo de una imagen totalmente en negro debería compararse con una imagen que no sea completamente negra, pero sería muy rápido.

Si tiene muchas instancias en las que las imágenes que no son completamente negras están bastante cerca de todo negro, obviamente este método no funcionaría.

+0

Sería fantástico si esto funcionara. Pero acabo de probarlo. Y no es así. De hecho, dos mapas de bits de dimensiones idénticas tendrán tamaños de archivo idénticos independientemente de qué estén compuestos. –

+0

Sí, solo funcionaría para un formato comprimido como png –

+0

@Gabe: estaba a punto de decir eso. Por improbable que sea, el OP solo menciona bitmaps. Los mapas de bits 1024 x 6000 son archivos enormes, por lo que no me parece ideal. Si se comprimieron como dijiste entonces podrías aprovechar esta optimización. –

2

El uso de la biblioteca AForgeNET (http://www.aforgenet.com) también podría ser una solución:

public bool IsNotBlackImage() 
{ 
    Assembly assembly = this.GetType().Assembly; 
    var imgTest = new Bitmap(assembly.GetManifestResourceStream("TestImage.png")); 
    var imgStatistics = new ImageStatistics(imgTest);    
    return imgStatistics.PixelsCountWithoutBlack != 0; 
} 

Para tener una referencia de clase ImageStatistics AForge.Imaging.dll en su proyecto.

http://code.google.com/p/aforge/source/browse/trunk/Sources/Imaging/ImageStatistics.cs

6

La primera respuesta a esta entrada es impresionante. Modifiqué el código para determinar más genéricamente si la imagen es de un solo color (todo negro, todo blanco, todo magenta, etc.).Suponiendo que tiene un mapa de bits con 4 valores de color de parte ARGB, compare cada color con el color en la parte superior izquierda, si alguno es diferente, entonces la imagen no es de un solo color.

private bool AllOneColor(Bitmap bmp) 
{ 
    // Lock the bitmap's bits. 
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); 
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat); 

    // Get the address of the first line. 
    IntPtr ptr = bmpData.Scan0; 

    // Declare an array to hold the bytes of the bitmap. 
    int bytes = bmpData.Stride * bmp.Height; 
    byte[] rgbValues = new byte[bytes]; 

    // Copy the RGB values into the array. 

    System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); 

    bool AllOneColor = true; 
    for (int index = 0; index < rgbValues.Length; index++) 
    { 
     //compare the current A or R or G or B with the A or R or G or B at position 0,0. 
     if (rgbValues[index] != rgbValues[index % 4]) 
     { 
      AllOneColor= false; 
      break; 
     } 
    } 
    // Unlock the bits. 
    bmp.UnlockBits(bmpData); 
    return AllOneColor; 
} 
0
private bool AllOneColor(Bitmap bmp) 
    { 
     BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed); 
     byte[] rgbValues = new byte[bmpData.Stride * bmpData.Height]; 
     System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, rgbValues, 0, rgbValues.Length); 
     bmp.UnlockBits(bmpData); 
     return !rgbValues.Where((v, i) => i % bmpData.Stride < bmp.Width && v != rgbValues[0]).Any(); 
    } 
0

Un truco que puede hacerlo tan bien se pone un píxel indicador en alguna parte, que siempre tiene el mismo color, a menos que la captura de imágenes falla, en cuyo caso todo sería completamente negro Asumo

Cuestiones relacionadas