ejemplo - image c#
¿Por qué “stride” en el constructor System.Drawing.Bitmap debe ser un múltiplo de 4? (6)
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);
}
Estoy escribiendo una aplicación que requiere que tome un formato de mapa de bits de propietario (un MVTec Halcon HImage) y lo convierta en un System.Drawing.Bitmap en C #.
Las únicas funciones exclusivas que me dieron para ayudarme a hacer esto me implican escribir en el archivo, excepto 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 una "zancada" que sea un múltiplo de 4. Esto puede ser un problema ya que no estoy seguro de qué tamaño de mapa de bits será alcanzado mi función. Suponiendo que termine con un mapa de bits que es 111x111 píxeles, no tengo otra forma de ejecutar esta función que no sea agregar una columna falsa a mi imagen o restar 3 columnas.
¿Hay alguna manera de que pueda escabullirme de esta limitación?
Como se ha dicho antes por Jake, usted calcula el paso encontrando los bytes por píxel (2 por 16 bits, 4 por 32 bits) y luego los multiplica 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 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. Obviamente, esto no es un múltiplo de 4. Por lo tanto, querría redondear a 336 (el siguiente múltiplo más alto de 4). A pesar de que tiene un poco de extra, 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 o 64 bits, que siempre son múltiplos de 4).
Esto se remonta a los primeros diseños de CPU. La forma más rápida de analizar los bits del mapa de bits es leerlos de 32 bits a la vez, comenzando desde el inicio de una línea de escaneo. Eso funciona mejor cuando el primer byte de la línea de exploración se alinea 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 primeras CPU, tener ese primer byte desalineado costaría ciclos adicionales de CPU para leer dos palabras de 32 bits de la RAM y barajar los bytes para crear el valor de 32 bits. Asegurarse de 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 requisito de múltiplo de 4 para zancada se atascó por razones de appcompat.
Por cierto, puedes calcular fácilmente el paso del formato y el ancho con esto:
int bitsPerPixel = ((int)format & 0xff00) >> 8;
int bytesPerPixel = (bitsPerPixel + 7) / 8;
int stride = 4 * ((width * bytesPerPixel + 3) / 4);
Porque está usando int32 para almacenar cada píxel.
Sizeof(int32) = 4
Pero no se preocupe, cuando la imagen se guarde de la memoria al archivo, utilizará el uso de memoria más eficiente posible. Internamente utiliza 24 bits por píxel (8 bits rojo, 8 verde y 8 azul) y deja los últimos 8 bits redundantes.
Recuerde que la stride
es diferente del width
. Puede tener una imagen que tiene 111 píxeles (8 bits) 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
.
Una forma mucho más fácil es simplemente hacer la imagen con el constructor (width, height, pixelformat)
. Entonces se encarga de la zancada misma.
Luego, puedes usar LockBits
para copiar los datos de tu imagen, línea por línea, sin molestarte con las cosas de Stride; literalmente puede solicitarlo al objeto BitmapData
. Para la operación de copia real, para cada línea de escaneo, simplemente aumenta el puntero de destino por la zancada y el puntero de origen por el ancho de los datos de línea.
Aquí hay un ejemplo donde obtuve los datos de la imagen en una matriz de bytes. Si se trata de datos completamente compactos, su paso de entrada normalmente es solo el ancho de la imagen multiplicado por la cantidad de bytes por píxel. Si se trata de datos paleteados de 8 bits, simplemente es exactamente el ancho.
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, al sacarla 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;
}