Code example for WcsGetDefaultColorProfile
Asked Answered
S

6

7

Does anyone have a working code example showing a call to the Windows Color System function WcsGetDefaultColorProfile to get the default color profile for a specific device? It works for me when I pass null for the pDeviceName parameter, but when I try to pass the device name of a monitor, I always get back an error code of ERROR_FILE_NOT_FOUND.

I would prefer a C# example, but I'll take anything I can get. I can't find any sample code for the newer WCS profile management functions anywhere.

Steger answered 23/11, 2012 at 17:49 Comment(2)
Any update on this? I have the impression that it doesn't work.Fikes
@Fikes It does work, see below.Nitz
S
5

I was running into the same problem, and the reason for your frustration is that the MSDN docs are incorrect (or at best misleading) about the pDeviceName parameter to WcsGetDefaultColorProfile.

The MSDN doc (http://msdn.microsoft.com/en-us/library/dd372247(v=vs.85).aspx) indicates pDeviceName refers to the "name of the device", which for display devices one would assume to be a windows display device name, such as "\.\DISPLAY1", as returned in the DeviceName parameter of the DISPLAY_DEVICE struct from EnumDisplayDevices.

In fact, what it is requiring here is the DeviceKey parameter of the monitor, and specifically the DeviceKey obtained when the EDD_GET_DEVICE_INTERFACE_NAME flag is used in EnumDisplayDevices.

So working code looks like this, assuming szDisplayDeviceName is already set to display device name you care about, e.g., "\.\DISPLAY1":

WCHAR szPath[MAX_PATH];
DISPLAY_DEVICE dd;
dd.cb = sizeof(dd);
if (EnumDisplayDevices(szDisplayDeviceName, 0, &dd, EDD_GET_DEVICE_INTERFACE_NAME))
{
    if (WcsGetDefaultColorProfile(WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, 
          dd.DeviceKey,
          CPT_ICC,
          CPST_PERCEPTUAL,
          1,  // dwProfileID -- doesn't seem to matter what value you use here
          MAX_PATH * sizeof(WCHAR),
          szPath))
    {
        PROFILE profile;
        profile.cbDataSize = (DWORD)(wcslen(szPath) + 1) * sizeof(WCHAR);
        profile.dwType = PROFILE_FILENAME;
        profile.pProfileData = (PVOID)szPath;

        HPROFILE hProfile = OpenColorProfile(&profile,
           PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);

        // now do something with the profile
    }
}
Sceptic answered 3/12, 2014 at 2:11 Comment(0)
G
2

Following up on the answer from Matt M above (thanks!): If you want to match what other apps do, then use the first monitor returned by EnumDisplayDevices as above.

But note that this may return an incorrect (inactive/disabled) monitor in a multi-monitor setup where one or more monitors are disabled but still connected. Of course, Microsoft doesn't document this anywhere, and so a lot of apps including big players like Photoshop are broken in that regard :(

In case you want to do the right thing, you have to call EnumDisplayDevices multiple times until you find the active monitor (flags DISPLAY_DEVICE_ACTIVE and DISPLAY_DEVICE_MULTI_DRIVER in case multiple monitors are associated to the display).

Groundspeed answered 12/1, 2015 at 2:26 Comment(0)
V
2

Tried translate Mark's code to C#:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace MonitorProfileNS {
    class MonitorProfile {
        [Flags()]
        enum DisplayDeviceStateFlags : UInt32 {
            // from: http://www.pinvoke.net/default.aspx/Enums/DisplayDeviceStateFlags.html
            // equvalent to defines from: wingdi.h (c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\wingdi.h)
            //#define DISPLAY_DEVICE_ATTACHED_TO_DESKTOP      0x00000001
            //#define DISPLAY_DEVICE_MULTI_DRIVER             0x00000002
            //#define DISPLAY_DEVICE_PRIMARY_DEVICE           0x00000004
            //#define DISPLAY_DEVICE_MIRRORING_DRIVER         0x00000008
            //#define DISPLAY_DEVICE_VGA_COMPATIBLE           0x00000010
            //#if (_WIN32_WINNT >= _WIN32_WINNT_WIN2K)
            //#define DISPLAY_DEVICE_REMOVABLE                0x00000020
            //#endif // (_WIN32_WINNT >= _WIN32_WINNT_WIN2K)
            //#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
            //#define DISPLAY_DEVICE_ACC_DRIVER               0x00000040
            //#endif
            //#define DISPLAY_DEVICE_MODESPRUNED              0x08000000
            //#if (_WIN32_WINNT >= _WIN32_WINNT_WIN2K)
            //#define DISPLAY_DEVICE_REMOTE                   0x04000000
            //#define DISPLAY_DEVICE_DISCONNECT               0x02000000
            //#endif
            //#define DISPLAY_DEVICE_TS_COMPATIBLE            0x00200000
            //#if (_WIN32_WINNT >= _WIN32_WINNT_LONGHORN)
            //#define DISPLAY_DEVICE_UNSAFE_MODES_ON          0x00080000
            //#endif

            ///* Child device state */
            //#if (_WIN32_WINNT >= _WIN32_WINNT_WIN2K)
            //#define DISPLAY_DEVICE_ACTIVE              0x00000001
            //#define DISPLAY_DEVICE_ATTACHED            0x00000002
            //#endif // (_WIN32_WINNT >= _WIN32_WINNT_WIN2K)
            /// <summary>The device is part of the desktop.</summary>
            AttachedToDesktop = 0x1,
            MultiDriver = 0x2,
            /// <summary>The device is part of the desktop.</summary>
            PrimaryDevice = 0x4,
            /// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
            MirroringDriver = 0x8,
            /// <summary>The device is VGA compatible.</summary>
            VGACompatible = 0x10,
            /// <summary>The device is removable; it cannot be the primary display.</summary>
            Removable = 0x20,
            /// <summary>The device has more display modes than its output devices support.</summary>
            ModesPruned = 0x8000000,
            Remote = 0x4000000,
            Disconnect = 0x2000000,

            /// <summary>Child device state: DISPLAY_DEVICE_ACTIVE</summary>
            Active = 0x1,
            /// <summary>Child device state: DISPLAY_DEVICE_ATTACHED</summary>
            Attached = 0x2
        }

        enum DeviceClassFlags : UInt32 {
            // from: c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\Icm.h
            /// <summary>
            ///#define CLASS_MONITOR           'mntr' 
            /// </summary>
            CLASS_MONITOR = 0x6d6e7472,

            /// <summary>
            /// #define CLASS_PRINTER           'prtr'
            /// </summary>
            CLASS_PRINTER = 0x70727472,

            /// <summary>
            /// #define CLASS_SCANNER           'scnr'
            /// </summary>
            CLASS_SCANNER = 0x73636e72
        }

        enum WCS_PROFILE_MANAGEMENT_SCOPE : UInt32 {
            // from: c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\Icm.h
            WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE,
            WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER
        }

        enum COLORPROFILETYPE: UInt32 {
            // from: c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\Icm.h
            CPT_ICC,
            CPT_DMP,
            CPT_CAMP,
            CPT_GMMP
        }

        enum COLORPROFILESUBTYPE: UInt32{
            // from: c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\Icm.h
            // intent
            CPST_PERCEPTUAL = 0,
            CPST_RELATIVE_COLORIMETRIC = 1,
            CPST_SATURATION = 2,
            CPST_ABSOLUTE_COLORIMETRIC = 3,

            // working space
            CPST_NONE,
            CPST_RGB_WORKING_SPACE,
            CPST_CUSTOM_WORKING_SPACE,
        };

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct DISPLAY_DEVICE {
            // from: http://www.pinvoke.net/default.aspx/Structures/DISPLAY_DEVICE.html
            [MarshalAs(UnmanagedType.U4)]
            public int cb;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string DeviceName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string DeviceString;
            [MarshalAs(UnmanagedType.U4)]
            public DisplayDeviceStateFlags StateFlags;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string DeviceID;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string DeviceKey;
        }

        const UInt32 EDD_GET_DEVICE_INTERFACE_NAME = 0x1;

        //BOOL EnumDisplayDevices(
        //  _In_ LPCTSTR         lpDevice,
        //  _In_ DWORD           iDevNum,
        //  _Out_ PDISPLAY_DEVICE lpDisplayDevice,
        //  _In_ DWORD           dwFlags
        //);
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern UInt32 EnumDisplayDevices(string s, UInt32 iDevNum, ref DISPLAY_DEVICE displayDevice, UInt32 dwFlags);

        // from: c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\Icm.h
        //BOOL WINAPI WcsGetUsePerUserProfiles(
        //  _In_ LPCWSTR pDeviceName,
        //  _In_ DWORD dwDeviceClass,
        //  _Out_ BOOL *pUsePerUserProfiles
        //);
        /// <summary>
        /// https://msdn.microsoft.com/en-us/library/windows/desktop/dd372253(v=vs.85).aspx
        /// </summary>
        /// <returns>0, if failed</returns>
        [DllImport("Mscms.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern UInt32 WcsGetUsePerUserProfiles(string deviceName, DeviceClassFlags deviceClass, out UInt32 usePerUserProfiles);

        // from: c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\Icm.h
        //BOOL WINAPI WcsGetDefaultColorProfileSize(
        //  _In_ WCS_PROFILE_MANAGEMENT_SCOPE profileManagementScope,
        //  _In_opt_ PCWSTR pDeviceName,
        //  _In_ COLORPROFILETYPE cptColorProfileType,
        //  _In_ COLORPROFILESUBTYPE cpstColorProfileSubType,
        //  _In_ DWORD dwProfileID,
        //  _Out_ PDWORD pcbProfileName
        //);
        /// <summary>
        /// https://msdn.microsoft.com/en-us/library/windows/desktop/dd372249(v=vs.85).aspx
        /// </summary>
        /// <param name="cbProfileName">Size in bytes! String length is /2</param>
        /// <returns>0, if failed</returns>
        [DllImport("Mscms.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern UInt32 WcsGetDefaultColorProfileSize( WCS_PROFILE_MANAGEMENT_SCOPE scope,
            string deviceName,
            COLORPROFILETYPE colorProfileType,
            COLORPROFILESUBTYPE colorProfileSubType,
            UInt32 dwProfileID,
            out UInt32 cbProfileName
        );

        // from: c:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\um\Icm.h
        //BOOL WINAPI WcsGetDefaultColorProfile(
        //  _In_ WCS_PROFILE_MANAGEMENT_SCOPE profileManagementScope,
        //  _In_opt_ PCWSTR pDeviceName,
        //  _In_ COLORPROFILETYPE cptColorProfileType,
        //  _In_ COLORPROFILESUBTYPE cpstColorProfileSubType,
        //  _In_ DWORD dwProfileID,
        //  _In_ DWORD cbProfileName,
        //  _Out_ LPWSTR pProfileName
        //);
        /// <summary>
        /// https://msdn.microsoft.com/en-us/library/windows/desktop/dd372247(v=vs.85).aspx
        /// </summary>
        /// <returns>0, if failed</returns>
        [DllImport("Mscms.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern UInt32 WcsGetDefaultColorProfile(WCS_PROFILE_MANAGEMENT_SCOPE scope,
            string deviceName,
            COLORPROFILETYPE colorProfileType,
            COLORPROFILESUBTYPE colorProfileSubType,
            UInt32 dwProfileID,
            UInt32 cbProfileName,
            StringBuilder profileName
        );

        public static string GetMonitorProfile() {
            // c++ recommendation: https://mcmap.net/q/1467704/-code-example-for-wcsgetdefaultcolorprofile

            DISPLAY_DEVICE displayDevice = new DISPLAY_DEVICE();
            displayDevice.cb = Marshal.SizeOf(displayDevice);

            // First, find the primary adaptor
            string  adaptorName=null;
            UInt32 deviceIndex = 0;

            while (EnumDisplayDevices(null, deviceIndex++, ref displayDevice, EDD_GET_DEVICE_INTERFACE_NAME)!=0) {
                if ((displayDevice.StateFlags & DisplayDeviceStateFlags.AttachedToDesktop)!=0 &&  
                    (displayDevice.StateFlags & DisplayDeviceStateFlags.PrimaryDevice)!=0) {
                    adaptorName = displayDevice.DeviceName;
                    break;
                }
            }

            // Second, find the first active (and attached) monitor
            string deviceName=null;
            deviceIndex = 0;
            while (EnumDisplayDevices(adaptorName, deviceIndex++, ref displayDevice, EDD_GET_DEVICE_INTERFACE_NAME)!=0) {
                if ((displayDevice.StateFlags & DisplayDeviceStateFlags.Active) != 0 &&
                    (displayDevice.StateFlags & DisplayDeviceStateFlags.Attached) != 0) {
                    deviceName = displayDevice.DeviceKey;
                    break;
                }
            }

            // Third, find out whether to use the global or user profile
            UInt32 usePerUserProfiles = 0;
            UInt32 res = WcsGetUsePerUserProfiles(deviceName, DeviceClassFlags.CLASS_MONITOR, out usePerUserProfiles);
            if (res==0) {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            }

            // Finally, get the profile name
            WCS_PROFILE_MANAGEMENT_SCOPE scope = (usePerUserProfiles != 0) ?
                WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER :
                WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE;

            UInt32 cbProfileName = 0;   // in bytes
            res = WcsGetDefaultColorProfileSize(scope, 
                deviceName,
                COLORPROFILETYPE.CPT_ICC, 
                COLORPROFILESUBTYPE.CPST_RGB_WORKING_SPACE, 
                0, 
                out cbProfileName);
            if (res == 0) {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            }

            int nLengthProfileName = (int)cbProfileName / 2;    // WcsGetDefaultColor... is using LPWSTR, i.e. 2 bytes/char
            StringBuilder profileName = new StringBuilder(nLengthProfileName);
            res = WcsGetDefaultColorProfile(scope, 
                deviceName, 
                COLORPROFILETYPE.CPT_ICC, 
                COLORPROFILESUBTYPE.CPST_RGB_WORKING_SPACE, 
                0, 
                cbProfileName, 
                profileName);
            if (res == 0) {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            }

            return profileName.ToString();
        }

        [DllImport("Mscms.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool GetColorDirectory(IntPtr pMachineName, StringBuilder pBuffer, ref uint pdwSize);

        public static string GetColorDirectory() {
            // s. https://mcmap.net/q/1481534/-is-there-an-equivalent-to-winapi-getcolordirectory-in-net
            uint pdwSize = 260;  // MAX_PATH 
            StringBuilder sb = new StringBuilder((int)pdwSize);
            if (GetColorDirectory(IntPtr.Zero, sb, ref pdwSize)) {
                return sb.ToString();
            } else {
                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
            }
        }

    }
}
Vat answered 15/8, 2016 at 16:31 Comment(0)
N
1

The documentation doesn't make it obvious at all, but from the previous answers I pieced together a complete solution (in the interests of clarity there is no error checking here). Also, this will only return a default as specified by the user, if they've never set a default, you will need to use GetStandardColorSpaceProfile to get the "other" default.

DISPLAY_DEVICE displayDevice = {};
displayDevice.cb = sizeof(DISPLAY_DEVICE);

// First, find the primary adaptor
std::stringw adaptorName;
DWORD deviceIndex = 0;
while (::EnumDisplayDevicesW(nullptr, deviceIndex++, &displayDevice, EDD_GET_DEVICE_INTERFACE_NAME))
{
    if (displayDevice.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP &&
        displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
    {
        adaptorName = displayDevice.DeviceName;
        break;
    }
}

// Second, find the first active (and attached) monitor
std::string deviceName;
deviceIndex = 0;
while (::EnumDisplayDevicesW(adaptorName, deviceIndex++, &displayDevice, EDD_GET_DEVICE_INTERFACE_NAME))
{
    if (displayDevice.StateFlags & DISPLAY_DEVICE_ACTIVE &&
        displayDevice.StateFlags & DISPLAY_DEVICE_ATTACHED)
    {
        deviceName = displayDevice.DeviceKey;
        break;
    }
}

// Third, find out whether to use the global or user profile
BOOL usePerUserProfiles = FALSE;
WcsGetUsePerUserProfiles(deviceName, CLASS_MONITOR, &usePerUserProfiles);

// Finally, get the profile name
const WCS_PROFILE_MANAGEMENT_SCOPE scope = usePerUserProfiles ? WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER : WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE;

DWORD profileNameLength = 0; // In bytes
WcsGetDefaultColorProfileSize(scope, deviceName, CPT_ICC, CPST_RGB_WORKING_SPACE, 0, &profileNameLength);

wchar_t *const profileName = new wchar_t[profileNameLength / sizeof(wchar_t)];
WcsGetDefaultColorProfile(scope, deviceName, CPT_ICC, CPST_RGB_WORKING_SPACE, 0, profileNameLength, profileName);
// Do something with your profile name
delete[] profileName;
Nitz answered 10/3, 2016 at 14:41 Comment(0)
B
0

Add the following structures:

public const uint ProfileRead = 1;

public enum FileShare : uint
{
    Read = 1,
    Write = 2,
    Delete = 4
};

public enum CreateDisposition : uint
{
    CreateNew = 1,
    CreateAlways = 2,
    OpenExisting = 3,
    OpenAlways = 4,
    TruncateExisting = 5

};

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class ProfileFilename
{
    public uint type;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string profileData;
    public uint dataSize;

    public ProfileFilename(string filename)
    {
        type = ProfileFilenameType;
        profileData = filename;
        dataSize = (uint)filename.Length * 2 + 2;
    }
};

[DllImport("mscms.dll", SetLastError = true, EntryPoint = "GetStandardColorSpaceProfileW", CallingConvention = CallingConvention.Winapi)]
static extern bool GetStandardColorSpaceProfile(    uint machineName,
                                                    LogicalColorSpace profileID,
                                                    [MarshalAs(UnmanagedType.LPTStr), In, Out] StringBuilder profileName,
                                                    ref uint size);

[DllImport("mscms.dll", SetLastError = true, EntryPoint = "OpenColorProfileW", CallingConvention = CallingConvention.Winapi)]
    static extern IntPtr OpenColorProfile(  [MarshalAs(UnmanagedType.LPStruct)] ProfileFilename profile,
                                            uint desiredAccess,
                                            FileShare shareMode,
                                            CreateDisposition creationMode);

Sample to open default profile:

public openDefaultColorProfile()
{ 
    StringBuilder profileName = new StringBuilder(256);
    uint size = (uint)profileName.Capacity * 2;
    success = GetStandardColorSpaceProfile(0, LogicalColorSpace.WindowsColorSpace, profileName, ref size);

    ProfileFilename sRGBFilename = new ProfileFilename(profileName.ToString());
    IntPtr hSRGBProfile = OpenColorProfile(sRGBFilename, ProfileRead, FileShare.Read, CreateDisposition.OpenExisting);
}
Burk answered 12/3, 2013 at 14:46 Comment(1)
Maxim, please see my original question. Nowhere in your answer do you use the WcsGetDefaultColorProfile API, and your answer doesn't demonstrate how to get the profile for a specific device. Am I missing something?Steger
C
0

I see a lot of rather complex answers using that API, but what about something much simpler and more effective?

static const std::filesystem::path GetICMProfile(const HDC hDC)
{
    std::filesystem::path Result;

    DWORD BufferSize = 0;

    if (!::GetICMProfile(hDC, &BufferSize, nullptr) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
        std::wstring wFilePath;

        wFilePath.resize(BufferSize);

        if (::GetICMProfile(hDC, &BufferSize, &wFilePath[0])) {
            Result = wFilePath;
        }
    }

    return Result;
}

You can pass any device context to it (screen, printer, scanner) no matter how you obtained it, and get the ICM profile path back, for example:

const HDC hDC = GetDC(nullptr); // get screen DC
cosnt std::filesystem::path ProfilePath = GetICMProfile(hDC);
ReleaseDC(hDC);
if (!ProfilePath.empty()) {
    // TODO: Do whatever you want with the profile
}

This also returns full filesystem path as opposed to only the filename returned by WcsGetDefaultColorProfile() so you don't have to query for color directory and concatenate if you want to open the profile.

Finally, using std::filesystem::path allows easier use of color management libraries such as LittleCMS by allowing you to pass ANSI or UTF-8 file path without having to explicitly convert from Windows UCS-2.

Clot answered 23/8, 2022 at 17:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.