/// <summary> /// Construct integral image from source grayscale image. /// </summary> /// /// <param name="image">Source unmanaged image.</param> /// /// <returns>Returns integral image.</returns> /// /// <exception cref="UnsupportedImageFormatException">The source image has incorrect pixel format.</exception> /// public static IntegralImage FromBitmap(UnmanagedImage image) { // check image format if (image.PixelFormat != PixelFormat.Format8bppIndexed) { throw new ArgumentException("Source image can be grayscale (8 bpp indexed) image only."); } // get source image size int width = image.Width; int height = image.Height; int offset = image.Stride - width; // create integral image var im = new IntegralImage(width, height); uint[][] matrix = im.matrix; // do the job unsafe { byte *src = (byte *)image.ImageData.ToPointer(); // for each line for (int y = 1; y <= height; y++) { uint rowSum = 0; // for each pixel for (int x = 1; x <= width; x++, src++) { image.CheckBounds(src); rowSum += *src; matrix[y][x] = rowSum + matrix[y - 1][x]; } src += offset; } } return(im); }
/// <summary> /// Process image looking for corners. /// </summary> /// /// <param name="image">Unmanaged source image to process.</param> /// /// <returns>Returns array of found corners (X-Y coordinates).</returns> /// /// <exception cref="UnsupportedImageFormatException">The source image has incorrect pixel format.</exception> /// public List <IntPoint> ProcessImage(UnmanagedImage image) { // check image format if ( (image.PixelFormat != PixelFormat.Format8bppIndexed) && (image.PixelFormat != PixelFormat.Format24bppRgb) && (image.PixelFormat != PixelFormat.Format32bppRgb) && (image.PixelFormat != PixelFormat.Format32bppArgb) ) { throw new UnsupportedImageFormatException("Unsupported pixel format of the source image."); } // get source image size int width = image.Width; int height = image.Height; int stride = image.Stride; int pixelSize = Bitmap.GetPixelFormatSize(image.PixelFormat) / 8; // window radius int windowRadius = windowSize / 2; // offset int offset = stride - windowSize * pixelSize; // create moravec cornerness map int[,] moravecMap = new int[height, width]; // do the job unsafe { byte *ptr = (byte *)image.ImageData.ToPointer(); // for each row for (int y = windowRadius, maxY = height - windowRadius; y < maxY; y++) { // for each pixel for (int x = windowRadius, maxX = width - windowRadius; x < maxX; x++) { int minSum = int.MaxValue; // go through 8 possible shifting directions for (int k = 0; k < 8; k++) { // calculate center of shifted window int sy = y + yDelta[k]; int sx = x + xDelta[k]; // check if shifted window is within the image if ( (sy < windowRadius) || (sy >= maxY) || (sx < windowRadius) || (sx >= maxX) ) { // skip this shifted window continue; } int sum = 0; byte *ptr1 = ptr + (y - windowRadius) * stride + (x - windowRadius) * pixelSize; byte *ptr2 = ptr + (sy - windowRadius) * stride + (sx - windowRadius) * pixelSize; // for each windows' rows for (int i = 0; i < windowSize; i++) { // for each windows' pixels for (int j = 0, maxJ = windowSize * pixelSize; j < maxJ; j++, ptr1++, ptr2++) { image.CheckBounds(ptr1); image.CheckBounds(ptr2); int dif = *ptr1 - *ptr2; sum += dif * dif; } ptr1 += offset; ptr2 += offset; } // check if the sum is mimimal if (sum < minSum) { minSum = sum; } } // threshold the minimum sum if (minSum < threshold) { minSum = 0; } moravecMap[y, x] = minSum; } } } // collect interesting points - only those points, which are local maximums List <IntPoint> cornersList = new List <IntPoint>(); // for each row for (int y = windowRadius, maxY = height - windowRadius; y < maxY; y++) { // for each pixel for (int x = windowRadius, maxX = width - windowRadius; x < maxX; x++) { int currentValue = moravecMap[y, x]; // for each windows' rows for (int i = -windowRadius; (currentValue != 0) && (i <= windowRadius); i++) { // for each windows' pixels for (int j = -windowRadius; j <= windowRadius; j++) { if (moravecMap[y + i, x + j] > currentValue) { currentValue = 0; break; } } } // check if this point is really interesting if (currentValue != 0) { cornersList.Add(new IntPoint(x, y)); } } } return(cornersList); }
/// <summary> /// Actual objects map building. /// </summary> /// /// <param name="image">Unmanaged image to process.</param> /// /// <remarks>The method supports 8 bpp indexed grayscale images and 24/32 bpp color images.</remarks> /// /// <exception cref="UnsupportedImageFormatException">Unsupported pixel format of the source image.</exception> /// <exception cref="InvalidImagePropertiesException">Cannot process images that are one pixel wide. Rotate the image /// or use <see cref="RecursiveBlobCounter"/>.</exception> /// protected override void BuildObjectsMap(UnmanagedImage image) { int stride = image.Stride; // check pixel format if ((image.PixelFormat != PixelFormat.Format8bppIndexed) && (image.PixelFormat != PixelFormat.Format24bppRgb) && (image.PixelFormat != PixelFormat.Format32bppRgb) && (image.PixelFormat != PixelFormat.Format32bppArgb) && (image.PixelFormat != PixelFormat.Format32bppPArgb)) { throw new UnsupportedImageFormatException("Unsupported pixel format of the source image."); } // we don't want one pixel width images if (ImageWidth == 1) { throw new InvalidImagePropertiesException("BlobCounter cannot process images that are one pixel wide. Rotate the image or use RecursiveBlobCounter."); } int imageWidthM1 = ImageWidth - 1; // allocate labels array ObjectLabels = new int[ImageWidth * ImageHeight]; // initial labels count int labelsCount = 0; // create map int maxObjects = ((ImageWidth / 2) + 1) * ((ImageHeight / 2) + 1) + 1; int[] map = new int[maxObjects]; // initially map all labels to themself for (int i = 0; i < maxObjects; i++) { map[i] = i; } // do the job unsafe { byte *src = (byte *)image.ImageData.ToPointer(); int p = 0; if (image.PixelFormat == PixelFormat.Format8bppIndexed) { int offset = stride - ImageWidth; image.CheckBounds(src); // 1 - for pixels of the first row if (*src > backgroundThresholdG) { ObjectLabels[p] = ++labelsCount; } ++src; ++p; // process the rest of the first row for (int x = 1; x < ImageWidth; x++, src++, p++) { image.CheckBounds(src); // check if we need to label current pixel if (*src > backgroundThresholdG) { image.CheckBounds(src - 1); // check if the previous pixel already was labeled if (src[-1] > backgroundThresholdG) { // label current pixel, as the previous ObjectLabels[p] = ObjectLabels[p - 1]; } else { // create new label ObjectLabels[p] = ++labelsCount; } } } src += offset; // 2 - for other rows // for each row for (int y = 1; y < ImageHeight; y++) { // for the first pixel of the row, we need to check // only upper and upper-right pixels image.CheckBounds(src); if (*src > backgroundThresholdG) { image.CheckBounds(src - stride); image.CheckBounds(src + 1 - stride); // check surrounding pixels if (src[-stride] > backgroundThresholdG) { // label current pixel, as the above ObjectLabels[p] = ObjectLabels[p - ImageWidth]; } else if (src[1 - stride] > backgroundThresholdG) { // label current pixel, as the above right ObjectLabels[p] = ObjectLabels[p + 1 - ImageWidth]; } else { // create new label ObjectLabels[p] = ++labelsCount; } } ++src; ++p; // check left pixel and three upper pixels for the rest of pixels for (int x = 1; x < imageWidthM1; x++, src++, p++) { image.CheckBounds(src); if (*src > backgroundThresholdG) { image.CheckBounds(src - 1); image.CheckBounds(src - 1 - stride); image.CheckBounds(src - stride); image.CheckBounds(src + 1 - stride); // check surrounding pixels if (src[-1] > backgroundThresholdG) { // label current pixel, as the left ObjectLabels[p] = ObjectLabels[p - 1]; } else if (src[-1 - stride] > backgroundThresholdG) { // label current pixel, as the above left ObjectLabels[p] = ObjectLabels[p - 1 - ImageWidth]; } else if (src[-stride] > backgroundThresholdG) { // label current pixel, as the above ObjectLabels[p] = ObjectLabels[p - ImageWidth]; } if (src[1 - stride] > backgroundThresholdG) { if (ObjectLabels[p] == 0) { // label current pixel, as the above right ObjectLabels[p] = ObjectLabels[p + 1 - ImageWidth]; } else { int l1 = ObjectLabels[p]; int l2 = ObjectLabels[p + 1 - ImageWidth]; if ((l1 != l2) && (map[l1] != map[l2])) { // merge if (map[l1] == l1) { // map left value to the right map[l1] = map[l2]; } else if (map[l2] == l2) { // map right value to the left map[l2] = map[l1]; } else { // both values already mapped map[map[l1]] = map[l2]; map[l1] = map[l2]; } // reindex for (int i = 1; i <= labelsCount; i++) { if (map[i] != i) { // reindex int j = map[i]; while (j != map[j]) { j = map[j]; } map[i] = j; } } } } } // label the object if it is not yet if (ObjectLabels[p] == 0) { // create new label ObjectLabels[p] = ++labelsCount; } } } // for the last pixel of the row, we need to check // only upper and upper-left pixels image.CheckBounds(src); if (*src > backgroundThresholdG) { image.CheckBounds(src - 1); image.CheckBounds(src - 1 - stride); image.CheckBounds(src - stride); // check surrounding pixels if (src[-1] > backgroundThresholdG) { // label current pixel, as the left ObjectLabels[p] = ObjectLabels[p - 1]; } else if (src[-1 - stride] > backgroundThresholdG) { // label current pixel, as the above left ObjectLabels[p] = ObjectLabels[p - 1 - ImageWidth]; } else if (src[-stride] > backgroundThresholdG) { // label current pixel, as the above ObjectLabels[p] = ObjectLabels[p - ImageWidth]; } else { // create new label ObjectLabels[p] = ++labelsCount; } } ++src; ++p; src += offset; } } else { // color images int pixelSize = Bitmap.GetPixelFormatSize(image.PixelFormat) / 8; int offset = stride - ImageWidth * pixelSize; int strideM1 = stride - pixelSize; int strideP1 = stride + pixelSize; // 1 - for pixels of the first row if ((src[RGB.R] | src[RGB.G] | src[RGB.B]) != 0) { ObjectLabels[p] = ++labelsCount; } src += pixelSize; ++p; // process the rest of the first row for (int x = 1; x < ImageWidth; x++, src += pixelSize, p++) { // check if we need to label current pixel if ((src[RGB.R] > backgroundThresholdR) || (src[RGB.G] > backgroundThresholdG) || (src[RGB.B] > backgroundThresholdB)) { // check if the previous pixel already was labeled if ((src[RGB.R - pixelSize] > backgroundThresholdR) || (src[RGB.G - pixelSize] > backgroundThresholdG) || (src[RGB.B - pixelSize] > backgroundThresholdB)) { // label current pixel, as the previous ObjectLabels[p] = ObjectLabels[p - 1]; } else { // create new label ObjectLabels[p] = ++labelsCount; } } } src += offset; // 2 - for other rows // for each row for (int y = 1; y < ImageHeight; y++) { // for the first pixel of the row, we need to check // only upper and upper-right pixels if ((src[RGB.R] > backgroundThresholdR) || (src[RGB.G] > backgroundThresholdG) || (src[RGB.B] > backgroundThresholdB)) { // check surrounding pixels if ((src[RGB.R - stride] > backgroundThresholdR) || (src[RGB.G - stride] > backgroundThresholdG) || (src[RGB.B - stride] > backgroundThresholdB)) { // label current pixel, as the above ObjectLabels[p] = ObjectLabels[p - ImageWidth]; } else if ((src[RGB.R - strideM1] > backgroundThresholdR) || (src[RGB.G - strideM1] > backgroundThresholdG) || (src[RGB.B - strideM1] > backgroundThresholdB)) { // label current pixel, as the above right ObjectLabels[p] = ObjectLabels[p + 1 - ImageWidth]; } else { // create new label ObjectLabels[p] = ++labelsCount; } } src += pixelSize; ++p; // check left pixel and three upper pixels for the rest of pixels for (int x = 1; x < ImageWidth - 1; x++, src += pixelSize, p++) { if ((src[RGB.R] > backgroundThresholdR) || (src[RGB.G] > backgroundThresholdG) || (src[RGB.B] > backgroundThresholdB)) { // check surrounding pixels if ((src[RGB.R - pixelSize] > backgroundThresholdR) || (src[RGB.G - pixelSize] > backgroundThresholdG) || (src[RGB.B - pixelSize] > backgroundThresholdB)) { // label current pixel, as the left ObjectLabels[p] = ObjectLabels[p - 1]; } else if ((src[RGB.R - strideP1] > backgroundThresholdR) || (src[RGB.G - strideP1] > backgroundThresholdG) || (src[RGB.B - strideP1] > backgroundThresholdB)) { // label current pixel, as the above left ObjectLabels[p] = ObjectLabels[p - 1 - ImageWidth]; } else if ((src[RGB.R - stride] > backgroundThresholdR) || (src[RGB.G - stride] > backgroundThresholdG) || (src[RGB.B - stride] > backgroundThresholdB)) { // label current pixel, as the above ObjectLabels[p] = ObjectLabels[p - ImageWidth]; } if ((src[RGB.R - strideM1] > backgroundThresholdR) || (src[RGB.G - strideM1] > backgroundThresholdG) || (src[RGB.B - strideM1] > backgroundThresholdB)) { if (ObjectLabels[p] == 0) { // label current pixel, as the above right ObjectLabels[p] = ObjectLabels[p + 1 - ImageWidth]; } else { int l1 = ObjectLabels[p]; int l2 = ObjectLabels[p + 1 - ImageWidth]; if ((l1 != l2) && (map[l1] != map[l2])) { // merge if (map[l1] == l1) { // map left value to the right map[l1] = map[l2]; } else if (map[l2] == l2) { // map right value to the left map[l2] = map[l1]; } else { // both values already mapped map[map[l1]] = map[l2]; map[l1] = map[l2]; } // reindex for (int i = 1; i <= labelsCount; i++) { if (map[i] != i) { // reindex int j = map[i]; while (j != map[j]) { j = map[j]; } map[i] = j; } } } } } // label the object if it is not yet if (ObjectLabels[p] == 0) { // create new label ObjectLabels[p] = ++labelsCount; } } } // for the last pixel of the row, we need to check // only upper and upper-left pixels if ((src[RGB.R] > backgroundThresholdR) || (src[RGB.G] > backgroundThresholdG) || (src[RGB.B] > backgroundThresholdB)) { // check surrounding pixels if ((src[RGB.R - pixelSize] > backgroundThresholdR) || (src[RGB.G - pixelSize] > backgroundThresholdG) || (src[RGB.B - pixelSize] > backgroundThresholdB)) { // label current pixel, as the left ObjectLabels[p] = ObjectLabels[p - 1]; } else if ((src[RGB.R - strideP1] > backgroundThresholdR) || (src[RGB.G - strideP1] > backgroundThresholdG) || (src[RGB.B - strideP1] > backgroundThresholdB)) { // label current pixel, as the above left ObjectLabels[p] = ObjectLabels[p - 1 - ImageWidth]; } else if ((src[RGB.R - stride] > backgroundThresholdR) || (src[RGB.G - stride] > backgroundThresholdG) || (src[RGB.B - stride] > backgroundThresholdB)) { // label current pixel, as the above ObjectLabels[p] = ObjectLabels[p - ImageWidth]; } else { // create new label ObjectLabels[p] = ++labelsCount; } } src += pixelSize; ++p; src += offset; } } } // allocate remapping array int[] reMap = new int[map.Length]; // count objects and prepare remapping array ObjectsCount = 0; for (int i = 1; i <= labelsCount; i++) { if (map[i] == i) { // increase objects count reMap[i] = ++ObjectsCount; } } // second pass to complete remapping for (int i = 1; i <= labelsCount; i++) { if (map[i] != i) { reMap[i] = reMap[map[i]]; } } // repair object labels for (int i = 0, n = ObjectLabels.Length; i < n; i++) { ObjectLabels[i] = reMap[ObjectLabels[i]]; } }
/// <summary> /// Constructs a new Integral image from an unmanaged image. /// </summary> /// /// <param name="image">The source image from where the integral image should be computed.</param> /// <param name="channel">The image channel to consider in the computations. Default is 0.</param> /// <param name="computeTilted"><c>True</c> to compute the tilted version of the integral image, /// <c>false</c> otherwise. Default is false.</param> /// /// <returns> /// The <see cref="IntegralImage2"/> representation of /// the <paramref name="image">source image</paramref>.</returns> /// public static IntegralImage2 FromBitmap(UnmanagedImage image, int channel, bool computeTilted) { // check image format if (!(image.PixelFormat == PixelFormat.Format8bppIndexed || image.PixelFormat == PixelFormat.Format24bppRgb || image.PixelFormat == PixelFormat.Format32bppArgb || image.PixelFormat == PixelFormat.Format32bppRgb)) { throw new UnsupportedImageFormatException("Only grayscale, 24 and 32 bpp RGB images are supported."); } int pixelSize = System.Drawing.Image.GetPixelFormatSize(image.PixelFormat) / 8; // get source image size int width = image.Width; int height = image.Height; int stride = image.Stride; int offset = stride - width * pixelSize; // create integral image IntegralImage2 im = new IntegralImage2(width, height, computeTilted); long * nSum = im.nSum; long * sSum = im.sSum; long * tSum = im.tSum; int nWidth = im.nWidth; int tWidth = im.tWidth; if (image.PixelFormat == PixelFormat.Format8bppIndexed && channel != 0) { throw new ArgumentException("Only the first channel is available for 8 bpp images.", "channel"); } byte *srcStart = (byte *)image.ImageData.ToPointer() + channel; // do the job byte *src = srcStart; // for each line for (int y = 1; y <= height; y++) { int yy = nWidth * (y); int y1 = nWidth * (y - 1); // for each pixel for (int x = 1; x <= width; x++, src += pixelSize) { image.CheckBounds(src); long p1 = *src; long p2 = p1 * p1; int r = yy + (x); int a = yy + (x - 1); int b = y1 + (x); int c = y1 + (x - 1); nSum[r] = p1 + nSum[a] + nSum[b] - nSum[c]; sSum[r] = p2 + sSum[a] + sSum[b] - sSum[c]; } src += offset; } if (computeTilted) { src = srcStart; // Left-to-right, top-to-bottom pass for (int y = 1; y <= height; y++, src += offset) { int yy = tWidth * (y); int y1 = tWidth * (y - 1); for (int x = 2; x < width + 2; x++, src += pixelSize) { image.CheckBounds(src); int a = y1 + (x - 1); int b = yy + (x - 1); int c = y1 + (x - 2); int r = yy + (x); tSum[r] = *src + tSum[a] + tSum[b] - tSum[c]; } } { int yy = tWidth * (height); int y1 = tWidth * (height + 1); for (int x = 2; x < width + 2; x++, src += pixelSize) { int a = yy + (x - 1); int c = yy + (x - 2); int b = y1 + (x - 1); int r = y1 + (x); tSum[r] = tSum[a] + tSum[b] - tSum[c]; } } // Right-to-left, bottom-to-top pass for (int y = height; y >= 0; y--) { int yy = tWidth * (y); int y1 = tWidth * (y + 1); for (int x = width + 1; x >= 1; x--) { int r = yy + (x); int b = y1 + (x - 1); tSum[r] += tSum[b]; } } for (int y = height + 1; y >= 0; y--) { int yy = tWidth * (y); for (int x = width + 1; x >= 2; x--) { int r = yy + (x); int b = yy + (x - 2); tSum[r] -= tSum[b]; } } } return(im); }
/// <summary> /// Extracts the contour from a single object in a grayscale image. /// </summary> /// /// <param name="image">A grayscale image.</param> /// <returns>A list of <see cref="IntPoint"/>s defining a contour.</returns> /// public List <IntPoint> FindContour(UnmanagedImage image) { CheckPixelFormat(image.PixelFormat); int width = image.Width; int height = image.Height; int stride = image.Stride; List <IntPoint> contour = new List <IntPoint>(); unsafe { byte *src = (byte *)image.ImageData.ToPointer(); byte *start = null; IntPoint prevPosition = new IntPoint(); // 1. Find the lowest point in the image // The lowest point is searched first by lowest X, then lowest Y, to use // the same ordering of AForge.NET's GrahamConvexHull. Unfortunately, this // means we have to search our image by inspecting columns rather than rows. bool found = false; byte *col = src; for (int x = 0; x < width && !found; x++, col++) { byte *row = col; for (int y = 0; y < height && !found; y++, row += stride) { image.CheckBounds(row); if (*row > Threshold) { start = row; prevPosition = new IntPoint(x, y); contour.Add(prevPosition); found = true; } } } if (contour.Count == 0) { // Empty image return(contour); } // 2. Beginning on the first point, starting from left // neighbor and going into counter-clockwise direction, // find a neighbor pixel which is black. int[] windowOffset = { +1, // 0: Right -stride + 1, // 1: Top-Right -stride, // 2: Top -stride - 1, // 3: Top-Left -1, // 4: Left +stride - 1, // 5: Bottom-Left +stride, // 6: Bottom +stride + 1, // 7: Bottom-Right }; int direction = 4; // 4: Left byte *current = start; byte *previous = null; do // Search until we find a dead end (or the starting pixel) { found = false; // Search in the neighborhood window for (int i = 0; i < windowOffset.Length; i++) { // Find the next candidate neighbor point IntPoint next = prevPosition + positionOffset[direction]; // Check if it is inside the blob area if (next.X < 0 || next.X >= width || next.Y < 0 || next.Y >= height) { // It isn't. Change direction and continue. direction = (direction + 1) % windowOffset.Length; continue; } // It is inside. Then find the next candidate neighbor pixel byte *neighbor = unchecked (current + windowOffset[direction]); image.CheckBounds(neighbor); // Make sure we are in the image // Check if it is a colored pixel if (*neighbor <= Threshold) { // It isn't. Change direction and continue. direction = (direction + 1) % windowOffset.Length; continue; } // Check if it is a previously found pixel if (neighbor == previous || neighbor == start) { // We found a dead end. found = false; break; } // If we reached until here, we have // found a neighboring black pixel. found = true; break; } if (found) { // Navigate to neighbor pixel previous = current; current = unchecked (current + windowOffset[direction]); // Add to the contour prevPosition += positionOffset[direction]; contour.Add(prevPosition); // Continue counter-clockwise search // from the most promising direction direction = nextDirection[direction]; } } while (found); } return(contour); }
public void Update(UnmanagedImage image, int channel) { int width = image.Width; int height = image.Height; int offset = image.Offset; int pixelSize = System.Drawing.Image.GetPixelFormatSize(image.PixelFormat) / 8; // create integral image long *nSum = this.nSum; long *sSum = this.sSum; long *tSum = this.tSum; size_t nWidth = this.nWidth; size_t tWidth = this.tWidth; if (image.PixelFormat == PixelFormat.Format8bppIndexed && channel != 0) { throw new ArgumentException("Only the first channel is available for 8 bpp images.", "channel"); } byte *srcStart = (byte *)image.ImageData.ToPointer() + channel; // do the job byte *src = srcStart; //var t1 = Task.Factory.StartNew(() => //{ // for each line for (int y = 1; y <= height; y++, src += offset) { size_t yy = nWidth * (y); size_t y1 = nWidth * (y - 1); // for each pixel for (int x = 1; x <= width; x++, src += pixelSize) { image.CheckBounds(src); long p1 = *src; long p2 = p1 * p1; size_t r = yy + (x); size_t a = yy + (x - 1); size_t b = y1 + (x); size_t c = y1 + (x - 1); nSum[r] = p1 + nSum[a] + nSum[b] - nSum[c]; sSum[r] = p2 + sSum[a] + sSum[b] - sSum[c]; } } //}); //var t2 = Task.Factory.StartNew(() => //{ if (this.tSumImage != null) { src = srcStart; // Left-to-right, top-to-bottom pass for (int y = 1; y <= height; y++, src += offset) { size_t yy = tWidth * (y); size_t y1 = tWidth * (y - 1); for (int x = 2; x < width + 2; x++, src += pixelSize) { image.CheckBounds(src); size_t a = y1 + (x - 1); size_t b = yy + (x - 1); size_t c = y1 + (x - 2); size_t r = yy + (x); tSum[r] = *src + tSum[a] + tSum[b] - tSum[c]; } } { size_t yy = tWidth * (height); size_t y1 = tWidth * (height + 1); for (int x = 2; x < width + 2; x++, src += pixelSize) { size_t a = yy + (x - 1); size_t c = yy + (x - 2); size_t b = y1 + (x - 1); size_t r = y1 + (x); tSum[r] = tSum[a] + tSum[b] - tSum[c]; } } // Right-to-left, bottom-to-top pass for (int y = height; y >= 0; y--) { size_t yy = tWidth * (y); size_t y1 = tWidth * (y + 1); for (int x = width + 1; x >= 1; x--) { size_t r = yy + (x); size_t b = y1 + (x - 1); tSum[r] += tSum[b]; } } for (int y = height + 1; y >= 0; y--) { size_t yy = tWidth * (y); for (int x = width + 1; x >= 2; x--) { size_t r = yy + (x); size_t b = yy + (x - 2); tSum[r] -= tSum[b]; } } } //}); //Task.WaitAll(t1, t2); }