I need to display .pgm images in my Universal Windows App. XAML Image control does not directly support .pgm images so I need to work around it.
There are many examples on the internet of opening .pgm files in c#, but all of these rely on using the Bitmap object which is not supported in Universal Windows Platform (the System.Drawing and System.Windows.Media libraries cannot be used).
I've got the code that reads the image width and height and reads the pixels in a byte array (containing values 0-255 representing gray shades).
The next step would be to draw the image from the byte[] array using any object that can eventually be passed to the XAML Image.Source (and to do it in a reasonable amount of time).
The best I managed to do was to display this but the actual picture is supposed to look like this (for some reason it shows the image 4x and the colors are wrong).
The code i used:
public int width;
public int height;
public int maxVal; //255
public byte[] pixels;
public async Task<WriteableBitmap> ToWriteableBitmap()
{
WriteableBitmap writeableBitmap = new WriteableBitmap(width, height);
using (Stream stream = writeableBitmap.PixelBuffer.AsStream())
{
await stream.WriteAsync(pixels, 0, pixels.Length);
}
return writeableBitmap;
}
Should it matter, I'm also providing the code I use to read the .pgm file to the PgmImage object, but I'm pretty sure this works fine:
public static async Task<PgmImage> LoadFromFile(string file)
{
FileStream ifs = null;
await Task.Run( () =>
{
Task.Yield();
ifs = new FileStream(file, FileMode.Open, FileAccess.Read);
});
BinaryReader br = new BinaryReader(ifs);
string magic = NextNonCommentLine(br);
//if (magic != "P5")
// throw new Exception("Unknown magic number: " + magic);
string widthHeight = NextNonCommentLine(br);
string[] tokens = widthHeight.Split(' ');
int width = int.Parse(tokens[0]);
int height = int.Parse(tokens[1]);
string sMaxVal = NextNonCommentLine(br);
int maxVal = int.Parse(sMaxVal);
byte[] pixels = new byte[height * width];
for (int i = 0; i < height * width; i++)
{
pixels[i] = br.ReadByte();
}
return new PgmImage(width, height, maxVal, pixels);
}
static string NextAnyLine(BinaryReader br)
{
string s = "";
byte b = 0; // dummy
while (b != 10) // newline
{
b = br.ReadByte();
char c = (char)b;
s += c;
}
return s.Trim();
}
static string NextNonCommentLine(BinaryReader br)
{
string s = NextAnyLine(br);
while (s.StartsWith("#") || s == "")
s = NextAnyLine(br);
return s;
}
(it's a slightly edited version of this: jamesmccaffrey.wordpress.com/2014/10/21/a-pgm-image-viewer-using-c). I should mention that I would prefer a solution that does not depend on any third-party libraries or NuGet packages, but I am desperate and therefore open to any solutions.
The WritableBitmap.PixelBuffer uses a RGBA color space which means every pixel is described with four bytes in the byte array, but the array generated from the PGM image uses only one byte to describe a pixel. By simply expanding the array 4 times (and setting the alpha value for each pixel to the max value of 255) I had managed to get the right display.
public async Task<WriteableBitmap> ToWriteableBitmap()
{
WriteableBitmap writeableBitmap = new WriteableBitmap(width, height);
byte[] expanded = new byte[pixels.Length * 4];
int j = 0;
for (int i = 0; i< pixels.Length; i++)
{
expanded[j++] = pixels[i];
expanded[j++]= pixels[i];
expanded[j++] = pixels[i];
expanded[j++] = 255; //Alpha
}
using (Stream stream = writeableBitmap.PixelBuffer.AsStream())
{
await stream.WriteAsync(expanded, 0, expanded.Length);
}
return writeableBitmap;
}