Perché il “passo” del costruttore System.Drawing.Bitmap deve essere un multiplo di 4?

Sto scrivendo un’applicazione che mi richiede di prendere un formato bitmap proprietario (un HalCode HalCode MVTec) e convertirlo in un System.Drawing.Bitmap in C #.

Le uniche funzioni proprietarie che mi sono state fornite per aiutarmi a fare questo mi hanno impegnato a scrivere su file, tranne che per l’uso della funzione “Ottieni puntatore”.

Questa funzione è ottima, mi dà un puntatore ai dati dei pixel, alla larghezza, all’altezza e al tipo di immagine.

Il mio problema è che quando creo il mio System.Drawing.Bitmap usando il costruttore:

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

Devo specificare un “passo falso” che è un multiplo di 4. Questo potrebbe essere un problema perché non sono sicuro di quale dimensione bitmap verrà colpita dalla mia funzione. Supponendo che finisca con una bitmap di 111×111 pixel, non ho modo di eseguire questa funzione se non aggiungendo una colonna fasulla alla mia immagine o sottraendo 3 colonne.

C’è un modo per aggirare questa limitazione?

Questo risale ai primi progetti della CPU. Il modo più veloce per eseguire il crunch attraverso i bit della bitmap consiste nel leggerli a 32 bit alla volta, iniziando all’inizio di una linea di scansione. Ciò funziona meglio quando il primo byte della linea di scansione è allineato su un limite di indirizzo a 32 bit. In altre parole, un indirizzo che è un multiplo di 4. Nelle prime CPU, avere quel primo byte disallineato costerebbe un ciclo di CPU in più per leggere due parole da 32 bit dalla RAM e mescolare i byte per creare il valore a 32 bit. Garantire che ogni linea di scansione inizi con un indirizzo allineato (automatico se la falcata è un multiplo di 4) lo evita.

Questo non è più un problema reale per le CPU moderne, ora l’allineamento al limite della linea cache è molto più importante. Tuttavia, il multiplo di 4 requisiti per il passo è rimasto bloccato per ragioni appcompat.

A proposito, puoi facilmente calcolare il passo dal formato e dalla larghezza con questo:

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

Come è stato affermato in precedenza da Jake, si calcola il passo individuando i byte per pixel (2 per 16 bit, 4 per 32 bit) e quindi moltiplicandolo per la larghezza. Quindi se hai una larghezza di 111 e un’immagine a 32 bit avresti 444 che è un multiplo di 4.

Tuttavia, diciamo per un minuto che hai un’immagine a 24 bit. 24 bit equivale a 3 byte, quindi con una larghezza di 111 pixel avresti 333 come passo. Questo, ovviamente, non è un multiplo di 4. Quindi vorresti arrotondare a 336 (il successivo multiplo più alto di 4). Anche se hai un po ‘di extra, questo spazio inutilizzato non è abbastanza significativo da fare davvero molta differenza nella maggior parte delle applicazioni.

Sfortunatamente, non c’è modo di aggirare questa restrizione (a meno che tu non usi sempre immagini a 32 o 64 bit, che sono sempre multipli di 4).

Ricorda che la stride è diversa dalla width . È ansible avere un’immagine con 111 (8 bit) pixel per riga, ma ogni riga è memorizzata nella memoria 112 byte.

Questo è fatto per fare un uso efficiente della memoria e come ho detto @Ian, sta memorizzando i dati in int32 .

Perché usa int32 per memorizzare ogni pixel.

 Sizeof(int32) = 4 

Ma non preoccuparti, quando l’immagine viene salvata dalla memoria al file utilizzerà l’utilizzo della memoria più efficiente ansible. Internamente utilizza 24 bit per pixel (8 bit rossi, 8 verdi e 8 blu) e lascia gli ultimi 8 bit ridondanti.

Un modo molto più semplice è semplicemente creare l’immagine con il costruttore (width, height, pixelformat) . Quindi si prende cura del passo stesso.

Quindi, puoi semplicemente usare LockBits per copiare i dati dell’immagine in esso, riga per riga, senza preoccuparti della roba di Stride; puoi letteralmente richiederlo dall’object BitmapData . Per l’operazione di copia effettiva, per ciascuna linea di scansione, si aumenta semplicemente il puntatore target in base alla falcata e il puntatore sorgente in base alla larghezza dei dati della linea.

Ecco un esempio in cui ho ottenuto i dati dell’immagine in un array di byte. Se si tratta di dati completamente compatti, il passo di input è normalmente solo la larghezza dell’immagine moltiplicata per la quantità di byte per pixel. Se si tratta di dati a tavolo a 8 bit, è semplicemente esattamente la larghezza.

Se i dati dell’immagine sono stati estratti da un object immagine, dovresti aver memorizzato il passo originale da quel processo di estrazione esattamente nello stesso modo, estraendolo dall’object 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; } 

Codice corretto:

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