Partilhar via


Instruções: Utilizar o Fluxo de Dados numa Aplicação Windows Forms

Este artigo demonstra como criar uma rede de blocos de fluxo de dados que efetuam o processamento de imagens numa aplicação Windows Forms.

Este exemplo carrega ficheiros de imagem da pasta especificada, cria uma imagem composta e apresenta o resultado. O exemplo utiliza o modelo de fluxo de dados para encaminhar imagens através da rede. No modelo de fluxo de dados, os componentes independentes de um programa comunicam entre si ao enviar mensagens. Quando um componente recebe uma mensagem, executa alguma ação e, em seguida, transmite o resultado para outro componente. Compare-o com o modelo de fluxo de controlo, no qual uma aplicação utiliza estruturas de controlo, por exemplo, instruções condicionais, ciclos, etc., para controlar a ordem das operações num programa.

Pré-requisitos

Leia o Fluxo de Dados antes de iniciar estas instruções.

Nota

A Biblioteca de Fluxos de Dados TPL (o System.Threading.Tasks.Dataflow espaço de nomes) não é distribuída com .NET. Para instalar o System.Threading.Tasks.Dataflow espaço de nomes no Visual Studio, abra o seu projeto, selecione Gerir Pacotes NuGet no menu Projeto e procure o System.Threading.Tasks.Dataflow pacote online. Em alternativa, para instalá-lo com a CLI do .NET Core, execute dotnet add package System.Threading.Tasks.Dataflow.

Secções

Estas instruções contêm as seguintes secções:

Criar a Aplicação Windows Forms

Esta secção descreve como criar a aplicação de Windows Forms básica e adicionar controlos ao formulário principal.

Para Criar a Aplicação Windows Forms

  1. No Visual Studio, crie um projeto visual C# ou Visual Basic Windows Forms Application. Neste documento, o projeto tem o nome CompositeImages.

  2. No estruturador de formulários do formulário principal, Form1.cs (Form1.vb para Visual Basic), adicione um ToolStrip controlo.

  3. Adicione um ToolStripButton controlo ao ToolStrip controlo. Defina a DisplayStyle propriedade como Text e a Text propriedade como Escolher Pasta.

  4. Adicione um segundo ToolStripButton controlo ao ToolStrip controlo. Defina a DisplayStyle propriedade como Text, a Text propriedade como Cancelar e a Enabled propriedade como False.

  5. Adicione um PictureBox objeto ao formulário principal. Defina a Dock propriedade como Fill.

Criar a Rede de Fluxo de Dados

Esta secção descreve como criar a rede de fluxo de dados que executa o processamento de imagens.

Para Criar a Rede de Fluxo de Dados

  1. Adicione uma referência ao System.Threading.Tasks.Dataflow.dll ao seu projeto.

  2. Certifique-se de que Form1.cs (Form1.vb para Visual Basic) contém as seguintes using instruções (Using no Visual Basic):

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Threading.Tasks.Dataflow;
    using System.Windows.Forms;
    
  3. Adicione os seguintes membros de dados à Form1 classe :

    // The head of the dataflow network.
    ITargetBlock<string> headBlock = null;
    
    // Enables the user interface to signal cancellation to the network.
    CancellationTokenSource cancellationTokenSource;
    
  4. Adicione o seguinte método, CreateImageProcessingNetwork, à Form1 classe . Este método cria a rede de processamento de imagens.

    // Creates the image processing dataflow network and returns the
    // head node of the network.
    ITargetBlock<string> CreateImageProcessingNetwork()
    {
       //
       // Create the dataflow blocks that form the network.
       //
    
       // Create a dataflow block that takes a folder path as input
       // and returns a collection of Bitmap objects.
       var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path =>
          {
             try
             {
                return LoadBitmaps(path);
             }
             catch (OperationCanceledException)
             {
                // Handle cancellation by passing the empty collection
                // to the next stage of the network.
                return Enumerable.Empty<Bitmap>();
             }
          });
    
       // Create a dataflow block that takes a collection of Bitmap objects
       // and returns a single composite bitmap.
       var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps =>
          {
             try
             {
                return CreateCompositeBitmap(bitmaps);
             }
             catch (OperationCanceledException)
             {
                // Handle cancellation by passing null to the next stage
                // of the network.
                return null;
             }
          });
    
       // Create a dataflow block that displays the provided bitmap on the form.
       var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap =>
          {
             // Display the bitmap.
             pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
             pictureBox1.Image = bitmap;
    
             // Enable the user to select another folder.
             toolStripButton1.Enabled = true;
             toolStripButton2.Enabled = false;
             Cursor = DefaultCursor;
          },
          // Specify a task scheduler from the current synchronization context
          // so that the action runs on the UI thread.
          new ExecutionDataflowBlockOptions
          {
              TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
          });
    
       // Create a dataflow block that responds to a cancellation request by
       // displaying an image to indicate that the operation is cancelled and
       // enables the user to select another folder.
       var operationCancelled = new ActionBlock<object>(delegate
          {
             // Display the error image to indicate that the operation
             // was cancelled.
             pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
             pictureBox1.Image = pictureBox1.ErrorImage;
    
             // Enable the user to select another folder.
             toolStripButton1.Enabled = true;
             toolStripButton2.Enabled = false;
             Cursor = DefaultCursor;
          },
          // Specify a task scheduler from the current synchronization context
          // so that the action runs on the UI thread.
          new ExecutionDataflowBlockOptions
          {
             TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
          });
    
       //
       // Connect the network.
       //
    
       // Link loadBitmaps to createCompositeBitmap.
       // The provided predicate ensures that createCompositeBitmap accepts the
       // collection of bitmaps only if that collection has at least one member.
       loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0);
    
       // Also link loadBitmaps to operationCancelled.
       // When createCompositeBitmap rejects the message, loadBitmaps
       // offers the message to operationCancelled.
       // operationCancelled accepts all messages because we do not provide a
       // predicate.
       loadBitmaps.LinkTo(operationCancelled);
    
       // Link createCompositeBitmap to displayCompositeBitmap.
       // The provided predicate ensures that displayCompositeBitmap accepts the
       // bitmap only if it is non-null.
       createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null);
    
       // Also link createCompositeBitmap to operationCancelled.
       // When displayCompositeBitmap rejects the message, createCompositeBitmap
       // offers the message to operationCancelled.
       // operationCancelled accepts all messages because we do not provide a
       // predicate.
       createCompositeBitmap.LinkTo(operationCancelled);
    
       // Return the head of the network.
       return loadBitmaps;
    }
    
  5. Implemente o LoadBitmaps método .

    // Loads all bitmap files that exist at the provided path.
    IEnumerable<Bitmap> LoadBitmaps(string path)
    {
       List<Bitmap> bitmaps = new List<Bitmap>();
    
       // Load a variety of image types.
       foreach (string bitmapType in
          new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" })
       {
          // Load each bitmap for the current extension.
          foreach (string fileName in Directory.GetFiles(path, bitmapType))
          {
             // Throw OperationCanceledException if cancellation is requested.
             cancellationTokenSource.Token.ThrowIfCancellationRequested();
    
             try
             {
                // Add the Bitmap object to the collection.
                bitmaps.Add(new Bitmap(fileName));
             }
             catch (Exception)
             {
                // TODO: A complete application might handle the error.
             }
          }
       }
       return bitmaps;
    }
    
  6. Implemente o CreateCompositeBitmap método .

    // Creates a composite bitmap from the provided collection of Bitmap objects.
    // This method computes the average color of each pixel among all bitmaps
    // to create the composite image.
    Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps)
    {
       Bitmap[] bitmapArray = bitmaps.ToArray();
    
       // Compute the maximum width and height components of all
       // bitmaps in the collection.
       Rectangle largest = new Rectangle();
       foreach (var bitmap in bitmapArray)
       {
          if (bitmap.Width > largest.Width)
             largest.Width = bitmap.Width;
          if (bitmap.Height > largest.Height)
             largest.Height = bitmap.Height;
       }
    
       // Create a 32-bit Bitmap object with the greatest dimensions.
       Bitmap result = new Bitmap(largest.Width, largest.Height,
          PixelFormat.Format32bppArgb);
    
       // Lock the result Bitmap.
       var resultBitmapData = result.LockBits(
          new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly,
          result.PixelFormat);
    
       // Lock each source bitmap to create a parallel list of BitmapData objects.
       var bitmapDataList = (from bitmap in bitmapArray
                             select bitmap.LockBits(
                               new Rectangle(new Point(), bitmap.Size),
                               ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
                            .ToList();
    
       // Compute each column in parallel.
       Parallel.For(0, largest.Width, new ParallelOptions
       {
          CancellationToken = cancellationTokenSource.Token
       },
       i =>
       {
          // Compute each row.
          for (int j = 0; j < largest.Height; j++)
          {
             // Counts the number of bitmaps whose dimensions
             // contain the current location.
             int count = 0;
    
             // The sum of all alpha, red, green, and blue components.
             int a = 0, r = 0, g = 0, b = 0;
    
             // For each bitmap, compute the sum of all color components.
             foreach (var bitmapData in bitmapDataList)
             {
                // Ensure that we stay within the bounds of the image.
                if (bitmapData.Width > i && bitmapData.Height > j)
                {
                   unsafe
                   {
                      byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride));
                      byte* pix = (byte*)(row + (4 * i));
                      a += *pix; pix++;
                      r += *pix; pix++;
                      g += *pix; pix++;
                      b += *pix;
                   }
                   count++;
                }
             }
    
             //prevent divide by zero in bottom right pixelless corner
             if (count == 0)
                 break;
    
             unsafe
             {
                // Compute the average of each color component.
                a /= count;
                r /= count;
                g /= count;
                b /= count;
    
                // Set the result pixel.
                byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride));
                byte* pix = (byte*)(row + (4 * i));
                *pix = (byte)a; pix++;
                *pix = (byte)r; pix++;
                *pix = (byte)g; pix++;
                *pix = (byte)b;
             }
          }
       });
    
       // Unlock the source bitmaps.
       for (int i = 0; i < bitmapArray.Length; i++)
       {
          bitmapArray[i].UnlockBits(bitmapDataList[i]);
       }
    
       // Unlock the result bitmap.
       result.UnlockBits(resultBitmapData);
    
       // Return the result.
       return result;
    }
    

    Nota

    A versão C# do CreateCompositeBitmap método utiliza ponteiros para permitir o System.Drawing.Bitmap processamento eficiente dos objetos. Por conseguinte, tem de ativar a opção Permitir código não seguro no seu projeto para utilizar a palavra-chave não segura . Para obter mais informações sobre como ativar código não seguro num projeto do Visual C#, consulte Build Page, Project Designer (C#).

A tabela seguinte descreve os membros da rede.

Membro Tipo Description
loadBitmaps TransformBlock<TInput,TOutput> Assume um caminho de pasta como entrada e produz uma coleção de Bitmap objetos como saída.
createCompositeBitmap TransformBlock<TInput,TOutput> Utiliza uma coleção de Bitmap objetos como entrada e produz um mapa de bits composto como saída.
displayCompositeBitmap ActionBlock<TInput> Apresenta o mapa de bits composto no formulário.
operationCancelled ActionBlock<TInput> Apresenta uma imagem para indicar que a operação foi cancelada e permite que o utilizador selecione outra pasta.

Para ligar os blocos de fluxo de dados para formar uma rede, este exemplo utiliza o LinkTo método . O LinkTo método contém uma versão sobrecarregada que utiliza um Predicate<T> objeto que determina se o bloco de destino aceita ou rejeita uma mensagem. Este mecanismo de filtragem permite que os blocos de mensagens recebam apenas determinados valores. Neste exemplo, a rede pode ramificar de uma de duas formas. O ramo principal carrega as imagens do disco, cria a imagem composta e apresenta essa imagem no formulário. O ramo alternativo cancela a operação atual. Os Predicate<T> objetos permitem que os blocos de fluxo de dados ao longo do ramo principal mudem para o ramo alternativo ao rejeitar determinadas mensagens. Por exemplo, se o utilizador cancelar a operação, o bloco createCompositeBitmap de fluxo de dados produz null (Nothing no Visual Basic) como resultado. O bloco displayCompositeBitmap de fluxo de dados rejeita os valores null de entrada e, por conseguinte, a mensagem é disponibilizada a operationCancelled. O bloco operationCancelled de fluxo de dados aceita todas as mensagens e, por conseguinte, apresenta uma imagem para indicar que a operação foi cancelada.

A ilustração seguinte mostra a rede de processamento de imagens:

Ilustração que mostra a rede de processamento de imagens.

Uma vez que os displayCompositeBitmap blocos de fluxo de dados e operationCancelled atuam na interface de utilizador, é importante que estas ações ocorram no thread da interface de utilizador. Para tal, durante a construção, cada um destes objetos fornece um ExecutionDataflowBlockOptions objeto que tem a TaskScheduler propriedade definida como TaskScheduler.FromCurrentSynchronizationContext. O TaskScheduler.FromCurrentSynchronizationContext método cria um TaskScheduler objeto que executa o trabalho no contexto de sincronização atual. Uma vez que o CreateImageProcessingNetwork método é chamado a partir do processador do botão Escolher Pasta , que é executado no thread da interface de utilizador, as ações dos displayCompositeBitmap blocos de fluxo de dados e operationCancelled também são executadas no thread da interface de utilizador.

Este exemplo utiliza um token de cancelamento partilhado em vez de definir a CancellationToken propriedade porque a propriedade cancela permanentemente a CancellationToken execução do bloco de fluxo de dados. Um token de cancelamento permite que este exemplo reutilize a mesma rede de fluxo de dados várias vezes, mesmo quando o utilizador cancela uma ou mais operações. Para obter um exemplo que utiliza CancellationToken para cancelar permanentemente a execução de um bloco de fluxo de dados, veja Como: Cancelar um Bloco de Fluxo de Dados.

Ligar a Rede de Fluxo de Dados à Interface de Utilizador

Esta secção descreve como ligar a rede de fluxo de dados à interface de utilizador. A criação da imagem composta e o cancelamento da operação são iniciados a partir dos botões Escolher Pasta e Cancelar . Quando o utilizador escolhe um destes botões, a ação adequada é iniciada de forma assíncrona.

Para ligar a Rede de Fluxo de Dados à Interface de Utilizador

  1. No estruturador de formulários do formulário principal, crie um processador de eventos para o Click evento para o botão Escolher Pasta .

  2. Implemente o Click evento para o botão Escolher Pasta .

    // Event handler for the Choose Folder button.
    private void toolStripButton1_Click(object sender, EventArgs e)
    {
       // Create a FolderBrowserDialog object to enable the user to
       // select a folder.
       FolderBrowserDialog dlg = new FolderBrowserDialog
       {
          ShowNewFolderButton = false
       };
    
       // Set the selected path to the common Sample Pictures folder
       // if it exists.
       string initialDirectory = Path.Combine(
          Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures),
          "Sample Pictures");
       if (Directory.Exists(initialDirectory))
       {
          dlg.SelectedPath = initialDirectory;
       }
    
       // Show the dialog and process the dataflow network.
       if (dlg.ShowDialog() == DialogResult.OK)
       {
          // Create a new CancellationTokenSource object to enable
          // cancellation.
          cancellationTokenSource = new CancellationTokenSource();
    
          // Create the image processing network if needed.
          headBlock ??= CreateImageProcessingNetwork();
    
          // Post the selected path to the network.
          headBlock.Post(dlg.SelectedPath);
    
          // Enable the Cancel button and disable the Choose Folder button.
          toolStripButton1.Enabled = false;
          toolStripButton2.Enabled = true;
    
          // Show a wait cursor.
          Cursor = Cursors.WaitCursor;
       }
    }
    
  3. No estruturador de formulários do formulário principal, crie um processador de eventos para o Click evento para o botão Cancelar .

  4. Implemente o Click evento para o botão Cancelar .

    // Event handler for the Cancel button.
    private void toolStripButton2_Click(object sender, EventArgs e)
    {
       // Signal the request for cancellation. The current component of
       // the dataflow network will respond to the cancellation request.
       cancellationTokenSource.Cancel();
    }
    

O Exemplo Completo

O exemplo seguinte mostra o código completo para estas instruções.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

namespace CompositeImages
{
   public partial class Form1 : Form
   {
      // The head of the dataflow network.
      ITargetBlock<string> headBlock = null;

      // Enables the user interface to signal cancellation to the network.
      CancellationTokenSource cancellationTokenSource;

      public Form1()
      {
         InitializeComponent();
      }

      // Creates the image processing dataflow network and returns the
      // head node of the network.
      ITargetBlock<string> CreateImageProcessingNetwork()
      {
         //
         // Create the dataflow blocks that form the network.
         //

         // Create a dataflow block that takes a folder path as input
         // and returns a collection of Bitmap objects.
         var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path =>
            {
               try
               {
                  return LoadBitmaps(path);
               }
               catch (OperationCanceledException)
               {
                  // Handle cancellation by passing the empty collection
                  // to the next stage of the network.
                  return Enumerable.Empty<Bitmap>();
               }
            });

         // Create a dataflow block that takes a collection of Bitmap objects
         // and returns a single composite bitmap.
         var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps =>
            {
               try
               {
                  return CreateCompositeBitmap(bitmaps);
               }
               catch (OperationCanceledException)
               {
                  // Handle cancellation by passing null to the next stage
                  // of the network.
                  return null;
               }
            });

         // Create a dataflow block that displays the provided bitmap on the form.
         var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap =>
            {
               // Display the bitmap.
               pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
               pictureBox1.Image = bitmap;

               // Enable the user to select another folder.
               toolStripButton1.Enabled = true;
               toolStripButton2.Enabled = false;
               Cursor = DefaultCursor;
            },
            // Specify a task scheduler from the current synchronization context
            // so that the action runs on the UI thread.
            new ExecutionDataflowBlockOptions
            {
                TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
            });

         // Create a dataflow block that responds to a cancellation request by
         // displaying an image to indicate that the operation is cancelled and
         // enables the user to select another folder.
         var operationCancelled = new ActionBlock<object>(delegate
            {
               // Display the error image to indicate that the operation
               // was cancelled.
               pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
               pictureBox1.Image = pictureBox1.ErrorImage;

               // Enable the user to select another folder.
               toolStripButton1.Enabled = true;
               toolStripButton2.Enabled = false;
               Cursor = DefaultCursor;
            },
            // Specify a task scheduler from the current synchronization context
            // so that the action runs on the UI thread.
            new ExecutionDataflowBlockOptions
            {
               TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
            });

         //
         // Connect the network.
         //

         // Link loadBitmaps to createCompositeBitmap.
         // The provided predicate ensures that createCompositeBitmap accepts the
         // collection of bitmaps only if that collection has at least one member.
         loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0);

         // Also link loadBitmaps to operationCancelled.
         // When createCompositeBitmap rejects the message, loadBitmaps
         // offers the message to operationCancelled.
         // operationCancelled accepts all messages because we do not provide a
         // predicate.
         loadBitmaps.LinkTo(operationCancelled);

         // Link createCompositeBitmap to displayCompositeBitmap.
         // The provided predicate ensures that displayCompositeBitmap accepts the
         // bitmap only if it is non-null.
         createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null);

         // Also link createCompositeBitmap to operationCancelled.
         // When displayCompositeBitmap rejects the message, createCompositeBitmap
         // offers the message to operationCancelled.
         // operationCancelled accepts all messages because we do not provide a
         // predicate.
         createCompositeBitmap.LinkTo(operationCancelled);

         // Return the head of the network.
         return loadBitmaps;
      }

      // Loads all bitmap files that exist at the provided path.
      IEnumerable<Bitmap> LoadBitmaps(string path)
      {
         List<Bitmap> bitmaps = new List<Bitmap>();

         // Load a variety of image types.
         foreach (string bitmapType in
            new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" })
         {
            // Load each bitmap for the current extension.
            foreach (string fileName in Directory.GetFiles(path, bitmapType))
            {
               // Throw OperationCanceledException if cancellation is requested.
               cancellationTokenSource.Token.ThrowIfCancellationRequested();

               try
               {
                  // Add the Bitmap object to the collection.
                  bitmaps.Add(new Bitmap(fileName));
               }
               catch (Exception)
               {
                  // TODO: A complete application might handle the error.
               }
            }
         }
         return bitmaps;
      }

      // Creates a composite bitmap from the provided collection of Bitmap objects.
      // This method computes the average color of each pixel among all bitmaps
      // to create the composite image.
      Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps)
      {
         Bitmap[] bitmapArray = bitmaps.ToArray();

         // Compute the maximum width and height components of all
         // bitmaps in the collection.
         Rectangle largest = new Rectangle();
         foreach (var bitmap in bitmapArray)
         {
            if (bitmap.Width > largest.Width)
               largest.Width = bitmap.Width;
            if (bitmap.Height > largest.Height)
               largest.Height = bitmap.Height;
         }

         // Create a 32-bit Bitmap object with the greatest dimensions.
         Bitmap result = new Bitmap(largest.Width, largest.Height,
            PixelFormat.Format32bppArgb);

         // Lock the result Bitmap.
         var resultBitmapData = result.LockBits(
            new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly,
            result.PixelFormat);

         // Lock each source bitmap to create a parallel list of BitmapData objects.
         var bitmapDataList = (from bitmap in bitmapArray
                               select bitmap.LockBits(
                                 new Rectangle(new Point(), bitmap.Size),
                                 ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
                              .ToList();

         // Compute each column in parallel.
         Parallel.For(0, largest.Width, new ParallelOptions
         {
            CancellationToken = cancellationTokenSource.Token
         },
         i =>
         {
            // Compute each row.
            for (int j = 0; j < largest.Height; j++)
            {
               // Counts the number of bitmaps whose dimensions
               // contain the current location.
               int count = 0;

               // The sum of all alpha, red, green, and blue components.
               int a = 0, r = 0, g = 0, b = 0;

               // For each bitmap, compute the sum of all color components.
               foreach (var bitmapData in bitmapDataList)
               {
                  // Ensure that we stay within the bounds of the image.
                  if (bitmapData.Width > i && bitmapData.Height > j)
                  {
                     unsafe
                     {
                        byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride));
                        byte* pix = (byte*)(row + (4 * i));
                        a += *pix; pix++;
                        r += *pix; pix++;
                        g += *pix; pix++;
                        b += *pix;
                     }
                     count++;
                  }
               }

               //prevent divide by zero in bottom right pixelless corner
               if (count == 0)
                   break;

               unsafe
               {
                  // Compute the average of each color component.
                  a /= count;
                  r /= count;
                  g /= count;
                  b /= count;

                  // Set the result pixel.
                  byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride));
                  byte* pix = (byte*)(row + (4 * i));
                  *pix = (byte)a; pix++;
                  *pix = (byte)r; pix++;
                  *pix = (byte)g; pix++;
                  *pix = (byte)b;
               }
            }
         });

         // Unlock the source bitmaps.
         for (int i = 0; i < bitmapArray.Length; i++)
         {
            bitmapArray[i].UnlockBits(bitmapDataList[i]);
         }

         // Unlock the result bitmap.
         result.UnlockBits(resultBitmapData);

         // Return the result.
         return result;
      }

      // Event handler for the Choose Folder button.
      private void toolStripButton1_Click(object sender, EventArgs e)
      {
         // Create a FolderBrowserDialog object to enable the user to
         // select a folder.
         FolderBrowserDialog dlg = new FolderBrowserDialog
         {
            ShowNewFolderButton = false
         };

         // Set the selected path to the common Sample Pictures folder
         // if it exists.
         string initialDirectory = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures),
            "Sample Pictures");
         if (Directory.Exists(initialDirectory))
         {
            dlg.SelectedPath = initialDirectory;
         }

         // Show the dialog and process the dataflow network.
         if (dlg.ShowDialog() == DialogResult.OK)
         {
            // Create a new CancellationTokenSource object to enable
            // cancellation.
            cancellationTokenSource = new CancellationTokenSource();

            // Create the image processing network if needed.
            headBlock ??= CreateImageProcessingNetwork();

            // Post the selected path to the network.
            headBlock.Post(dlg.SelectedPath);

            // Enable the Cancel button and disable the Choose Folder button.
            toolStripButton1.Enabled = false;
            toolStripButton2.Enabled = true;

            // Show a wait cursor.
            Cursor = Cursors.WaitCursor;
         }
      }

      // Event handler for the Cancel button.
      private void toolStripButton2_Click(object sender, EventArgs e)
      {
         // Signal the request for cancellation. The current component of
         // the dataflow network will respond to the cancellation request.
         cancellationTokenSource.Cancel();
      }

      ~Form1()
      {
         cancellationTokenSource.Dispose();
      }
   }
}

A ilustração seguinte mostra o resultado típico da pasta \Sample Pictures\ comum.

O TPLDataflow_CompositeImages da

Ver também