File: System\Windows\Media\ColorContext.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#pragma warning disable 1634, 1691 // Allow suppression of certain presharp messages
 
using MS.Internal;
using MS.Win32;
using System.IO;
using System.Runtime.InteropServices;
using System.Resources;
using System.Reflection;
using System.Globalization;
using System.Net;
using System.Text;
 
using UnsafeNativeMethodsMilCoreApi = MS.Win32.PresentationCore.UnsafeNativeMethods;
using IWICCC = MS.Win32.PresentationCore.UnsafeNativeMethods.IWICColorContext;
 
namespace System.Windows.Media
{
    /// <summary>
    /// Color Context
    /// </summary>
    public class ColorContext
    {
        #region Constructors
 
        /// <summary>
        /// Create a ColorContext from an unmanaged color context
        /// </summary>
        private ColorContext(SafeMILHandle colorContextHandle)
        {
            _colorContextHandle = colorContextHandle;
 
            //
            // For 3.* backwards compat, we aren't going to HRESULT.Check() anywhere because
            // that could introduce new exceptions. If anything fails, _colorContextHelper
            // will be invalid and we'll emulate the old failure behavior later in
            // OpenProfileStream()
            //
            
            IWICCC.WICColorContextType type;
            if (HRESULT.Failed(IWICCC.GetType(_colorContextHandle, out type)))
            {
                return;
            }
 
            switch (type)
            {
                case IWICCC.WICColorContextType.WICColorContextProfile:
                    uint cbProfileActual;
                    int hr = IWICCC.GetProfileBytes(_colorContextHandle, 0, null, out cbProfileActual);
                    if (HRESULT.Succeeded(hr) && cbProfileActual != 0)
                    {
                        byte[] profileData = new byte[cbProfileActual];
                        if (HRESULT.Failed(IWICCC.GetProfileBytes(
                            _colorContextHandle, cbProfileActual, profileData, out cbProfileActual))
                            )
                        {
                            return;
                        }
                        
                        FromRawBytes(profileData, (int)cbProfileActual, /* dontThrowException = */ true);
                    }
 
                    break;
 
                case IWICCC.WICColorContextType.WICColorContextExifColorSpace:
                    uint colorSpace;
                    if (HRESULT.Failed(IWICCC.GetExifColorSpace(_colorContextHandle, out colorSpace)))
                    {
                        return;
                    }
 
                    //
                    // From MSDN:
                    //     "1" is sRGB. We will use our built-in sRGB profile.
                    //     "2" is Adobe RGB. WIC says we should never see this because they are nonstandard and instead a 
                    //     real profile will be returned.
                    //     "3-65534" is unused.
                    //
                    // From the Exif spec:
                    //     B. Tag Relating to Color Space
                    //     ColorSpace
                    //
                    //     The color space information tag (ColorSpace) is always recorded as the color space specifier.
                    //     Normally sRGB (=1) is used to define the color space based on the PC monitor conditions and environment. If a
                    //     color space other than sRGB is used, Uncalibrated (=FFFF.H) is set. Image data recorded as Uncalibrated can be
                    //     treated as sRGB when it is converted to Flashpix. On sRGB see Annex E.
                    //     Tag = 40961 (A001.H)
                    //     Type = SHORT
                    //     Count = 1
                    //     1 = sRGB
                    //     FFFF.H = Uncalibrated
                    //
                    // So for 65535 we will return sRGB since it is acceptible rather than having an invalid ColorContext. The Exif 
                    // CC should always be the second one so the real one is given priority. Alternatively, we could ignore the
                    // uncalibrated CC but that would be a breaking change with 3.* (returning 1 instead of 2).
                    //
                    // If anything other than 1 or 65535 happens, _colorContextHelper will remain invalid and we will emulate
                    // the old crash behavior in OpenProfileStream().
                    //
                    
                    if (colorSpace == 1 || colorSpace == 65535)
                    {
                        ResourceManager resourceManager = new ResourceManager(
                            _colorProfileResources, Assembly.GetAssembly(typeof(ColorContext))
                            );
                        byte[] sRGBProfile = (byte[])resourceManager.GetObject(_sRGBProfileName);
                        // The existing ColorContext has already been initialized as Exif so we can't initialize it again
                        // and instead must create a new one.
                        using (FactoryMaker factoryMaker = new FactoryMaker())
                        {
                            _colorContextHandle.Dispose();
                            _colorContextHandle = null;
                            
                            if (HRESULT.Failed(UnsafeNativeMethodsMilCoreApi.WICCodec.CreateColorContext(
                                factoryMaker.ImagingFactoryPtr, out _colorContextHandle))
                                )
                            {
                                return;
                            }
 
                            if (HRESULT.Failed(IWICCC.InitializeFromMemory(
                                _colorContextHandle, sRGBProfile, (uint)sRGBProfile.Length))
                                )
                            {
                                return;
                            }
                        }
 
                        // Finally, fill in _colorContextHelper
                        FromRawBytes(sRGBProfile, sRGBProfile.Length, /* dontThrowException = */ true);
                    }
                    else if (Invariant.Strict)
                    {
                        Invariant.Assert(false, String.Format(CultureInfo.InvariantCulture, "IWICColorContext::GetExifColorSpace returned {0}.", colorSpace));
                    }
 
                    break;
 
                default:
                    if (Invariant.Strict)
                    {
                        Invariant.Assert(false, "IWICColorContext::GetType() returned WICColorContextUninitialized.");
                    }
 
                    break;
            }
 
            Debug.Assert(_profileUri is null);
        }
 
        /// <summary>
        /// Creates a new ColorContext object from a .icm or .icc color profile specified by profileUri.
        /// </summary>
        /// <param name="profileUri">Specifies the URI of a color profile used by the newly created ColorContext.</param>
        public ColorContext(Uri profileUri)
        {
            Initialize(profileUri, /* isStandardProfileUriNotFromUser = */ false);
        }
 
        /// <summary>
        /// Given a pixel format, this function will return the closest standard color space (sRGB, scRGB, etc)
        /// </summary>
        public ColorContext(PixelFormat pixelFormat)
        {
            switch (pixelFormat.Format)
            {
                case PixelFormatEnum.Default:
                case PixelFormatEnum.Indexed1:
                case PixelFormatEnum.Indexed2:
                case PixelFormatEnum.Indexed4:
                case PixelFormatEnum.Indexed8:
                case PixelFormatEnum.Bgr555:
                case PixelFormatEnum.Bgr565:
                case PixelFormatEnum.Bgr24:
                case PixelFormatEnum.Rgb24:
                case PixelFormatEnum.Bgr32:
                case PixelFormatEnum.Bgra32:
                case PixelFormatEnum.Pbgra32:
                default:
                    Initialize(GetStandardColorSpaceProfile(), /* isStandardProfileUriNotFromUser = */ true);
                    break;
 
                case PixelFormatEnum.Rgba64:
                case PixelFormatEnum.Prgba64:
                case PixelFormatEnum.Rgba128Float:
                case PixelFormatEnum.Prgba128Float:
                case PixelFormatEnum.BlackWhite:
                case PixelFormatEnum.Gray2:
                case PixelFormatEnum.Gray4:
                case PixelFormatEnum.Gray8:
                case PixelFormatEnum.Gray32Float:
                case PixelFormatEnum.Cmyk32:
                    throw new NotSupportedException(); // standard scRGB profile does not exist yet
            }
        }
 
        #endregion
 
        #region Public Methods
 
        /// <summary>
        /// Returns a memory stream to the color profile bits
        /// </summary>
        public Stream OpenProfileStream()
        {
            //
            // 3.* backwards compat for a "bad" ColorContext. Now the helper is a
            // struct so when it's invalid we'll pretend it's a reference type. This
            // should only happen if the color profile is corrupt (see early exits in
            // ColorContext(SafeMILHandle) and FromRawBytes()).
            //
            if (_colorContextHelper.IsInvalid)
            {
                throw new NullReferenceException();
            }
            
            uint profileSize = 0;
            _colorContextHelper.GetColorProfileFromHandle(null, ref profileSize);
            
            byte[] profile = new byte[profileSize];
            _colorContextHelper.GetColorProfileFromHandle(profile, ref profileSize);
 
            return new MemoryStream(profile);
        }
 
        #endregion Public Methods
 
        #region Public Properties
 
        /// <summary>
        /// ProfileUri
        /// </summary>
        public Uri ProfileUri
        {
            get
            {
                Uri uri = _profileUri;
 
                //
                // If the user didn't give us the uri value, then the uri has
                // to be a file path because we got it from GetStandardColorSpaceProfile
                //
                if (_isProfileUriNotFromUser)
                {
                    Invariant.Assert(uri.IsFile);
                }
 
                return uri;
            }
        }
 
        #endregion Public Properties
 
        #region Internal Properties
 
        /// <summary>
        /// ProfileHandle
        /// </summary>
        internal SafeProfileHandle ProfileHandle
        {
            get
            {
                return _colorContextHelper.ProfileHandle;
            }
        }
 
        /// <summary>
        /// ColorContextHandleHandle
        /// </summary>
        internal SafeMILHandle ColorContextHandle
        {
            get
            {
                return _colorContextHandle;
            }
        }
 
 
        /// <summary>
        /// NumChannels
        /// </summary>
        internal int NumChannels
        {
            get
            {
                if (_colorContextHelper.IsInvalid) // sRGB or scRGB
                    return 3;
 
                return _numChannels;
            }
        }
 
        /// <summary>
        /// ColorType
        /// </summary>
        internal UInt32 ColorType
        {
            get
            {
                return (UInt32)_colorTypeFromChannels[NumChannels];
            }
        }
 
        /// <summary>
        /// ColorSpaceFamily
        /// </summary>
        internal StandardColorSpace ColorSpaceFamily
        {
            get
            {
                if (_colorContextHelper.IsInvalid) // sRGB or scRGB
                {
                    return StandardColorSpace.Srgb;
                }
                else
                {
                    return _colorSpaceFamily;
                }
            }
        }
 
        /// <summary>
        /// Returns false if the ColorContext hasn't been properly initialized due to a bad color profile.
        /// If this is false, use of this ColorContext will lead to exceptions.
        /// </summary>
        internal bool IsValid
        {
            get
            {
                return !_colorContextHelper.IsInvalid;
            }
        }
 
        internal delegate int GetColorContextsDelegate(ref uint numContexts, IntPtr[] colorContextPtrs);
 
        /// <summary>
        /// Helper method that will retrieve ColorContexts from an unmanaged object (e.g. BitmapDecoder or BitmapFrameDecode)
        /// </summary>
        internal static IList<ColorContext> GetColorContextsHelper(GetColorContextsDelegate getColorContexts)
        {
            uint numContexts = 0;
            List<ColorContext> colorContextsList = null;
 
            int hr = getColorContexts(ref numContexts, null);
            if (hr != (int)WinCodecErrors.WINCODEC_ERR_UNSUPPORTEDOPERATION)
            {
                HRESULT.Check(hr);
            }
            
            if (numContexts > 0)
            {
                // GetColorContexts does not create new IWICColorContexts. Instead, it initializes existing
                // ones so we must create them beforehand.
                SafeMILHandle[] colorContextHandles = new SafeMILHandle[numContexts];
                
                using (FactoryMaker factoryMaker = new FactoryMaker())
                {
                    for (uint i = 0; i < numContexts; ++i)
                    {
                        HRESULT.Check(UnsafeNativeMethodsMilCoreApi.WICCodec.CreateColorContext(factoryMaker.ImagingFactoryPtr, out colorContextHandles[i]));
                    }
                }
 
                // The Marshal is unable to handle SafeMILHandle[] so we will convert it to an IntPtr[] ourselves.
                {
                    IntPtr[] colorContextPtrs = new IntPtr[numContexts];
                    
                    for (uint i = 0; i < numContexts; ++i)
                    {
                        colorContextPtrs[i] = colorContextHandles[i].DangerousGetHandle();
                    }
 
                    HRESULT.Check(getColorContexts(ref numContexts, colorContextPtrs));
                }
 
                colorContextsList = new List<ColorContext>((int)numContexts);
                for (uint i = 0; i < numContexts; ++i)
                {
                    colorContextsList.Add(new ColorContext(colorContextHandles[i]));
                }
            }
 
            return colorContextsList;
        }
 
        #endregion
 
 
        //------------------------------------------------------
        //
        //  Equality Methods/Properties
        //
        //------------------------------------------------------
 
        #region Equality methods and Properties
 
        /// <summary>
        /// Equals method
        /// </summary>
        override public bool Equals(object obj)
        {
            ColorContext context = obj as ColorContext;
 
            return (context == this);
        }
 
        /// <summary>
        /// GetHashCode
        /// </summary>
        override public int GetHashCode()
        {
            // phDateTime_2 contains the minute and second that the profile was created. Obviously this 
            // is not a great hash, but the compiler forces us to implement this due to us implementing
            // operator==. Plus, we don't see hashing ColorContexts as an important scenario. This
            // is good enough.
            return (int)_profileHeader.phDateTime_2;
        }
 
        /// <summary>
        /// Operator==
        /// </summary>
        public static bool operator==(ColorContext context1, ColorContext context2)
        {
            object obj1 = context1;
            object obj2 = context2;
 
            if (obj1 == null && obj2 == null)
            {
                return true;
            }
            else if (obj1 != null && obj2 != null)
            {
                #pragma warning disable 6506
                return (
                    (context1._profileHeader.phSize == context2._profileHeader.phSize) &&
                    (context1._profileHeader.phCMMType == context2._profileHeader.phCMMType) &&
                    (context1._profileHeader.phVersion == context2._profileHeader.phVersion) &&
                    (context1._profileHeader.phClass == context2._profileHeader.phClass) &&
                    (context1._profileHeader.phDataColorSpace == context2._profileHeader.phDataColorSpace) &&
                    (context1._profileHeader.phConnectionSpace == context2._profileHeader.phConnectionSpace) &&
                    (context1._profileHeader.phDateTime_0 == context2._profileHeader.phDateTime_0) &&
                    (context1._profileHeader.phDateTime_1 == context2._profileHeader.phDateTime_1) &&
                    (context1._profileHeader.phDateTime_2 == context2._profileHeader.phDateTime_2) &&
                    (context1._profileHeader.phSignature == context2._profileHeader.phSignature) &&
                    (context1._profileHeader.phPlatform == context2._profileHeader.phPlatform) &&
                    (context1._profileHeader.phProfileFlags == context2._profileHeader.phProfileFlags) &&
                    (context1._profileHeader.phManufacturer == context2._profileHeader.phManufacturer) &&
                    (context1._profileHeader.phModel == context2._profileHeader.phModel) &&
                    (context1._profileHeader.phAttributes_0 == context2._profileHeader.phAttributes_0) &&
                    (context1._profileHeader.phAttributes_1 == context2._profileHeader.phAttributes_1) &&
                    (context1._profileHeader.phRenderingIntent == context2._profileHeader.phRenderingIntent) &&
                    (context1._profileHeader.phIlluminant_0 == context2._profileHeader.phIlluminant_0) &&
                    (context1._profileHeader.phIlluminant_1 == context2._profileHeader.phIlluminant_1) &&
                    (context1._profileHeader.phIlluminant_2 == context2._profileHeader.phIlluminant_2) &&
                    (context1._profileHeader.phCreator == context2._profileHeader.phCreator)
                    );
                #pragma warning restore 6506
            }
            else
            {
                return false;
            }
        }
 
        /// <summary>
        /// Operator!=
        /// </summary>
        public static bool operator!=(ColorContext context1, ColorContext context2)
        {
            return !(context1 == context2);
        }
 
        #endregion
 
        #region Private Methods
 
        /// <summary>
        /// Loads color profile given by profileUri
        /// </summary>
        private void Initialize(Uri profileUri, bool isStandardProfileUriNotFromUser)
        {
            bool tryProfileFromResource = false;
 
            ArgumentNullException.ThrowIfNull(profileUri);
 
            if (!profileUri.IsAbsoluteUri)
            {
                throw new ArgumentException(SR.UriNotAbsolute, "profileUri");
            }
 
            _profileUri = profileUri;
            _isProfileUriNotFromUser = isStandardProfileUriNotFromUser;
 
            Stream profileStream = null;
 
            try
            {
                profileStream = WpfWebRequestHelper.CreateRequestAndGetResponseStream(profileUri);
            }
            catch (WebException)
            {
                //
                // If we couldn't load the system's default color profile (e.g. in partial trust), load a color profile from
                // a resource so the image shows up at least. If the user specified a color profile and we weren't 
                // able to load it, we'll fail to avoid letting the user use this resource fallback as a way to discover 
                // files on disk.
                //
                if (isStandardProfileUriNotFromUser)
                {
                    tryProfileFromResource = true;
                }
            }
 
            if (profileStream == null)
            {
                if (tryProfileFromResource)
                {
                    ResourceManager resourceManager = new ResourceManager(_colorProfileResources, Assembly.GetAssembly(typeof(ColorContext)));
                    byte[] sRGBProfile = (byte[])resourceManager.GetObject(_sRGBProfileName);
 
                    profileStream = new MemoryStream(sRGBProfile);
                }
                else
                {
                    //
                    // SECURITY WARNING: This exception includes the profile URI which may contain sensitive information. However, as of right now,
                    // this is safe because it can only happen when the URI is given to us by the user.
                    //
                    Invariant.Assert(!isStandardProfileUriNotFromUser);
                    throw new FileNotFoundException(SR.Format(SR.FileNotFoundExceptionWithFileName, profileUri.AbsolutePath), profileUri.AbsolutePath);
                }
            }
 
            FromStream(profileStream, profileUri.AbsolutePath);
        }
 
        /// <summary>
        /// Obtains the system color profile path
        /// </summary>
        private static Uri GetStandardColorSpaceProfile()
        {
            const int SIZE = NativeMethods.MAX_PATH;
            
            uint dwProfileID = (uint)NativeMethods.ColorSpace.SPACE_sRGB;
            uint bufferSize = SIZE;
            StringBuilder buffer = new StringBuilder(SIZE);
 
            HRESULT.Check(UnsafeNativeMethodsMilCoreApi.Mscms.GetStandardColorSpaceProfile(IntPtr.Zero, dwProfileID, buffer, out bufferSize));
 
            Uri profilePath;
            string profilePathString = buffer.ToString();
 
            if (!Uri.TryCreate(profilePathString, UriKind.Absolute, out profilePath))
            {
                //
                // GetStandardColorSpaceProfile() returns whatever was given to SetStandardColorSpaceProfile().
                // If it were set to a relative path by the user, we should throw an exception to avoid any possible
                // security issues. However, the Vista control panel uses the same API and sometimes likes to set
                // relative paths. Since we can't tell the difference and we want people to be able to change
                // their color profile from the control panel, we'll tack on the system directory.
                //
 
                // bufferSize was modified by GetStandardColorSpaceProfile so set it again
                bufferSize = SIZE;
 
                HRESULT.Check(UnsafeNativeMethodsMilCoreApi.Mscms.GetColorDirectory(IntPtr.Zero, buffer, out bufferSize));
 
                profilePath = new Uri(Path.Combine(buffer.ToString(), profilePathString));
            }
 
            return profilePath;
        }
 
        private void FromStream(Stream stm, string filename)
        {
            Debug.Assert(stm != null);
 
            int bufferSize = _bufferSizeIncrement;
 
            if (stm.CanSeek)
            {
                bufferSize = (int)stm.Length + 1; // If this stream is seekable (most cases), we will only have one buffer alloc and read below
                                                // otherwise, we will incrementally grow the buffer and read until end of profile.
                                                // profiles are typcially small, so usually one allocation will suffice
            }
 
            byte[] rawBytes = new byte[bufferSize];
            int numBytesRead = 0;
            while (bufferSize < _maximumColorContextLength)
            {
                numBytesRead += stm.Read(rawBytes, numBytesRead, bufferSize - numBytesRead);
 
                if (numBytesRead < bufferSize)
                {
                    FromRawBytes(rawBytes, numBytesRead, /* dontThrowException = */ false);
 
                    using (FactoryMaker factoryMaker = new FactoryMaker())
                    {
                        HRESULT.Check(UnsafeNativeMethodsMilCoreApi.WICCodec.CreateColorContext(factoryMaker.ImagingFactoryPtr, out _colorContextHandle));
                        HRESULT.Check(IWICCC.InitializeFromMemory(_colorContextHandle, rawBytes, (uint)numBytesRead));
                    }
 
                    return;
                }
                else
                {
                    bufferSize += _bufferSizeIncrement;
                    byte[] newRawBytes = new byte[bufferSize];
                    rawBytes.CopyTo(newRawBytes, 0);
                    rawBytes = newRawBytes;
                }
            }
 
            throw new ArgumentException(SR.ColorContext_FileTooLarge, filename);
        }
 
        /// Note: often the data buffer is larger than the actual data in it.
        ///
        /// dontThrowException is for preserving the 3.* behavior of ColorContext(SafeMILHandle)
        ///
        private void FromRawBytes(byte[] data, int dataLength, bool dontThrowException) 
        {
            Invariant.Assert(dataLength <= data.Length);
            Invariant.Assert(dataLength >= 0);
            
            UnsafeNativeMethods.PROFILEHEADER header;
            UnsafeNativeMethods.PROFILE profile;
 
            unsafe
            {
                fixed (void *dataPtr = data)
                {
                    profile.dwType = NativeMethods.ProfileType.PROFILE_MEMBUFFER;
                    profile.pProfileData = dataPtr;
                    profile.cbDataSize = (uint)dataLength;
 
                    _colorContextHelper.OpenColorProfile(ref profile);
 
                    if (_colorContextHelper.IsInvalid)
                    {
                        if (dontThrowException)
                        {
                            return;
                        }
                        else
                        {                
                            HRESULT.Check(Marshal.GetHRForLastWin32Error());
                        }
                    }
                }
            }
 
            if (!_colorContextHelper.GetColorProfileHeader(out header))
            {
                if (dontThrowException)
                {
                    return;
                }
                else
                {                
                    HRESULT.Check(Marshal.GetHRForLastWin32Error());
                }
            }
            
            // Copy the important stuff from the header into our smaller cache
            _profileHeader.phSize            = header.phSize;
            _profileHeader.phCMMType         = header.phCMMType;
            _profileHeader.phVersion         = header.phVersion;
            _profileHeader.phClass           = header.phClass;
            _profileHeader.phDataColorSpace  = header.phDataColorSpace;
            _profileHeader.phConnectionSpace = header.phConnectionSpace;
            _profileHeader.phDateTime_0      = header.phDateTime_0;
            _profileHeader.phDateTime_1      = header.phDateTime_1;
            _profileHeader.phDateTime_2      = header.phDateTime_2;
            _profileHeader.phSignature       = header.phSignature;
            _profileHeader.phPlatform        = header.phPlatform;
            _profileHeader.phProfileFlags    = header.phProfileFlags;
            _profileHeader.phManufacturer    = header.phManufacturer;
            _profileHeader.phModel           = header.phModel;
            _profileHeader.phAttributes_0    = header.phAttributes_0;
            _profileHeader.phAttributes_1    = header.phAttributes_1;
            _profileHeader.phRenderingIntent = header.phRenderingIntent;
            _profileHeader.phIlluminant_0    = header.phIlluminant_0;
            _profileHeader.phIlluminant_1    = header.phIlluminant_1;
            _profileHeader.phIlluminant_2    = header.phIlluminant_2;
            _profileHeader.phCreator         = header.phCreator;
 
            switch (_profileHeader.phDataColorSpace)
            {
                case NativeMethods.ColorSpace.SPACE_XYZ:
                case NativeMethods.ColorSpace.SPACE_Lab:
                case NativeMethods.ColorSpace.SPACE_Luv:
                case NativeMethods.ColorSpace.SPACE_YCbCr:
                case NativeMethods.ColorSpace.SPACE_Yxy:
                case NativeMethods.ColorSpace.SPACE_HSV:
                case NativeMethods.ColorSpace.SPACE_HLS:
                case NativeMethods.ColorSpace.SPACE_CMY:
                    _numChannels = 3;
                    _colorSpaceFamily = StandardColorSpace.Unknown;
                    break;
                case NativeMethods.ColorSpace.SPACE_RGB:
                    _colorSpaceFamily = StandardColorSpace.Rgb;
                    _numChannels = 3;
                    break;
                case NativeMethods.ColorSpace.SPACE_GRAY:
                    _colorSpaceFamily = StandardColorSpace.Gray;
                    _numChannels = 1;
                    break;
                case NativeMethods.ColorSpace.SPACE_CMYK:
                    _colorSpaceFamily = StandardColorSpace.Cmyk;
                    _numChannels = 4;
                    break;
                case NativeMethods.ColorSpace.SPACE_2_CHANNEL:
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    _numChannels = 2;
                    break;
                case NativeMethods.ColorSpace.SPACE_3_CHANNEL:
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    _numChannels = 3;
                    break;
                case NativeMethods.ColorSpace.SPACE_4_CHANNEL:
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    _numChannels = 4;
                    break;
                case NativeMethods.ColorSpace.SPACE_5_CHANNEL:
                    _numChannels = 5;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_6_CHANNEL:
                    _numChannels = 6;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_7_CHANNEL:
                    _numChannels = 7;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_8_CHANNEL:
                    _numChannels = 8;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_9_CHANNEL:
                    _numChannels = 9;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_A_CHANNEL:
                    _numChannels = 10;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_B_CHANNEL:
                    _numChannels = 11;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_C_CHANNEL:
                    _numChannels = 12;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_D_CHANNEL:
                    _numChannels = 13;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_E_CHANNEL:
                    _numChannels = 14;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                case NativeMethods.ColorSpace.SPACE_F_CHANNEL:
                    _numChannels = 15;
                    _colorSpaceFamily = StandardColorSpace.Multichannel;
                    break;
                default:
                    _numChannels = 0;
                    _colorSpaceFamily = StandardColorSpace.Unknown;
                    break;
            }
        }
 
        #endregion
 
        #region Private Fields
 
        private ColorContextHelper _colorContextHelper;
 
        private StandardColorSpace _colorSpaceFamily;
 
        private int _numChannels;
 
        private Uri _profileUri;
        
        private bool _isProfileUriNotFromUser;
 
        private AbbreviatedPROFILEHEADER _profileHeader;
 
        private SafeMILHandle _colorContextHandle;
 
        private const int _bufferSizeIncrement = 1024 * 1024;  // 1 Mb
 
        private const int _maximumColorContextLength = _bufferSizeIncrement * 32; // 32 Mb
 
        private readonly static NativeMethods.COLORTYPE[] _colorTypeFromChannels =
            new NativeMethods.COLORTYPE[9] {
                NativeMethods.COLORTYPE.COLOR_UNDEFINED,
                NativeMethods.COLORTYPE.COLOR_UNDEFINED,
                NativeMethods.COLORTYPE.COLOR_UNDEFINED,
                NativeMethods.COLORTYPE.COLOR_3_CHANNEL,
                NativeMethods.COLORTYPE.COLOR_CMYK,
                NativeMethods.COLORTYPE.COLOR_5_CHANNEL,
                NativeMethods.COLORTYPE.COLOR_6_CHANNEL,
                NativeMethods.COLORTYPE.COLOR_7_CHANNEL,
                NativeMethods.COLORTYPE.COLOR_8_CHANNEL
                };
 
        private const string _colorProfileResources = "ColorProfiles";
 
        private const string _sRGBProfileName = "sRGB_icm";
 
        [StructLayout(LayoutKind.Sequential)]
        private struct AbbreviatedPROFILEHEADER
        {
            public uint phSize;                  // profile size in bytes
            public uint phCMMType;               // CMM for this profile
            public uint phVersion;               // profile format version number
            public uint phClass;                 // type of profile
            public NativeMethods.ColorSpace phDataColorSpace;  // color space of data
            public uint phConnectionSpace;       // PCS
            public uint phDateTime_0;            // date profile was created
            public uint phDateTime_1;            // date profile was created
            public uint phDateTime_2;            // date profile was created
            public uint phSignature;             // magic number ("Reserved for internal use.")
            public uint phPlatform;              // primary platform
            public uint phProfileFlags;          // various bit settings
            public uint phManufacturer;          // device manufacturer
            public uint phModel;                 // device model number
            public uint phAttributes_0;          // device attributes
            public uint phAttributes_1;          // device attributes
            public uint phRenderingIntent;       // rendering intent
            public uint phIlluminant_0;          // profile illuminant
            public uint phIlluminant_1;          // profile illuminant
            public uint phIlluminant_2;          // profile illuminant
            public uint phCreator;               // profile creator
            // Not including the reserved bits because we don't want to unnecessarily
            // increase the size of ColorContext
            //    public byte phReserved[44];   
        };
 
        internal enum StandardColorSpace : int
        {
            Unknown = 0,
            Srgb = 1,
            ScRgb = 2,
            Rgb = 3,
            Cmyk = 4,
            Gray = 6,
            Multichannel = 7
        }
 
        #endregion
    }
}