Alvin F
Alvin F

Reputation: 1

Generate int[,] array based on Bitmap

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 :

output image

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

Answers (1)

JonasH
JonasH

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

Related Questions