¿Por qué debe “avanzar” en el constructor System.Drawing.Bitmap ser un múltiplo de 4?

Estoy escribiendo una aplicación que requiere que tome un formato de bitmap propietario (un MVTec Halcon HImage) y lo convierta en un System.Drawing.Bitmap en C #.

Las únicas funciones exclusivas 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 imagen.

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

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

Necesito especificar un “paso” que sea un múltiplo de 4. Esto puede ser un problema ya que no estoy seguro de con qué tamaño de bitmap se verá mi función. Supongamos que termino con un bitmap de 111×111 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 escabullirme por esta limitación?

Esto se remonta a los primeros diseños de CPU. La manera más rápida de atravesar los bits del bitmap es leyéndolos de 32 bits a la vez, comenzando por el comienzo de una línea de exploración. 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, puedes calcular fácilmente la zancada desde el formato y el ancho con esto:

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

Como ha dicho Jake anteriormente, calcule el paso encontrando los bytes por píxel (2 por 16 bit, 4 por 32 bit) y luego multiplicándolo por el ancho. Entonces, 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 tienes 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 forma de evitar esta restricción (a menos que siempre use imágenes de 32 bits o de 64 bits, que siempre son múltiplos de 4).

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

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

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

 Sizeof(int32) = 4 

Pero no se preocupe, cuando la imagen se guarda de memoria a archivo, 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.

Una forma mucho más sencilla es simplemente crear la imagen con el constructor (width, height, pixelformat) . Entonces se ocupa de la zancada en sí misma.

Luego, puedes usar LockBits para copiar tus datos de imagen en línea, línea por línea, sin molestarte con las cosas de Stride; 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 donde obtuve los datos de 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, debería haber almacenado la zancada original de ese proceso de extracción exactamente de la misma manera, sacándolo del objeto BitmapData .

 ///  /// Creates a bitmap based on data, width, height, stride and pixel format. ///  /// Byte array of raw source data /// Width of the image /// Height of the image /// Scanline length inside the data /// Pixel format /// Color palette /// Default color to fill in on the palette if the given colors don't fully fill it. /// The new image 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; } 

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); } 
Intereting Posts