/// <summary> /// Returns the brightness of each pixel, storing their quantity of incidence into a 256-item array. /// Each index holds the number of pixels whose brightness was the index. /// Useful for quickly calculating median, mode, etc /// </summary> /// <param name="bitmap">The input bitmap</param> /// <returns>The array of quantities of indicence per brightness level, in pixels</returns> private int[] GetBrightnessSamples(Bitmap bitmap) { // Begin with an array of 0 for each brightness value int[] outputArray = new int[256]; Array.Clear(outputArray, 0, 256); // Scan through all the pixels of the bitmap BitmapData imageData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); unsafe { // For each pixel of 'brightness', increment outputArray[brightness] for (uint *pixel = (uint *)imageData.Scan0.ToPointer(), lastPixel = &pixel[imageData.Height * imageData.Stride / 4]; pixel < lastPixel; ++pixel) { int brightness = ImageMath.Max((byte)(*pixel >> 16), (byte)(*pixel >> 8), (byte)*pixel); ++outputArray[brightness]; } } bitmap.UnlockBits(imageData); return(outputArray); }
/// <summary> /// Calculates the similarity between this point and either 1 or 2 previous points representing a line segment /// This function decides how appropriately this point would fit into a path joining 'point' /// Comparison factors include: thickness of this valley compared to the last, valley colour, relative directions of the valleys /// </summary> /// <param name="point">Point to compare with</param> /// <param name="previousPoint">Point prior to 'point', if applicable</param> /// <returns>A floating-point similarity value between 0 and 1 where 1 is virtually identical</returns> public SimilarityPack CalculateSimilarity(EdgePoint point, EdgePoint previousPoint = null) { SimilarityPack similarity = new SimilarityPack() { Colour = ImageMath.PixelDifference(ColourUnderPoint, point.ColourUnderPoint), Direction = 1.0f, Length = 1.0f, Width = 1.0f, Distance = 1.0f, }; if (previousPoint != null) { Vector2 previousVector = new Vector2(point.X - previousPoint.X, point.Y - previousPoint.Y); Vector2 currentVector = new Vector2(X - point.X, Y - point.Y); if (previousVector.LengthSquared() > 0 && currentVector.LengthSquared() > 0) { // Direction similarity: Use the dot product of the normalised segment vectors similarity.Direction = Vector2.Dot(previousVector / previousVector.Length(), currentVector / currentVector.Length()); // Length similarity: Use the smaller length divided by the bigger length.....? similarity.Length = previousVector.Length() / currentVector.Length(); if (similarity.Length > 1.0f) { similarity.Length = 1.0f / similarity.Length; // swap the division operands lazily } } } if (point.LastPoint != null && LastPoint != null) { // Width similarity: Use the smaller width divided by the bigger width? similarity.Width = Vector2.Distance(new Vector2(X, Y), new Vector2(LastPoint.X, LastPoint.Y)) / Vector2.Distance(new Vector2(point.X, point.Y), new Vector2(point.LastPoint.X, point.LastPoint.Y)); if (similarity.Width > 1.0f) { similarity.Width = 1.0f / similarity.Width; } } if (previousPoint == null) { float distanceToPoint = Vector2.Distance(new Vector2(X, Y), new Vector2(point.X, point.Y)); similarity.Distance = distanceToPoint > 0 ? 1.0f / distanceToPoint : 1.0f; } return(similarity); }
public override void Apply(ref Bitmap bitmap, out Highlighter[] highlighters) { Bitmap outputBitmap = new Bitmap(bitmap); BitmapData imageData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); BitmapData outputImageData = outputBitmap.LockBits(new Rectangle(0, 0, outputBitmap.Width, outputBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); unsafe { // Welcome back to unsafe-land, home of C++ and myself uint *pixels = (uint *)imageData.Scan0.ToPointer(); uint *outputPixels = (uint *)outputImageData.Scan0.ToPointer(); for (int baseY = 0; baseY < bitmap.Height - PixelRadius; ++baseY) { uint *pixelRow = &pixels[baseY * imageData.Stride / 4]; byte *pixelBs = (byte *)pixelRow, pixelGs = (byte *)&pixelRow[1], pixelRs = (byte *)&pixelRow[2]; for (int baseX = 0; baseX < bitmap.Width - PixelRadius; ++baseX) { byte curR = pixelRs[baseX << 2], curG = pixelGs[baseX << 2], curB = pixelBs[baseX << 2]; int rMin = 255, gMin = 255, bMin = 255; int rMax = 0, gMax = 0, bMax = 0; // Search the radius around this pixel and track the difference for (int y = 0; y <= PixelRadius; ++y) { byte *subBs = &pixelBs[(y * imageData.Stride) + (baseX << 2)], subGs = &subBs[1], subRs = &subBs[2]; for (int x = 0; x <= PixelRadius; ++x) { // Compare red // TODO is there a cleaner way to do this? if (subRs[x << 2] < rMin) { rMin = subRs[x << 2]; } if (subGs[x << 2] < gMin) { gMin = subGs[x << 2]; } if (subBs[x << 2] < bMin) { bMin = subBs[x << 2]; } if (subRs[x << 2] > rMax) { rMax = subRs[x << 2]; } if (subGs[x << 2] > gMax) { gMax = subGs[x << 2]; } if (subBs[x << 2] > bMax) { bMax = subBs[x << 2]; } } } // Write the difference to the output outputPixels[baseY * outputImageData.Stride / 4 + baseX] = 0xFF000000 | (uint)((rMax - rMin) << 16 | (gMax - gMin) << 8 | (bMax - bMin)); // or the maximum difference of all colour channels.. byte maxDiff = ImageMath.Max((byte)(rMax - rMin), (byte)(gMax - gMin), (byte)(bMax - bMin)); outputPixels[baseY * outputImageData.Stride / 4 + baseX] = 0xFF000000 | (uint)(maxDiff << 16 | maxDiff << 8 | maxDiff); } } } // Release resources bitmap.UnlockBits(imageData); outputBitmap.UnlockBits(outputImageData); // Copy output data to input data bitmap = new Bitmap(outputBitmap); // TODO there is a better way to do this...right? // Done!No highlighters to return highlighters = null; }