// ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // ----------------------------------------------------------------------- namespace Microsoft.Samples.Kinect.Webserver.Sensor { using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using Microsoft.Kinect; using Microsoft.Kinect.Toolkit.Interaction; /// /// Colorizes a depth image according to desired size and color mapping. /// internal class UserViewerColorizer { /// /// Number of bytes per pixel in colorized image. /// private const int BytesPerPixel = 4; /// /// Background color is always transparent. /// private const int BackgroundColor = 0x00000000; /// /// Lookup table mapping player indexes (observable in depth image pixels) to 32-bit /// ARGB color. /// private readonly int[] playerColorLookupTable; /// /// Initializes a new instance of the class. /// /// /// Desired width of colorized image. /// /// /// Desired height of colorized image. /// public UserViewerColorizer(int width, int height) { this.playerColorLookupTable = new int[SharedConstants.MaxUsersTracked + 1]; this.SetResolution(width, height); } /// /// Desired width of colorized image. /// public int Width { get; private set; } /// /// Desired height of colorized image. /// public int Height { get; private set; } /// /// Buffer that holds colorized image. /// public byte[] Buffer { get; private set; } /// /// Updates the desired resolution for user viewer image. /// /// /// Desired width of colorized image. /// /// /// Desired height of colorized image. /// public void SetResolution(int width, int height) { if ((this.Buffer == null) || (this.Width != width) || (this.Height != height)) { this.Width = width; this.Height = height; this.Buffer = new byte[width * height * BytesPerPixel]; } } /// /// Color the user viewer image pixels appropriately given the specified depth image. /// /// /// Depth image from which we will colorize user viewer image. /// /// /// Width of depth image, in pixels. /// /// /// Height of depth image, in pixels. /// public void ColorizeDepthPixels(DepthImagePixel[] depthImagePixels, int depthWidth, int depthHeight) { if (depthImagePixels == null) { throw new ArgumentNullException("depthImagePixels"); } if (depthWidth <= 0) { throw new ArgumentException(@"Width of depth image must be greater than zero", "depthWidth"); } if (depthWidth % this.Width != 0) { throw new ArgumentException( string.Format( CultureInfo.InvariantCulture, "Depth image width '{0}' is not a multiple of the desired user viewer image width '{1}'", depthWidth, this.Width), "depthWidth"); } if (depthHeight <= 0) { throw new ArgumentException(@"Height of depth image must be greater than zero", "depthHeight"); } if (depthHeight % this.Height != 0) { throw new ArgumentException( string.Format( CultureInfo.InvariantCulture, "Depth image height '{0}' is not a multiple of the desired user viewer image height '{1}'", depthHeight, this.Height), "depthHeight"); } int downscaleFactor = depthWidth / this.Width; Debug.Assert(depthHeight / this.Height == downscaleFactor, "Downscale factor in x and y dimensions should be exactly the same."); int pixelDisplacementBetweenRows = depthWidth * downscaleFactor; unsafe { fixed (byte* colorBufferPtr = this.Buffer) { fixed (DepthImagePixel* depthImagePixelPtr = depthImagePixels) { fixed (int* playerColorLookupPtr = this.playerColorLookupTable) { // Write color values using int pointers instead of byte pointers, // since each color pixel is 32-bits wide. int* colorBufferIntPtr = (int*)colorBufferPtr; DepthImagePixel* currentPixelRowPtr = depthImagePixelPtr; for (int row = 0; row < depthHeight; row += downscaleFactor) { DepthImagePixel* currentPixelPtr = currentPixelRowPtr; for (int column = 0; column < depthWidth; column += downscaleFactor) { *colorBufferIntPtr++ = playerColorLookupPtr[currentPixelPtr->PlayerIndex]; currentPixelPtr += downscaleFactor; } currentPixelRowPtr += pixelDisplacementBetweenRows; } } } } } } /// /// Reset the lookup table mapping user indexes (observable in depth image pixels) /// to 32-bit ARGB color to map all indexes to background color. /// public void ResetColorLookupTable() { // Initialize all player indexes to background color for (int entryIndex = 0; entryIndex < this.playerColorLookupTable.Length; ++entryIndex) { this.playerColorLookupTable[entryIndex] = BackgroundColor; } } /// /// Update the lookup table mapping user indexes (observable in depth image pixels) /// to 32-bit ARGB color. /// /// /// User information obtained from an . /// /// /// 32-bit ARGB representation of default color assigned to users. /// /// /// Mapping between user tracking IDs and user states (obtained from /// ). /// /// /// Mapping between user states and 32-bit ARGB color. /// public void UpdateColorLookupTable(UserInfo[] userInfos, int defaultUserColor, IDictionary userStates, IDictionary userColors) { if ((userInfos == null) || (userStates == null) || (userColors == null)) { this.ResetColorLookupTable(); return; } // Reset lookup table to have all player indexes map to default user color for (int i = 1; i < this.playerColorLookupTable.Length; i++) { this.playerColorLookupTable[i] = defaultUserColor; } // Iterate through user tracking Ids to populate color table. for (int i = 0; i < userInfos.Length; ++i) { // Player indexes in depth image are shifted by one in order to be able to // use zero as a marker to mean "pixel does not correspond to any player". int depthPlayerIndex = i + 1; var trackingId = userInfos[i].SkeletonTrackingId; string state; if (userStates.TryGetValue(trackingId, out state)) { int color; if (userColors.TryGetValue(state, out color)) { this.playerColorLookupTable[depthPlayerIndex] = color; } } } } } }