2010-02-02 16 views
19

Estoy escribiendo una aplicación que requiere que tome un formato de mapa de bits propietario (un MVTec Halcon HImage) y lo convierta en un System.Drawing.Bitmap en C#.¿Por qué debe "avanzar" en el constructor System.Drawing.Bitmap ser un múltiplo de 4?

Las únicas funciones propietarias que se me han otorgado para ayudarme a hacer esto implican que escriba en un archivo, excepto por el uso de la función "obtener puntero".

Esta función es excelente, me da un puntero a los datos de píxeles, el ancho, la altura y el tipo de la imagen.

Mi problema es que cuando creo mi System.Drawing.Bitmap utilizando el constructor:

new System.Drawing.Bitmap(width, height, stride, format, scan) 

necesito especificar un "paso" que es un múltiplo de 4. Esto puede ser un problema, ya No estoy seguro de qué tamaño de mapa de bits tendrá mi función. Supongamos que termino con un mapa de bits de 111x111 píxeles, no tengo forma de ejecutar esta función más que agregar una columna falsa a mi imagen o restar 3 columnas.

¿Hay alguna manera de evitar esta limitación?

Respuesta

56

Esto se remonta a los primeros diseños de CPU. La forma más rápida de atravesarlo los bits del mapa de bits es leyéndolos a 32 bits a la vez, comenzando al comienzo de una línea de escaneo. Eso funciona mejor cuando el primer byte de la línea de exploración está alineado en un límite de dirección de 32 bits. En otras palabras, una dirección que es un múltiplo de 4. En las CPUs tempranas, tener ese primer byte mal alineado costaría ciclos extra de CPU para leer dos palabras de 32 bits de RAM y mezclar los bytes para crear el valor de 32 bits. Asegurar que cada línea de exploración comience en una dirección alineada (automática si la zancada es un múltiplo de 4) evita eso.

Esto ya no es una preocupación real en las CPU modernas, ahora la alineación con el límite de la línea de caché es mucho más importante. Sin embargo, el múltiplo de 4 requisitos de zancada atrapado por razones de appcompat.

Por cierto, se puede calcular fácilmente el paso del formato y la anchura con esto:

 int bitsPerPixel = ((int)format & 0xff00) >> 8; 
     int bytesPerPixel = (bitsPerPixel + 7)/8; 
     int stride = 4 * ((width * bytesPerPixel + 3)/4); 
+1

Esta es la respuesta correcta; más uno todo el día. Aquí hay más sobre el cálculo de 'zancada': http://stackoverflow.com/questions/1983781/why-does-bitmapsource-create-throw-an-argumentexception/1983886#1983886. – jason

+0

Por lo tanto, probablemente debería intentar cambiar el formato de mi imagen. Ahora estoy usando algo que almacena cada píxel como un solo byte. Habiendo dicho eso, supongo que no puedo usar ese constructor de mapa de bits ya que no hay ningún tipo de imagen que solo tome un solo byte y no dependa de un mapa de color wonky. –

+0

Correcto, 8bpp requiere una paleta. GDI + no los admite bien, le proporcionará un sinfín de problemas. Puede comenzar a cargar una imagen de 8bpp existente para comenzar. Puede robar sus entradas de paleta o LockBits directamente si tiene el tamaño correcto. O sintetiza uno en un MemoryStream. –

1

Porque está usando int32 para almacenar cada píxel.

Sizeof(int32) = 4 

Pero no se preocupe, cuando la imagen se guarda en la memoria a presentar usará el uso de memoria más eficiente posible. Internamente usa 24 bits por píxel (8 bits rojo, 8 verde y 8 azul) y deja los últimos 8 bits redundantes.

2

Como ya ha dicho Jake, calcule el paso encontrando los bytes por píxel (2 por 16 bit, 4 por 32 bit) y luego multiplicándolo por el ancho. Por lo tanto, si tiene un ancho de 111 y una imagen de 32 bits, tendría 444, que es un múltiplo de 4.

Sin embargo, digamos por un minuto que tiene una imagen de 24 bits. 24 bits es igual a 3 bytes, por lo que con un ancho de 111 píxeles tendría 333 como su paso. Esto es, obviamente, no un múltiplo de 4. Entonces, debería redondear hasta 336 (el siguiente múltiplo más alto de 4). A pesar de que tiene un poco más, este espacio no utilizado no es lo suficientemente significativo como para realmente hacer una gran diferencia en la mayoría de las aplicaciones.

Desafortunadamente, no hay manera de evitar esta restricción (a menos que utilice siempre 32 bits o 64 bits imagina, que son siempre múltiplos de 4.

2

Recuerde stride es diferente de width. Usted puede tener una imagen que tiene 111 (8 bits) píxeles por línea, pero cada línea se almacena en la memoria 112 bytes.

Esto se hace para hacer un uso eficiente de la memoria y como dijo @Ian, es almacenar los datos en int32.

0

código correcto:

public static void GetStride(int width, PixelFormat format, ref int stride, ref int bytesPerPixel) 
{ 
    //int bitsPerPixel = ((int)format & 0xff00) >> 8; 
    int bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format); 
    bytesPerPixel = (bitsPerPixel + 7)/8; 
    stride = 4 * ((width * bytesPerPixel + 3)/4); 
} 
1

Una manera mucho más fácil es hacer sólo la imagen con el (width, height, pixelformat) constructor. Entonces se ocupa de la zancada en sí misma.

Luego, puede usar LockBits para copiar los datos de su imagen en él, línea por línea, sin molestarse con la materia de Stride usted mismo; simplemente puede solicitarlo desde el objeto BitmapData. Para la operación de copia real, para cada línea de exploración, simplemente aumenta el puntero de destino por la zancada, y el puntero de fuente por el ancho de los datos de su línea.

Aquí hay un ejemplo en el que obtuve los datos de la imagen en una matriz de bytes. Si se trata de datos completamente compactos, la zancada de entrada normalmente es solo el ancho de la imagen multiplicado por la cantidad de bytes por píxel. Si se trata de datos paletizados de 8 bits, es simplemente el ancho exacto.

Si los datos de la imagen se extrajeron de un objeto de imagen, debe haber almacenado la zancada original de ese proceso de extracción exactamente de la misma manera, sacándolo del objeto BitmapData.

/// <summary> 
/// Creates a bitmap based on data, width, height, stride and pixel format. 
/// </summary> 
/// <param name="sourceData">Byte array of raw source data</param> 
/// <param name="width">Width of the image</param> 
/// <param name="height">Height of the image</param> 
/// <param name="stride">Scanline length inside the data</param> 
/// <param name="pixelFormat">Pixel format</param> 
/// <param name="palette">Color palette</param> 
/// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param> 
/// <returns>The new image</returns> 
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor) 
{ 
    Bitmap newImage = new Bitmap(width, height, pixelFormat); 
    BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat); 
    Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7)/8; 
    // Compensate for possible negative stride on BMP format. 
    Boolean isFlipped = stride < 0; 
    stride = Math.Abs(stride); 
    // Cache these to avoid unnecessary getter calls. 
    Int32 targetStride = targetData.Stride; 
    Int64 scan0 = targetData.Scan0.ToInt64(); 
    for (Int32 y = 0; y < height; y++) 
     Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth); 
    newImage.UnlockBits(targetData); 
    // Fix negative stride on BMP format. 
    if (isFlipped) 
     newImage.RotateFlip(RotateFlipType.Rotate180FlipX); 
    // For indexed images, set the palette. 
    if ((pixelFormat & PixelFormat.Indexed) != 0 && palette != null) 
    { 
     ColorPalette pal = newImage.Palette; 
     for (Int32 i = 0; i < pal.Entries.Length; i++) 
     { 
      if (i < palette.Length) 
       pal.Entries[i] = palette[i]; 
      else if (defaultColor.HasValue) 
       pal.Entries[i] = defaultColor.Value; 
      else 
       break; 
     } 
     newImage.Palette = pal; 
    } 
    return newImage; 
} 
Cuestiones relacionadas