I am following this tutorial on image hashing.
So far I have achieved the following:
Code:
private async Task<ImageSource> ProcessImageAsync(StorageFile ImageFile)
{
if (ImageFile == null)
throw new ArgumentNullException("ImageFile cannot be null.");
//The new size of processed image.
const int side = 300; //300 is for clarity. Should be 8 or 16 px.
//Initialize bitmap transformations to be applied to the image.
var transform = new BitmapTransform() { ScaledWidth = side, ScaledHeight = side, InterpolationMode = BitmapInterpolationMode.Cubic };
//Get image pixels.
var stream = await ImageFile.OpenStreamForReadAsync();
var decoder = await BitmapDecoder.CreateAsync(stream.AsRandomAccessStream());
var pixelData = await decoder.GetPixelDataAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, transform, ExifOrientationMode.RespectExifOrientation, ColorManagementMode.ColorManageToSRgb);
var pixels = pixelData.DetachPixelData();
//Initialize writable bitmap.
var wBitmap = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);
await wBitmap.SetSourceAsync(stream.AsRandomAccessStream());
//Make it gray
var grayBitmapBuffer = await ConvertToGrayAsync(wBitmap);
//Create a software bitmap from the writable bitmap's pixel buffer.
var sBitmap = SoftwareBitmap.CreateCopyFromBuffer(wBitmap.PixelBuffer, BitmapPixelFormat.Bgra8, side, side, BitmapAlphaMode.Premultiplied);
//Create software bitmap source.
var sBitmapSource = new SoftwareBitmapSource();
await sBitmapSource.SetBitmapAsync(sBitmap);
return sBitmapSource;
}
private async Task<IBuffer> ConvertToGrayAsync(WriteableBitmap srcBitmap)
{
// Get the source bitmap pixels
byte[] srcPixels = new byte[4 * srcBitmap.PixelWidth * srcBitmap.PixelHeight];
using (Stream pixelStream = srcBitmap.PixelBuffer.AsStream())
{
await pixelStream.ReadAsync(srcPixels, 0, srcPixels.Length);
}
// Create a destination bitmap and pixels array
WriteableBitmap dstBitmap = new WriteableBitmap(srcBitmap.PixelWidth, srcBitmap.PixelHeight);
byte[] dstPixels = new byte[4 * dstBitmap.PixelWidth * dstBitmap.PixelHeight];
for (int i = 0; i < srcPixels.Length; i += 4)
{
double b = (double)srcPixels[i] / 255.0;
double g = (double)srcPixels[i + 1] / 255.0;
double r = (double)srcPixels[i + 2] / 255.0;
byte a = srcPixels[i + 3];
double e = (0.21 * r + 0.71 * g + 0.07 * b) * 255;
byte f = Convert.ToByte(e);
dstPixels[i] = f;
dstPixels[i + 1] = f;
dstPixels[i + 2] = f;
dstPixels[i + 3] = a;
}
// Move the pixels into the destination bitmap
using (Stream pixelStream = dstBitmap.PixelBuffer.AsStream())
{
await pixelStream.WriteAsync(dstPixels, 0, dstPixels.Length);
}
dstBitmap.Invalidate();
// Display the new bitmap
return dstBitmap.PixelBuffer;
}
This is the original image which user selects:
And this is the output produced by my code:
It is getting scaled and grayed all-right. But I don't understand why is the image repeating horizontally??
Here is the XAML code that renders the image:
<Image x:Name="myImage" Stretch="None" />
And the C# code that sets the image source:
StorageFile userPickedFile = ...; //code ignored.
myImage.Source = await ProcessImageAsync(userPickedFile);
What am I doing wrong? Something looks fishy about the processed image.. I am lost from here... How should I move ahead with Hashing? Any help?
Your ConvertToGrayAsync
method should simply return the converted WriteableBitmap. The pixel conversion code inside the loop could also be simplified, and the method does not need to operate on a source and destination buffer. It could as well manipulate the pixel values in place.
private async Task<WriteableBitmap> ConvertToGrayAsync(WriteableBitmap srcBitmap)
{
var pixels = srcBitmap.PixelBuffer.ToArray();
for (int i = 0; i < pixels.Length; i += 4)
{
var b = pixels[i];
var g = pixels[i + 1];
var r = pixels[i + 2];
var f = (byte)(0.21 * r + 0.71 * g + 0.07 * b);
pixels[i] = f;
pixels[i + 1] = f;
pixels[i + 2] = f;
}
var dstBitmap = new WriteableBitmap(srcBitmap.PixelWidth, srcBitmap.PixelHeight);
using (var pixelStream = dstBitmap.PixelBuffer.AsStream())
{
await pixelStream.WriteAsync(pixels, 0, pixels.Length);
}
return dstBitmap;
}
The following method loads a WriteableBitmap with a predefined size:
private static async Task<WriteableBitmap> LoadWriteableBitmapAsync(
StorageFile file, int width, int height)
{
using (var fileStream = await file.OpenReadAsync())
using (var memoryStream = new InMemoryRandomAccessStream())
{
var decoder = await BitmapDecoder.CreateAsync(fileStream);
var transform = new BitmapTransform
{
ScaledWidth = (uint)width,
ScaledHeight = (uint)height,
InterpolationMode = BitmapInterpolationMode.Cubic
};
var pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.ColorManageToSRgb);
var pixels = pixelData.DetachPixelData();
var bitmap = new WriteableBitmap(width, height);
using (var pixelStream = bitmap.PixelBuffer.AsStream())
{
await pixelStream.WriteAsync(pixels, 0, pixels.Length);
}
return bitmap;
}
}
Your ProcessImageAsync
method would now look like this:
private async Task<WriteableBitmap> ProcessImageAsync(
StorageFile file, int width, int height)
{
return await ConvertToGrayAsync(
await LoadWriteableBitmapAsync(file, width, height));
}