2010-02-02 15 views
19

Özel bir bitmap biçimini (MVTec Halcon HImage) almamı ve onu C# içinde bir System.Drawing.Bitmap dosyasına dönüştürmemi gerektiren bir uygulama yazıyorum.Neden System.Drawing.Bitmap yapıcısında "geçmeli" 4'ün katları olmalı?

Bana yardımcı olmak için bana verilen tek özel işlevler, "alma işaretçisi" işlevinin kullanılması dışında dosyaya yazmamı içerir.

Bu işlev harika, bana piksel verileri, genişlik, yükseklik ve görüntünün türü için bir işaretçi veriyor.

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

Ben Bu gibi bir sorun olabilir 4. katları olan bir "adım" belirtmek gerekir:

Sorunum kurucuyu kullanarak benim System.Drawing.Bitmap oluştururken olmasıdır Fonksiyonumun hangi bitmap ile vurulacağından emin değilim. Sanırım 111x111 piksele sahip bir bitmap ile sona ererim, bu işlevi görüntüye sahte bir sütun eklemek veya 3 sütunu çıkarmak dışında kullanmam mümkün değildir.

Bu sınırlamayı aşmamın bir yolu var mı?

cevap

56

Bu, eski CPU tasarımlarına geri gider. bitmap bitleri, bir tarama hattının başlangıcından başlayarak, bir seferde 32 bit okuyarak elde edilir. Bu, tarama hattının ilk baytı 32 bit adres sınırında hizalandığında en iyi sonuç verir. Başka bir deyişle, 4'in katları olan bir adres. İlk CPU'larda, ilk bayt yanlış hizalanmış olması, RAM'den iki 32-bit kelimeyi okumak ve 32 bitlik değeri oluşturmak için baytları karıştırmak için fazladan CPU döngüleri gerektirecektir. Her bir tarama hattının hizalanmış bir adreste başlamasının sağlanması (adım 4'ün katları ise otomatiktir) bunu engeller.

Bu, modern CPU'larda artık gerçek bir sorun değil, şimdi önbellek çizgisi sınırlamasına uyum daha önemli. Yine de, appcompat nedenlerden dolayı stride için 4 şartın kat kat fazla sıkışmış.

Btw, kolayca bununla biçimi ve genişliği dan adım hesaplayabilirsiniz:

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

Doğru yanıt budur; artı bir bütün gün. Burada, "adım" hesaplaması hakkında daha fazla bilgi bulabilirsiniz: http://stackoverflow.com/questions/1983781/why-does-bitmapsource-create-throw-an-argumentexception/1983886#1983886. – jason

+0

Yani, büyük olasılıkla görüntü formatımı değiştirmeyi denemeliyim. Şu anda, her bir pikseli tek bir bayt olarak depolayan bir şey kullanıyorum. Bunu söyledikten sonra, bu bitmap yapıcısını kullanamayacağımı farzediyorum, çünkü sadece tek bir bayt alıp, kazıklı bir renk haritasına dayanmayan görüntü tipi yok. –

+0

Doğru, 8bpp bir palet gerektiriyor. GDI + onları iyi desteklemez, size herhangi bir sıkıntı vermez. Başlamak için mevcut bir 8bpp resmi yükleyerek sorunu çözebilirsiniz. Palet girişlerini çalabilir veya doğru boyuta sahipse doğrudan kilitleyebilirsiniz. Veya bir MemoryStream'de sentezleyin. –

1

Her pikseli saklamak için int32 kullanıyor.

Sizeof(int32) = 4 

Ancak endişelenmeyin, görüntü bellekten dosyaya kaydedildiğinde, mümkün olan en verimli bellek kullanımını kullanır. Dahili olarak piksel başına 24 bit (8 bit kırmızı, 8 yeşil ve 8 mavi) kullanır ve son 8 biti gereksiz bırakır.

2

Daha önce Jake tarafından belirtildiği gibi, piksel başına bayt (16 bit için 2, 32 bit için 4) ve ardından genişlikle çarparak adımı hesaplarsınız. Dolayısıyla, 111 ve 32 bitlik bir görüntü genişliğiniz varsa, 4 katına sahip olan 444'e sahip olursunuz. Bu, bir dakika için 24 bitlik bir görüntünüz olduğunu söyleyelim. 24 bit, 3 bayta eşittir, bu nedenle 111 piksel genişliğinde, adımınız olarak 333 olacaktır. Bu, belli ki, 4'ün katları değildir. Yani 336'ya (4'ün sonraki en yüksek katı) kadar yuvarlamak isteyeceksiniz. Biraz fazladan almanıza rağmen, bu kullanılmayan alan çoğu uygulamada gerçekten bir fark yaratacak kadar önemli değil. Hep stridewidth farklıdır hatırla daima 4.

2

katları olan, 32 bit veya 64 bit hayal kullanmadıkça

Maalesef bu kısıtlama etrafında hiçbir şekilde (yoktur. Sen sahip bir resim olabilir 111 (8 bit) çizgi başına piksel, ancak her bir satır hafıza 112 bayt depolanır.

Bu bellek verimli kullanımı için yapılır ve @Ian bahsedilen olarak, bu int32 veri saklamak var.

0

Doğru kodu:

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

bir daha kolay bir yolu sadece (width, height, pixelformat) ile görüntüyü yapmaktır yapıcı. O zaman adımın kendisi ile ilgilenir.

Ardından, görüntü verilerinizi Stride sayfalarını kendiniz rahatsız etmeden satır satır satırına kopyalamak için LockBits10'u kullanabilirsiniz; Sadece tam olarak BitmapData nesnesinden bunu isteyebilirsiniz. Gerçek kopyalama işlemi için, her bir tarama hattı için, hedef işaretçiyi adım adım ve kaynak göstericiniz satır veri genişliğinizle artırır.

Resim verisini bir bayt dizisinde aldığım bir örnek. Bu tamamen kompakt bir veri ise, giriş adımınız normalde sadece görüntü genişliği piksel başına bayt miktarı ile çarpılır. 8 bitlik paletlenmiş veri ise, sadece genişliktir.

Görüntü verisi bir görüntü nesnesinden ayıklanmışsa, özgün adımı bu çıkarma işleminden tam olarak aynı şekilde, BitmapData nesnesinden çıkararak kaydetmiş olmalısınız.

/// <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; 
} 
İlgili konular