0

I want to make a lazy loading of image pixels to the 3 dimensional array of integers. For example in simple way it looks like this:

   for i=0 to Width 
     for j=0 to Height
       let point=image.GetPixel(i,j)
       pixels.[0,i,j] <- point.R
       pixels.[1,i,j] <- point.G
       pixels.[2,i,j] <- point.B

How it can be made in lazy way?

Chris Smith
  • 16,814
  • 12
  • 55
  • 78
Nozim
  • 590
  • 3
  • 14
  • 33

3 Answers3

5

What would be slow is the call to GetPixel. If you want to call it only as needed, you could use something like this:

open System.Drawing

let lazyPixels (image:Bitmap) =
    let Width = image.Width
    let Height = image.Height
    let pixels : Lazy<byte>[,,] = Array3D.zeroCreate 3 Width Height
    for i = 0 to Width-1 do
        for j = 0 to Height-1 do
            let point = lazy image.GetPixel(i,j)
            pixels.[0,i,j] <- lazy point.Value.R
            pixels.[1,i,j] <- lazy point.Value.G
            pixels.[2,i,j] <- lazy point.Value.B
    pixels

GetPixel will be called at most once for every pixel, and then reused for the other components.

Another way of approaching this problem would be to do a bulk-load of the entire image. This will be a lot quicker than calling GetPixel over and over again.

open System.Drawing
open System.Drawing.Imaging

let pixels (image:Bitmap) =
    let Width = image.Width
    let Height = image.Height
    let rect = new Rectangle(0,0,Width,Height)

    // Lock the image for access
    let data = image.LockBits(rect, ImageLockMode.ReadOnly, image.PixelFormat)

    // Copy the data
    let ptr = data.Scan0
    let stride = data.Stride
    let bytes = stride * data.Height
    let values : byte[] = Array.zeroCreate bytes
    System.Runtime.InteropServices.Marshal.Copy(ptr,values,0,bytes)

    // Unlock the image
    image.UnlockBits(data)

    let pixelSize = 4 // <-- calculate this from the PixelFormat

    // Create and return a 3D-array with the copied data
    Array3D.init 3 Width Height (fun i x y ->
        values.[stride * y + x * pixelSize + i])

(adopted from the C# sample on Bitmap.LockBits)

Markus Jarderot
  • 79,575
  • 18
  • 131
  • 135
2

What do you mean by lazy?

An array is not a lazy data type, which means that if you want to use arrays, you need to load all pixels during the initialization. If we were using single-dimensional array, an alternative would be to use seq<_> which is lazy (but you can access elements only sequentially). There is nothing like seq<_> for multi-dimensional arrays, so you'll need to use something else.

Probably the closest option would be to use three-dimensional array of lazy values (Lazy<int>[,,]). This is an array of delayed thunks that access pixels and are evaluated only when you actually read the value at the location. You could initialize it like this:

for i=0 to Width 
  for j=0 to Height 
    let point = lazy image.GetPixel(i,j) 
    pixels.[0,i,j] <- lazy point.Value.R 
    pixels.[1,i,j] <- lazy point.Value.G 
    pixels.[2,i,j] <- lazy point.Value.B

The snippet creates a lazy value that reads the pixel (point) and then three lazy values to get the individual color components. When accessing color component, the point value is evaluated (by accessing Value).

The only difference in the rest of your code is that you'll need to call Value (e.g. pixels.[0,10,10].Value to get the actual color component of the pixel.

You could define more complex data structures (such as your own type that supports indexing and is lazy), but I think that array of lazy values should be a good starting point.

Tomas Petricek
  • 225,798
  • 19
  • 345
  • 516
2

As mentioned already by other comments that you can use the lazy pixel loading in the 3D array but that would just make the GetPixel operation lazy and not the memory allocation of the 3D array as the array is allocated already when you call create method of Array3D.

If you want to make the memory allocation as well as GetPixel lazy then you can use sequences as shown by below code:

let getPixels (bmp:Bitmap) =
  seq {
        for i = 0 to bmp.Height-1 do
            yield seq {
                            for j = 0 to bmp.Width-1 do
                                let pixel = bmp.GetPixel(j,i)
                                yield (pixel.R,pixel.G,pixel.B)
                       }
  }
Ankur
  • 32,475
  • 2
  • 42
  • 69