How to get RGB value of a pixel in WinRT

2020-03-31 02:08发布

问题:

I am trying to get the RGB value of each pixel in WinRT app. I can access an array of bytes containing PixelData but I don't know how to work with that so how can I extract the RGB information from the byte array?

var bd = await Windows.Graphics.Imaging.BitmapDecoder.CreateAsync(stream);
var pd = await bd.GetPixelDataAsync();
var tempBuffer = pd.DetachPixelData();
var PixelMatrix = new byte[bd.PixelWidth, bd.PixelHeight];
// how do I get the RGB value for PixelMatrix[0,0]?

回答1:

Since you have a RGB image, tempBuffer[k + 0] is the red channel, tempBuffer[k + 1] is the green channel, and tempBuffer[k + 2] is the blue channel, i.e. tempBuffer is a 1D array. If you were looping over all the pixels, the pseudo code for this would be:

for i = 0 to height - 1
    for j = 0 to width - 1
        k = (i * width + j) * 3
        r, g, b = tempBuffer[k + 0], tempBuffer[k + 1], tempBuffer[k + 2]


回答2:

Since the Marshal class isn't available on WinRT - the most performant way to proceed would be to use a SafeMemoryMappedViewHandle (SafeBuffer).

This method can also handle pixelformats with multi-byte components without needing to use a BinaryReader and reading it component by component (RGBA16 with 16 bits per component). Find out what the pixel format is using the decoder's BitmapPixelFormat property and use the appropriately declared structure.

    // declare more of these appropriately laid
    // out structures for different pixel formats
    struct RGBA16
    {
        public uint R;
        public uint G;
        public uint B;
        public uint A;
    }

    struct RGBA8
    {
        public byte R;
        public byte G;
        public byte B;
        public byte A;
    }

    struct BRGA8
    {
        public byte B;
        public byte G;
        public byte R;
        public byte A;
    }
    ...

    var handle = GCHandle.Alloc(tempBuffer /* the raw byte[] */, GCHandleType.Pinned);
    try
    {
        var ptr = handle.AddrOfPinnedObject();
        var safeBuffer = new SafeMemoryMappedViewHandle(true /* I believe DetachPixelData returns a copy? false otherwise  */)
        safeBuffer.SetHandle(ptr);

        #if STREAM_PROCESSING
            // pixel by pixel
            int offset = 0;
            for (int i = 0; i < width * height; i++)
            {
                var pixel = safeBuffer.Read<RGBA16>(offset);
                offset += RGB24bpp.Size;
            }
        #else
            // Read it all in at once - this makes a copy
            var pixels = new RGBA16[width * height];
            safeBuffer.ReadArray<RGBA16>(0, pixels, 0, width * height);
        #endif
    }
    finally
    {
        safeBuffer.Dispose();
        handle.Free;
    }

Note: This method can also be a replacement for any operation that requires Marshal.PtrToStructure or some such equivalent on WinRT.