Reputation: 1
I'm currently writing an ASCOM driver which is used to take picture with a SigmaFP camera.
Everything works fine but I'm currently hitting a wall when I have to convert my DNG to a standard image array for ASCOM (https://ascom-standards.org/Help/Developer/html/P_ASCOM_DriverAccess_Camera_ImageArray.htm)
With libraw I convert my DNG file, convert it to a bitmap and save it as a new jpeg file.
But, when I try to conver this jpeg file to an array (using Bitmap and my custom ReadBitmap function) I get this kind of pictures :
Here is the input image
I don't know what I have missed during those steps
Here is my ReadBitmap function :
public int[,] ReadBitmap(Bitmap img)
{
BitmapData data = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, img.PixelFormat);
IntPtr ptr = data.Scan0;
int bytesCount = Math.Abs(data.Stride) * img.Height;
var result = new int[img.Width, img.Height];
byte[] bytesArray = new byte[bytesCount];
Marshal.Copy(ptr, bytesArray, 0, bytesCount);
img.UnlockBits(data);
var width = img.Width;
var height = img.Height;
Parallel.For(0, width * height, rc =>
{
var b = bytesArray[rc * 3];
var g = bytesArray[rc * 3 + 1];
var r = bytesArray[rc * 3 + 2];
int row = rc / width;
int col = rc - width * row;
//var rowReversed = height - row - 1;
result[col, row] = (int)(0.299 * r + 0.587 * g + 0.114 * b);
}
);
return result;
}
Here is the method used to convert DNG to jpeg (which will be used to get the int array)
IntPtr data = LoadRaw(path);
NativeMethods.libraw_dcraw_process(data);
// extract raw data into allocated memory buffer
var errc = 0;
var ptr = NativeMethods.libraw_dcraw_make_mem_image(data, out errc);
// convert pointer to structure to get image info and raw data
var img = PtrToStructure<libraw_processed_image_t>(ptr);
Console.WriteLine("\nImage type: " + img.type);
Console.WriteLine("Image height: " + img.height);
Console.WriteLine("Image width: " + img.width);
Console.WriteLine("Image colors: " + img.colors);
Console.WriteLine("Image bits: " + img.bits);
Console.WriteLine("Data size: " + img.data_size);
Console.WriteLine("Checksum: " + img.height * img.width * img.colors * (img.bits / 8));
// rqeuired step before accessing the "data" array
Array.Resize(ref img.data, (int)img.data_size);
var adr = ptr + OffsetOf(typeof(libraw_processed_image_t), "data").ToInt32();
Copy(adr, img.data, 0, (int)img.data_size);
// calculate padding for lines and add padding
var num = img.width % 4;
var padding = new byte[num];
var stride = img.width * img.colors * (img.bits / 8);
var line = new byte[stride];
var tmp = new List<byte>();
for (var i = 0; i < img.height; i++)
{
Buffer.BlockCopy(img.data, stride * i, line, 0, stride);
tmp.AddRange(line);
tmp.AddRange(padding);
}
// release memory allocated by [libraw_dcraw_make_mem_image]
NativeMethods.libraw_dcraw_clear_mem(ptr);
// create/save bitmap from mem image/array
var bmp = new Bitmap(img.width, img.height, PixelFormat.Format24bppRgb);
var bmd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
Copy(tmp.ToArray(), 0, bmd.Scan0, (int)img.data_size);
NativeMethods.libraw_close(data);
var outJPEG = path.Replace(Path.GetExtension(path), ".jpg");
Console.WriteLine("Saving image to: " + outJPEG);
bmp.Save(outJPEG, ImageFormat.Jpeg);
CurrentFilePath = outJPEG;
Upvotes: 0
Views: 540
Reputation: 36629
Bitmaps use a stride to describe the number of bytes of a row, this is to ensure all rows are aligned properly, i.e. stride >= width * bitsPerPixel
. Additionally you use a parallel loop over all pixels and this is to fine grained parallelism, you should go parallel over rows, and process each row sequentially.
So your code should look something like
var result = new int[height, width];
Parallel.For(0, height, y =>
{
var rowStart = y * data.Stride;
for(var x = 0 ; x < width; x++){
var i = rowStart + x*3;
var b = bytesArray[i];
var g = bytesArray[i + 1];
var r = bytesArray[i + 2];
result[y, x] = (int) (0.299 * r + 0.587 * g + 0.114 * b);
}
}
);
You might also consider using unsafe code to access the pixelvalues from the datapointer directly, instead of using a large temporary buffer that is allocated on the large object heap and immediately thrown away.
You should probably also do a check of the pixel format to ensure it is bgr24, since your code will fail otherwise.
Node that multidimensional arrays may not be ideal for images. Images are usually indexed like [x,y]
and converted to a linear index like var i = y * width + x
. For multidimensional arrays the storage order is inverse, this might require indexing like [y,x]
, or doing unnecessary transpositions of images instead of a straight memory-copy whenever converting data to some other format. So I usually prefer to write my own 2D array since this usually make interoperability much easier:
public class Array2D<T> : IReadOnlyList<T>
{
public int Width { get; }
public int Height { get; }
public T[] Data { get; }
public Array2D(int width, int height)
{
Width = width;
Height = height;
Data = new T[width * height];
}
/// <summary>
/// Index operator
/// Note that this does not check if x is valid, it may wrap around to the next row
/// </summary>
public T this[int x, int y]
{
get => Data[y * Width + x];
set => Data[y * Width + x] = value;
}
}
Upvotes: 0