2012-03-13 15 views
7

Ho un bitmap sourceImage.bmpritaglio di un'area da BitmapData con C#

bloccaggio è bit:

BitmapData dataOriginal = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 

fare analisi, ottenere un clone:

Bitmap originalClone = AForge.Imaging.Image.Clone(dataOriginal); 

sblocco bit:

sourceImage.UnlockBits(dataOriginal); 

è possibile specificare quale parte di "dataOriginal" copiare (x, y, w, h)? o per creare nuovi dati da dataOriginal, specificando le coordinate X e Y, nonché H e W?

L'obiettivo è copiare una piccola area da questa immagine. Questo metodo potrebbe essere più veloce di DrawImage, ecco perché non uso quest'ultimo.

Edit:

Allora presi 29 Mb bitmap e ha fatto alcuni test hardcore! Ritaglio a grandezza naturale (in pratica una copia) + 100 iterazioni.

http://i.minus.com/ibmcUsT1qUGw6f.png

Codice:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Windows.Forms; 
using AForge; 
using AForge.Imaging; 
using System.Diagnostics; 
using System.Drawing.Imaging; 
using System.IO; 
using System.Runtime.InteropServices; 


namespace testCropClone 
{ 
    public partial class Form1 : Form 
    { 
     public Form1() 
     { 
      InitializeComponent(); 
     } 

     private unsafe Bitmap Clone(Bitmap bmp, int startX, int startY, int width, int height) 
     { 
     Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); 
     BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 

     int origByteCount = rawOriginal.Stride * rawOriginal.Height; 
     byte[] origBytes = new Byte[origByteCount]; 
     Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount); 

     int BPP = 4;  //4 Bpp = 32 bits, 3 = 24, etc. 

     byte[] croppedBytes = new Byte[width * height * BPP]; 

     //Iterate the selected area of the original image, and the full area of the new image 
     for (int i = 0; i < height; i++) 
     { 
      for (int j = 0; j < width * BPP; j += BPP) 
      { 
       int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j); 
       int croppedIndex = (i * width * BPP) + (j); 

       //copy data: once for each channel 
       for (int k = 0; k < BPP; k++) 
       { 
        croppedBytes[croppedIndex + k] = origBytes[origIndex + k]; 
       } 
      } 
     } 

     //copy new data into a bitmap 
     Bitmap croppedBitmap = new Bitmap(width, height); 
     BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); 
     Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length); 

     bmp.UnlockBits(rawOriginal); 
     croppedBitmap.UnlockBits(croppedData); 

     return croppedBitmap; 
     } 

     private Bitmap cloneBitmap(Bitmap bmp, int startX, int startY, int width, int height) 
     { 
      Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height); 
      Bitmap cloneBitmap = bmp.Clone(srcRect, bmp.PixelFormat); 
      return cloneBitmap; 
     } 


     private Bitmap cloneRectangle(Bitmap bmp, int startX, int startY, int width, int height) 
     { 
      Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height); 
      Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height); 
      Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height); 
      using (Graphics graphics = Graphics.FromImage(dest)) 
      { 
       graphics.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel); 
      } 
      return dest; 
     } 


     private Bitmap cloneAforge(Bitmap bmp, int startX, int startY, int width, int height) 
     { 
      BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 
      Bitmap cloneBitmap = AForge.Imaging.Image.Clone(rawOriginal); 
      bmp.UnlockBits(rawOriginal); 
      return cloneBitmap; 
     } 



     private void button1_Click(object sender, EventArgs e) 
     { 
      Bitmap source = new Bitmap(@"C:\9\01.bmp"); 

      Stopwatch s1 = Stopwatch.StartNew(); 
      for (int i = 0; i < 100; i++) 
      { 
       Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height); 
       Clone1.Dispose(); 

      } 

      /*Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height); 
      Clone1.Save(@"C:\9\01_aforge.bmp"); 
      Clone1.Dispose();*/ 

      s1.Stop(); 
      source.Dispose(); 
      textBox1.Text = ("" + s1.ElapsedMilliseconds/100 + " ms"); 
     } 

     private void button2_Click(object sender, EventArgs e) 
     { 
      Bitmap source = new Bitmap(@"C:\9\01.bmp"); 

      Stopwatch s1 = Stopwatch.StartNew(); 
      for (int i = 0; i < 100; i++) 
      { 
       Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height); 
       Clone1.Dispose(); 

      } 

      /*Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height); 
      Clone1.Save(@"C:\9\01_bitmap.bmp"); 
      Clone1.Dispose();*/ 

      s1.Stop(); 


      source.Dispose(); 
      textBox2.Text = ("" + s1.ElapsedMilliseconds/100 + " ms"); 
     } 

     private void button3_Click(object sender, EventArgs e) 
     { 
      Bitmap source = new Bitmap(@"C:\9\01.bmp"); 

      Stopwatch s1 = Stopwatch.StartNew(); 
      for (int i = 0; i < 100; i++) 
      { 
       Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height); 
       Clone1.Dispose(); 

      } 

      /*Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height); 
      Clone1.Save(@"C:\9\01_bits.bmp"); 
      Clone1.Dispose();*/ 

      s1.Stop(); 
      source.Dispose(); 
      textBox3.Text = ("" + s1.ElapsedMilliseconds/100 + " ms"); 
     } 

     private void button4_Click(object sender, EventArgs e) 
     { 
      Bitmap source = new Bitmap(@"C:\9\01.bmp"); 

      Stopwatch s1 = Stopwatch.StartNew(); 
      for (int i = 0; i < 100; i++) 
      { 
       Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height); 
       Clone1.Dispose(); 

      } 


      /*Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height); 
      Clone1.Save(@"C:\9\01_rect.bmp"); 
      Clone1.Dispose();*/ 


      s1.Stop(); 
      source.Dispose(); 
      textBox4.Text = ("" + s1.ElapsedMilliseconds/100 + " ms"); 
     } 
    } 
} 

Edit2: metodo (Aforge full-size Crop ..) Nr. 2

 for (int i = 0; i < 100; i++) 
     { 
      Crop crop = new Crop(new Rectangle(0, 0, source.Width, source.Height)); 
      var source2 = crop.Apply(source); 
      source2.Dispose(); 

     } 

= 62ms medio (40ms meno di 1 ° approccio Aforge)

Risultati:

  1. BitmapClone (0 ms) ?? (Barare, non è vero?)
  2. Aforge # 2 (65 ms)
  3. Aforge # 1 (105 ms)
  4. Rettangolo (170 ms)
  5. blocco bit (803 ms) (in attesa di correzioni/nuovi risultati dei test ..)
+1

La risposta è sì, ma si sta andando ad avere per scrivere una funzione per fare se se si vuole che Sii veloce. Dovrai creare un nuovo bitmapdata della dimensione desiderata e scorrere i dati originali come byte, copiandoli in un nuovo array di byte, che potrai quindi effettuare il marshalling nel nuovo bitmapData. – Fopedush

+0

Questa domanda è estremamente perspicace. Sto cercando di ritagliare una grande immagine in molti piccoli 8x8. I metodi Graphics.DrawImage() e Bitmap.Clone() sono estremamente lenti per questo. – JBeurer

risposta

6

Ho montato una soluzione manuale rapida (e certamente approssimativa) che dimostra come eseguire questa operazione utilizzando bitmap bloccate. Dovrebbe essere considerevolmente più veloce dei metodi alternativi, ma implica molto più codice.

 Bitmap bmp = new Bitmap(@"C:\original.jpg"); 
     Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); 
     BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 

     int origByteCount = rawOriginal.Stride * rawOriginal.Height; 
     byte[] origBytes = new Byte[origByteCount]; 
     Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount); 

     //I want to crop a 100x100 section starting at 15, 15. 
     int startX = 15; 
     int startY = 15; 
     int width = 100; 
     int height = 100; 
     int BPP = 4;  //4 Bpp = 32 bits, 3 = 24, etc. 

     byte[] croppedBytes = new Byte[width * height * BPP]; 

     //Iterate the selected area of the original image, and the full area of the new image 
     for (int i = 0; i < height; i++) 
     { 
      for (int j = 0; j < width * BPP; j += BPP) 
      { 
       int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j); 
       int croppedIndex = (i * width * BPP) + (j); 

       //copy data: once for each channel 
       for (int k = 0; k < BPP; k++) 
       { 
        croppedBytes[croppedIndex + k] = origBytes[origIndex + k]; 
       } 
      } 
     } 

     //copy new data into a bitmap 
     Bitmap croppedBitmap = new Bitmap(width, height); 
     BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); 
     Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length); 

     bmp.UnlockBits(rawOriginal); 
     croppedBitmap.UnlockBits(croppedData); 

     croppedBitmap.Save(@"C:\test.bmp"); 

Ho usato questa immagine originale:

original

Per produrre questa immagine, ritagliata a 100x100 @ 15,15:

cropped

Ovviamente se si utilizza questo codice , vorrai pulirlo un po 'e aggiungere la gestione degli errori. Se capisco correttamente la tua domanda, fare le cose in questo modo dovrebbe eliminare la necessità di usare AForge.

+1

prima di tutto, grazie per il vostro tempo e impegno nel scrivere questo codice! In secondo luogo, ho fatto alcuni test, ma sfortunatamente questa soluzione ha i risultati peggiori! ;) sentiti libero di criticare il mio approccio di test .. forse ho fatto un errore o due. (Vedi Modifica) – Alex

+0

Interessante, non me l'aspettavo.Farò un piccolo test per conto mio e vedrò se i miei risultati sono d'accordo con i tuoi. – Fopedush

+0

sarebbe bello! Ho cercato di rendere il framework di test il più semplice possibile per escludere errori gravi (codice completo in Modifica), ma forse otterrai altri risultati utilizzando un approccio diverso. Non vedere l'ora di! – Alex

2

si può provare qualcosa di simile:

public static Bitmap CropBitmap(Bitmap bitmap, int x, int y, int w, int h) 
{ 
    Rectangle rect = new Rectangle(x, y, w, h); 
    Bitmap cropped = bitmap.Clone(rect, bitmap.PixelFormat); 
    return cropped; 
} 

e fare qualcosa di simile in codice di yout (campione):

var croppedImagem = CropBitmap(dataOriginal, 0, 0, 100, 100); 

Spero che sia d'aiuto!

+1

"var croppedImagem = CropBitmap (dataOriginal, 0, 0, 100, 100);" - i programmi non funzionano in questo modo .. non puoi mettere (BitmapData) nel campo (Bitmap), anche questo metodo è simile a DrawImage. Sto pianificando di ritirare fino a 30 immagini allo stesso tempo. Sia DrawImage che Bitmap.Clone sono molto lenti a consumare/rallentare, quindi mi sono chiesto se potevo ritagliare BitmapData. – Alex

+0

Spiacente, dovresti passare un'istanza Bitmap e non un'istanza BitmapData. Ho testato questo codice qui e funziona bene, di couse è necessario avere un'immagine sulla dimensione giusta della dimensione del rettangolo che si desidera tagliare. Se vuoi ritagliare un sacco di immagini ti consiglierei di fissarlo sul thread per ottenere prestazioni. Non puoi tagliare un'immagine in 0,0,100,100 se questa immagine è di 20x20 pixel. Puoi prendere Bitmap e ingnore BitmapData? Perché hai bisogno dell'istanza BitmapData? –

+0

Bitmap.Clone è MOLTO, MOLTO lento se si desidera acquisire un'immagine grande e ritagliarla in piccole immagini 8x8. – JBeurer

2

La risposta di Fopedush avvantaggia notevolmente quando si sostituisce Marshal.copy con memcpy, perché in questo modo non è necessario copiarlo attraverso un array byte []. In questo modo la memoria viene copiata una sola volta, invece di tre volte!

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] 
static unsafe extern int memcpy(byte* dest, byte* src, long count); 

static public Bitmap cropBitmap(Bitmap sourceImage, Rectangle rectangle) 
{ 
    const int BPP = 4; //4 Bpp = 32 bits; argb 
    var sourceBitmapdata = sourceImage.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 
    var croppedImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppArgb); 
    var croppedBitmapData = croppedImage.LockBits(new Rectangle(0, 0, rectangle.Width, rectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); 
    unsafe 
    { 
     croppedBitmapData.Stride = sourceBitmapdata.Stride; 
     byte* sourceImagePointer = (byte*)sourceBitmapdata.Scan0.ToPointer(); 
     byte* croppedImagePointer = (byte*)croppedBitmapData.Scan0.ToPointer(); 
     memcpy(croppedImagePointer, sourceImagePointer, 
       Math.Abs(croppedBitmapData.Stride) * rectangle.Height); 
    } 
    sourceImage.UnlockBits(sourceBitmapdata); 
    croppedImage.UnlockBits(croppedBitmapData); 
    return croppedImage; 
} 

I miei risultati sono:

BitmapClone: 1823 ms 
LockBits: 4857 ms 
Rectangle: 1479 ms 
My method: 559 ms 
My method with LockBits on source image done only once (before loop): 160 ms 

non ho AForge quindi non ho incluso, ma cercando sui risultati di op sarebbe più lento di questo. Stavo testando ritagliare l'immagine a metà.

Si prega di notare, che se vogliamo scambiare memcpy con:

for (int k = 0; k < Math.Abs(croppedBitmapData.Stride) * rectangle.Height; k++) 
    *(croppedImagePointer++) = *(sourceImagePointer++); 

ottiene 10 volte più lento!

+0

Per qualche strana ragione ottengo AccessViolationException su memcpy che dice qualcosa sulla lettura/scrittura sulla memoria protetta. – Kosmos

+0

Forse è necessario regolare la costante BPP? Nel mio esempio assume 32 bit (argb), per 24 bit (rgb) usa 3. AccessViolationException significa che stai cercando di accedere alla memoria fuori dai limiti. – trakos

+0

Non penso che sarà d'aiuto dato che è stato assegnato, ma mai usato. – Kosmos

1
internal unsafe sealed class FastImageCroper : IDisposable 
{ 
    private readonly Bitmap _srcImg; 
    private readonly BitmapData _srcImgBitmapData; 
    private readonly int _bpp; 
    private readonly byte* _srtPrt; 

    public FastImageCroper(Bitmap srcImg) 
    { 
     _srcImg = srcImg; 
     _srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat); 
     _bpp = _srcImgBitmapData.Stride/_srcImgBitmapData.Width; // == 4 
     _srtPrt = (byte*)_srcImgBitmapData.Scan0.ToPointer(); 
    } 

    public Bitmap Crop(Rectangle rectangle) 
    { 
     Bitmap dstImg = new Bitmap(rectangle.Width, rectangle.Height, _srcImg.PixelFormat); 
     BitmapData dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat); 
     byte* dstPrt = (byte*)dstImgBitmapData.Scan0.ToPointer(); 
     byte* srcPrt = _srtPrt + rectangle.Y*_srcImgBitmapData.Stride + rectangle.X*_bpp; 

     for (int y = 0; y < rectangle.Height; y++) 
     { 
      int srcIndex = y * _srcImgBitmapData.Stride; 
      int croppedIndex = y * dstImgBitmapData.Stride; 
      memcpy(dstPrt + croppedIndex, srcPrt + srcIndex, dstImgBitmapData.Stride); 
     } 

     dstImg.UnlockBits(dstImgBitmapData); 
     return dstImg; 
    } 


    public void Dispose() 
    { 
     _srcImg.UnlockBits(_srcImgBitmapData); 
    } 


    [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] 
    private static extern int memcpy(byte* dest, byte* src, long count); 
} 
+1

Benvenuti in Stack Overflow. Forse potresti leggere http://stackoverflow.com/help/how-to-answer per rendere la tua risposta più conforme agli standard SO. –

+0

Mentre questo è stato gentile da parte tua, SO non è una community di codifica freelance. Dovresti spiegare come questo aiuta l'OP e quindi fornire il codice pertinente. Basta incollare il codice di lavoro in realtà non risponde a una domanda tanto quanto fornisce lavoro freelance. – leigero

2

Sono un nuovo utente e non può votare ancora, altrimenti avrei upvoted risposta Korwin80 s' in quanto fornisce la soluzione di lavoro più efficiente, a mio parere. La soluzione "trakos" può essere eseguita più rapidamente ma produce immagini criptate, almeno per me. Ecco come ho applicato la soluzione Korwin80 's, con alcuni piccoli miglioramenti, nel mio codice:

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] 
private unsafe static extern int memcpy(byte* dest, byte* src, long count); 

private unsafe Bitmap Crop(Bitmap srcImg, Rectangle rectangle) 
{ 
    if ((srcImg.Width == rectangle.Width) && (srcImg.Height == rectangle.Height)) 
     return srcImg; 

    var srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat); 
    var bpp = srcImgBitmapData.Stride/srcImgBitmapData.Width; // 3 or 4 
    var srcPtr = (byte*)srcImgBitmapData.Scan0.ToPointer() + rectangle.Y * srcImgBitmapData.Stride + rectangle.X * bpp; 
    var srcStride = srcImgBitmapData.Stride; 

    var dstImg = new Bitmap(rectangle.Width, rectangle.Height, srcImg.PixelFormat); 
    var dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat); 
    var dstPtr = (byte*)dstImgBitmapData.Scan0.ToPointer(); 
    var dstStride = dstImgBitmapData.Stride; 

    for (int y = 0; y < rectangle.Height; y++) 
    { 
     memcpy(dstPtr, srcPtr, dstStride); 
     srcPtr += srcStride; 
     dstPtr += dstStride; 
    } 

    srcImg.UnlockBits(srcImgBitmapData); 
    dstImg.UnlockBits(dstImgBitmapData); 
    return dstImg; 
}