using System; using System.Windows; using System.Windows.Media.Imaging; namespace RBG.Helpers { public class ImageProcessing { /// /// Returns a copy of the original bitmap with a specified per-channel colour multiplier /// Each channel expects a rate 0-1 (will handle other values, with unexpected results) /// Defaults to 1.0 for each channel (no modification) /// /// The source bitmap /// The red-channel multiplier /// The green-channel multiplier /// The blue-channel multiplier /// The alpha-channel multiplier /// public static BitmapSource UpdatePixelColours(BitmapSource sourceBitmap, double redRate = 1.0, double greenRate = 1.0, double blueRate = 1.0, double alphaRate = 1.0) { // Create a writeable bitmap from the source bitmap WriteableBitmap wbmp = new(sourceBitmap); // Ensure 32 bpp if (wbmp.Format.BitsPerPixel != 32) return null; // Lock the bitmap so the buffer cannot change wbmp.Lock(); // Enter unsafe code (used over marshalled pointer access so that it is more performant, but has a // higher security risk as this is raw [but still bounded] memory access ( => data may be changed by a third party) // As the buffer is of a known size, and not terminated by a specific symbol, modified data in the buffer is // unlikely to cause a novel vulnerability in the software ( => vulnerability exists in BitmapSource, can be // exploited with any bitmap image being loaded) unsafe { // Retrieve the buffer pointer int* arrayptr = (int*)wbmp.BackBuffer; // Iterate through each pixel for (int i = 0; i < wbmp.PixelWidth * wbmp.PixelHeight; i++) { // Dereference the exact pixel value int pixel = *(arrayptr + i); // decompose the colour channels int a = (pixel >> 24) & 0xFF; int r = (pixel >> 16) & 0xFF; int g = (pixel >> 8) & 0xFF; int b = pixel & 0xFF; // Calc new values & clamp to a max of 0xFF (8 bits per channel) int newB = (int)Math.Min((uint)(b * blueRate), 255); int newG = (int)Math.Min((uint)(g * greenRate), 255); int newR = (int)Math.Min((uint)(r * redRate), 255); int newA = (int)Math.Min((uint)(a * alphaRate), 255); // Set the pixel to the new value *(arrayptr + i) = (newA << 24) | (newR << 16) | (newG << 8) | newB; } } // Invalidate the full current image, so that the new buffer data is fully applied wbmp.AddDirtyRect(new Int32Rect(0, 0, wbmp.PixelWidth, wbmp.PixelHeight)); // Unlock the image, as everything has finished changing wbmp.Unlock(); return wbmp; } /// /// Retrieves a cropped area of the provided bitmap /// /// /// /// /// /// /// /// /// public static BitmapSource RetrieveSubImage(BitmapSource source, int row, int col, int rowSize, int colSize, int colsIncluded = 1, int rowsIncluded = 1) { // Get number of pixels per segment int rowPixelsPerTile = (source.PixelWidth / rowSize) - 1; int colPixelsPerTile = (source.PixelHeight / colSize) - 1; // Create a cropping bound Int32Rect pixelRect = new(colPixelsPerTile * col, rowPixelsPerTile * row, colPixelsPerTile * colsIncluded, rowPixelsPerTile * rowsIncluded); return new CroppedBitmap(source, pixelRect); } } }