2009-03-13 7 views
0

J'essaye de traiter rapidement de grandes images (environ 2000x2000). J'utilise une TrackBar pour changer la luminosité globale d'une image. Le problème est que le TrackBar (comme vous le savez tous) peut être déplacé très rapidement. C'est correct car je n'ai pas besoin de traiter l'image pour tous les ticks de la TrackBar, mais il doit être raisonnablement sensible. Pensez à la luminosité TrackBars trouvée dans les éditeurs d'image.Comment utiliser plusieurs threads pour traiter une image dans des sections?

J'ai essayé de faire tout le traitement dans un autre thread, ce qui fonctionne, mais il est encore trop lent (même en utilisant LockBits sur le Bitmap). Je ne peux pas utiliser un ColorMatrix car il permet des débordements de composants et j'ai besoin de valeurs pour le clip à 255. Donc, je pense que je peux obtenir de bonnes performances en utilisant le ThreadPool et en divisant l'image en sections. Le problème avec cette approche est que je n'ai pas beaucoup d'expérience avec les applications multi-têtes et je ne sais pas comment éviter les conditions de course qui surgissent (appel LockBits sur une image déjà verrouillée, etc.). Quelqu'un pourrait-il me donner un exemple de comment faire cela?

Voici le code que j'ai pour le moment. Je sais que c'est loin d'être bon, mais je suis en train de travailler et d'essayer un tas de choses différentes à ce stade. Le concept de base consiste à utiliser une image de base comme source, à effectuer une opération sur chaque pixel, puis à dessiner le pixel traité dans un bitmap d'affichage. Toute aide serait géniale.

public Form1() 
    { 
     InitializeComponent(); 
     testPBox1.Image = Properties.Resources.test; 
     trackBar1.Value = 100; 
     trackBar1.ValueChanged += trackBar1_ValueChanged;   
    }  

    void trackBar1_ValueChanged(object sender, EventArgs e) 
    { 
     testPBox1.IntensityScale = (float) trackBar1.Value/100; 
    } 

class TestPBox : Control 
{ 
    private const int MIN_SAT_WARNING = 240;   
    private Bitmap m_srcBitmap; 
    private Bitmap m_dispBitmap; 
    private float m_scale;   

    public TestPBox() 
    { 
     this.DoubleBuffered = true;            
     IntensityScale = 1.0f; 
    }  

    public Bitmap Image 
    { 
     get 
     { 
      return m_dispBitmap; 
     } 
     set 
     { 
      if (value != null) 
      { 
       m_srcBitmap = value; 
       m_dispBitmap = (Bitmap) value.Clone(); 
       Invalidate(); 
      } 
     } 
    } 

    [DefaultValue(1.0f)] 
    public float IntensityScale 
    { 
     get 
     { 
      return m_scale; 
     } 
     set 
     { 
      if (value == 0.0 || m_scale == value) 
      {     
       return; 
      } 

      if (!this.DesignMode) 
      { 
       m_scale = value; 
       ProcessImage();      
      } 
     } 
    } 

    private void ProcessImage() 
    { 
     if (Image != null) 
     { 
      int sections = 10; 
      int sectionHeight = (int) m_srcBitmap.Height/sections; 
      for (int i = 0; i < sections; ++i) 
      { 
       Rectangle next = new Rectangle(0, i * sectionHeight, m_srcBitmap.Width, sectionHeight); 
       ThreadPool.QueueUserWorkItem(new WaitCallback(delegate { ChangeIntensity(next); })); 
      } 
     } 
    } 

    private unsafe void ChangeIntensity(Rectangle rect) 
    {    
     BitmapData srcData = m_srcBitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb); 
     BitmapData dspData = m_dispBitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);    
     byte* pSrc = (byte*) srcData.Scan0; 
     byte* pDsp = (byte*) dspData.Scan0; 

     for (int y = 0; y < rect.Height; ++y) 
     { 
      for (int x = 0; x < rect.Width; ++x) 
      { 
       // we are dealing with a monochrome image, so r = g = b. 
       // We only need to get one component value to use for all r, g, and b. 
       byte b = (byte) CompMinMax(0, 255, (int) (pSrc[0] * m_scale)); 
       Color c = (b > MIN_SAT_WARNING) ? Color.FromArgb(b, Color.Red) : Color.FromArgb(255, b, b, b);      

       // windows stores images internally in 
       // reverse byte order, i.e., Bgra, not Argb. 
       pDsp[3] = (byte) c.A; 
       pDsp[2] = (byte) c.R; 
       pDsp[1] = (byte) c.G; 
       pDsp[0] = (byte) c.B; 

       pSrc += 4; 
       pDsp += 4;       
      } 
     } 

     m_srcBitmap.UnlockBits(srcData); 
     m_dispBitmap.UnlockBits(dspData); 
     this.Invalidate();      
    } 

    private int CompMinMax(int min, int max, int value) 
    { 
     if (value > max) return max; 
     if (value < min) return min; 
     return value; 
    }   

    protected override void OnPaint(PaintEventArgs e) 
    { 
     if (Image != null) 
     {    
      Graphics g = e.Graphics; 
      Rectangle drawingRect = PaintUtils.CenterInRect(ClientRectangle, PaintUtils.ScaleRect(ClientRectangle, Image.Size).Size); 
      g.DrawImage(Image, drawingRect, 0, 0, Image.Width, Image.Height, GraphicsUnit.Pixel);   
     } 

     base.OnPaint(e); 
    } 
} 

Répondre

4

Avez-vous envisagé la génération d'une version réduite de l'image et de faire la manipulation d'image que si l'utilisateur fait glisser la barre de suivi? Cela vous donnerait une réactivité tout en donnant à l'utilisateur un retour utile jusqu'à ce qu'il se fixe une valeur finale.

+0

J'y ai pensé ... j'essaierai ... –

+0

Excellent 'out of the box' en pensant! – Jack

1

Si vous voulez utiliser votre approche actuelle, il y a quelques petites modifications que vous pourriez faire pour vous débarrasser des conditions de course. La chose que vous auriez besoin de changer serait de l'ajuster pour que vous appeliez LockBits/UnlockBits dans ProcessImage, pas dans les requêtes individuelles de pool de threads. Toutefois, vous devez également suivre un WaitHandle qui a été transmis à votre délégué de pool de threads afin que vous puissiez le bloquer jusqu'à ce que tous vos threads de pool de threads soient terminés. Ne débloquez pas les bits, invalidez et retournez jusqu'à ce que tous vos délégués aient fini. Toutefois, en fonction de votre calendrier de lancement, vous pouvez envisager d'utiliser la bibliothèque parallèle de tâches au lieu de ThreadPool. C# 4 inclura ceci, mais si vous ne pouvez pas attendre aussi longtemps, la version Mono fonctionne raisonnablement bien aujourd'hui. En fait, ce type de travail est un peu plus facile que de le faire via le ThreadPool.

0

Généralement, le traitement multithread sur des sous-ensembles d'images est possible mais a généralement un sens pour les algorithmes qui peuvent donner le même résultat en traitant des sous-ensembles qu'en traitant l'image entière. En d'autres termes, si A et B sont des sous-ensembles de l'image, cela devrait être l'équation valide:

alg(A + B) = alg(A) + alg(B) 

Donc, vous devez identifier si votre une opération appartient à cette classe d'algorithmes.

Questions connexes