0

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;
Alvin F
  • 1
  • 1
  • 1
    FYI: You Parallel.For each individual pixel. Considering in the little (and therefore quick) work that is done for a pixel, i expect the overhead of scheduling every single little pixel work for running on some background thread costing a multiple of time than the actual little processing that is done on a pixel. In other words: I expect your per-pixel Parallel.For to conclude in a significantly longer time than it would needed if you were to process your pixels sequentially in a simple for loop. –  Sep 01 '22 at 12:33
  • Parallel.For only makes sense if the work done per Parallel.For iteration takes so much time that the overhead of thread scheduling becomes insignificant in comparison. Thus, if you really want to see perf improvement when using Parallel.For, make your Parallel.For iterations do more work, like for example an iteration not processing only a single pixel, but processing a whole line. –  Sep 01 '22 at 12:36
  • Also, why are you doing `rc*3` to calculate the position of a pixel in the byte array? That's not going to work if the bitmap is not using 24-bit pixels. (Which might or might not explain your problem...) –  Sep 01 '22 at 12:38
  • Thanks for your answer, in fact my bitmap pixel format is 24bppRgb so it should be good in this case – Alvin F Sep 01 '22 at 12:41
  • Could you perhaps include the source image in your Q. This might perhaps reveal something about what is going... –  Sep 01 '22 at 12:45
  • Uh, instead of the image i get `That puush could not be found.`... –  Sep 01 '22 at 12:50
  • Right, my bad ! https://easyupload.io/y3vasc – Alvin F Sep 01 '22 at 12:56
  • Alright. Got it now. Lets see what's going on... –  Sep 01 '22 at 12:57

1 Answers1

0

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;
    }
}
JonasH
  • 28,608
  • 2
  • 10
  • 23
  • I'm currently trying this solution but still having some issues with weird lines (probably due to stride as you said) https://easyupload.io/8a0fs7 – Alvin F Sep 01 '22 at 13:27
  • @AlvinF If you are still having issues I would suggest creating a tiny image, say 2x2, with some unique colors for each pixel, and write a unit test to check that this is converted correctly. Also note that multidimensional arrays store images column-major, so you either need to declare your array as `int[height, width]` or do the writing like `result[x, y]`, the later may cause images to be rotated 90 degrees unless other components agree on your format. – JonasH Sep 01 '22 at 13:39