18

this question seems posted at many places over the interwebs and SO, but I could not find a satisfactory answer :(

How can I convert a RGB value to a CMYK value using an ICC profile?

The closest answer I have is there, where it explains how to convert from CMYK to RGB but not the other way around, which is what I need. (http://stackoverflow.com/questions/4920482/cmyk-to-rgb-formula-of-photoshop/5076731#5076731)

float[] colorValues = new float[4];
colorValues[0] = c / 255f;
colorValues[1] = m / 255f;
colorValues[2] = y / 255f;
colorValues[3] = k / 255f;

System.Windows.Media.Color color = Color.FromValues(colorValues,
new Uri(@"C:\Users\me\Documents\ISOcoated_v2_300_eci.icc"));
System.Drawing.Color rgbColor = System.Drawing.Color.FromArgb(color.R, color.G, color.B);

I guess I should be using some classes/structures/methods from the System.Windows.Media namespace.

The System.Windows.Media.Color structure contains a method FromRgb, but I can't get CMYK values after, out of that System.Windows.Media.Color!

Many thanks

Paul Suart
  • 6,505
  • 7
  • 44
  • 65
Bruno
  • 4,685
  • 7
  • 54
  • 105
  • Have you seen this? http://stackoverflow.com/questions/2426432/convert-rgb-color-to-cmyk – Hannesh Mar 08 '11 at 19:25
  • yes but this seems incorrect, I need to work with a ICC profile in order to be the most precise, this method looks like an approximation, but thanks! – Bruno Mar 08 '11 at 19:48
  • I've now got a solution to converting an RGB value to CMYK using an ICC profile - if you're interested I'll post the code. – Paul Suart Sep 04 '11 at 21:11
  • ibiza: did you got the solution. Can you please let me know did you converted RGB to CYMK using ICC profile. – Umesha MS Dec 06 '11 at 10:38

4 Answers4

12

I don't know of any C# API or library that can achieve this. However, if you have enough C/C++ knowledge to build a wrapper for C#, I see two options:

The System.Windows.Media namespace is very limited. There's probably a powerful engine (WCS?) behind it, but just a small part is made available.

Update:

Here's some C# code to do the conversion using WCS. It certainly could use a wrapper that would make it easier to use:

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

namespace ICM
{
    public class WindowsColorSystem
    {
        [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;
            }
        };

        public const uint ProfileFilenameType = 1;
        public const uint ProfileMembufferType = 2;

        public const uint ProfileRead = 1;
        public const uint ProfileReadWrite = 2;


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

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

        public enum LogicalColorSpace : uint
        {
            CalibratedRGB = 0x00000000,
            sRGB = 0x73524742,
            WindowsColorSpace = 0x57696E20
        };

        public enum ColorTransformMode : uint
        {
            ProofMode = 0x00000001,
            NormalMode = 0x00000002,
            BestMode = 0x00000003,
            EnableGamutChecking = 0x00010000,
            UseRelativeColorimetric = 0x00020000,
            FastTranslate = 0x00040000,
            PreserveBlack = 0x00100000,
            WCSAlways = 0x00200000
        };


        enum ColorType : int
        {
            Gray = 1,
            RGB = 2,
            XYZ = 3,
            Yxy = 4,
            Lab = 5,
            _3_Channel = 6,
            CMYK = 7,
            _5_Channel = 8,
            _6_Channel = 9,
            _7_Channel = 10,
            _8_Channel = 11,
            Named = 12
        };


        public const uint IntentPerceptual = 0;
        public const uint IntentRelativeColorimetric = 1;
        public const uint IntentSaturation = 2;
        public const uint IntentAbsoluteColorimetric = 3;

        public const uint IndexDontCare = 0;


        [StructLayout(LayoutKind.Sequential)]
        public struct RGBColor
        {
            public ushort red;
            public ushort green;
            public ushort blue;
            public ushort pad;
        };

        [StructLayout(LayoutKind.Sequential)]
        public struct CMYKColor
        {
            public ushort cyan;
            public ushort magenta;
            public ushort yellow;
            public ushort black;
        };

        [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);

        [DllImport("mscms.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
        static extern bool CloseColorProfile(IntPtr hProfile);

        [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, CallingConvention = CallingConvention.Winapi)]
        static extern IntPtr CreateMultiProfileTransform(
            [In] IntPtr[] profiles,
            uint nProfiles,
            [In] uint[] intents,
            uint nIntents,
            ColorTransformMode flags,
            uint indexPreferredCMM);

        [DllImport("mscms.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
        static extern bool DeleteColorTransform(IntPtr hTransform);

        [DllImport("mscms.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
        static extern bool TranslateColors(
            IntPtr hColorTransform,
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2), In] RGBColor[] inputColors,
            uint nColors,
            ColorType ctInput,
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2), Out] CMYKColor[] outputColors,
            ColorType ctOutput);



        public static void Test()
        {
            bool success;

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

            ProfileFilename sRGBFilename = new ProfileFilename(profileName.ToString());
            IntPtr hSRGBProfile = OpenColorProfile(sRGBFilename, ProfileRead, FileShare.Read, CreateDisposition.OpenExisting);

            ProfileFilename isoCoatedFilename = new ProfileFilename(@"C:\Users\me\Documents\ISOcoated_v2_300_eci.icc");
            IntPtr hIsoCoatedProfile = OpenColorProfile(isoCoatedFilename, ProfileRead, FileShare.Read, CreateDisposition.OpenExisting);

            IntPtr[] profiles = new IntPtr[] { hSRGBProfile, hIsoCoatedProfile };
            uint[] intents = new uint[] { IntentPerceptual };
            IntPtr transform = CreateMultiProfileTransform(profiles, 2, intents, 1, ColorTransformMode.BestMode, IndexDontCare);

            RGBColor[] rgbColors = new RGBColor[1];
            rgbColors[0] = new RGBColor();
            CMYKColor[] cmykColors = new CMYKColor[1];
            cmykColors[0] = new CMYKColor();

            rgbColors[0].red = 30204;
            rgbColors[0].green = 4420;
            rgbColors[0].blue = 60300;

            success = TranslateColors(transform, rgbColors, 1, ColorType.RGB, cmykColors, ColorType.CMYK);

            success = DeleteColorTransform(transform);

            success = CloseColorProfile(hSRGBProfile);
            success = CloseColorProfile(hIsoCoatedProfile);
        }
    }
}
Codo
  • 75,595
  • 17
  • 168
  • 206
  • 1
    Codo:RGB values varies from 0-255 but you have mentioned RGB(30204, 4420, 60300); And for YCMK value varies from 0-1 but the result of transformation are very large. Can you please let me know how to convert this value to 0 -1. – Umesha MS Dec 06 '11 at 10:34
  • 3
    The input and output values are in the range from 0 to 65535 (unsigned word). So in your case, you have to multiply your input values with 257 (not 256 or 255) and divide the output values by 65535. – Codo Dec 06 '11 at 18:18
  • Codo: thanks for replay. after applying the changes u told. output looks ok. – Umesha MS Dec 07 '11 at 08:56
  • Just to add another solution to this question, I found today (yes 3 years+ later) this (*Javascript*) project that seems to be able to convert between different color schemes : https://github.com/jiin/Rainbow – Bruno Apr 29 '15 at 16:28
  • 1
    Rainbow uses the CMYK formula posted all over the net that is so poor I cannot understand what it could be good for. The result is so far from the real color. I cannot think of any situation where it would be helpful. Stop using it. It makes no sense. – Codo Apr 30 '15 at 06:13
  • In a 64 bit process, the ```ProfileFilename``` must use a member alignment (```Pack```) of 8 while the ```RGBColor``` and ```CMYKColor``` structures require the ```Size``` set to 16 bytes. There are no attributes that match both the 32 and 64 bit environment. You have to use different types for a 32 and 64 bit process. – Mark Aug 25 '15 at 08:28
  • I'm using this sample, unfortunately the values it creates are CMY (black = 0). Is this dependent on the ICC profile used? My google-fu didn't provide me with any resources for more info. – Nattfrosten Jan 05 '17 at 14:02
  • Yes, it depends on the ICC profile used. Try it with ISO_coated_v2_300_eci.icc, contained in this [zip file](http://www.eci.org/_media/downloads/icc_profiles_from_eci/eci_offset_2009.zip). – Codo Jan 05 '17 at 17:20
  • Cheers, Thanks a lot! Another q if you don't mind, how do I go about converting back from cmyk to rgb? My first try was to reverse the order of the profiles in the aggregated transform, and then call transform with reversed input/output, eg: TranslateColors(_cmykToRgbTransform, cmykColors, 1, ColorType.CMYK, rgbColors, ColorType.RGB) Where cmykToRgbTransform is instantiated with profiles = cmykToRgbProfiles = { _hIsoCoatedProfile, _hSrgbProfile } This only generates empty rgb values. Any help would be fantastic – Nattfrosten Jan 10 '17 at 09:56
  • Have a look at http://stackoverflow.com/a/4412368/413337. It's pretty simple and doable without Interop. – Codo Jan 10 '17 at 10:08
  • Cheers, that did it! – Nattfrosten Jan 10 '17 at 19:19
2

None of the answers here seem to satisfactorily address the need to use an ICC profile.

I found an MS Connect issue page that has some sample code using Windows Imaging Components to convert an RBG JPEG to CMYK using an ICC profile.

If you have your ICC file and a sample JPEG file, you can set-up a console app to use this code very quickly.

I have saved the ICC profile in a folder called "Profiles" and have set the "Copy to Output Directory" value to "Always".

The JPEG is saved to a folder called "Images", and I have set its "Build Action" value to "Embedded Resource".

The console app project needs references to the following modules:

  • PresentationCore
  • System.Xaml
  • WindowsBase

The console app in full (named CMYKConversion) :

Program.cs:

using System;

namespace CMYKConversion
{
    class Program
    {
        static void Main(string[] args)
        {
            Converter c = new Converter();
            c.Convert();

            Console.ReadKey();
        }
    }
}

Converter.cs:

using System;
using System.IO;
using System.Reflection;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace CMYKConversion
{
    public class Converter
    {
        public void Convert()
        {
            var rgbJpeg = BitmapFrame.Create(GetStreamFromResource("CMYKConversion.Images.Desert.jpg"));
            var iccCmykJpeg = new ColorConvertedBitmap(
                rgbJpeg,
                new ColorContext(PixelFormats.Default),
                new ColorContext(GetProfilePath("Profiles/1010_ISO_Coated_39L.icc")),
                PixelFormats.Cmyk32
                );
            var jpegBitmapEncoder = new JpegBitmapEncoder();
            jpegBitmapEncoder.Frames.Add(BitmapFrame.Create(iccCmykJpeg));
            var iccCmykJpegStream = new MemoryStream();
            jpegBitmapEncoder.Save(iccCmykJpegStream);

            iccCmykJpegStream.Flush();
            SaveMemoryStream(iccCmykJpegStream, "C:\\desertCMYK.jpg");
            iccCmykJpegStream.Close();
        }

        private Stream GetStreamFromResource(string name)
        {
            return typeof(Program).Assembly.GetManifestResourceStream(name);
        }

        private Uri GetProfilePath(string name)
        {
            string folder = Path.GetDirectoryName(Assembly.GetAssembly(typeof(Program)).CodeBase);
            return new Uri(Path.Combine(folder, name));
        }

        private void SaveMemoryStream(MemoryStream ms, string fileName)
        {
            FileStream outStream = File.OpenWrite(fileName);
            ms.WriteTo(outStream);
            outStream.Flush();
            outStream.Close();
        }
    }
}
Paul Suart
  • 6,505
  • 7
  • 44
  • 65
1

According to an MVP GDI+ can read CMYK but can't encode it (Source: http://www.pcreview.co.uk/forums/convert-rgb-image-cmyk-t1419911.html). They go on to say that using TIF as an intermediation format may be the way to go.

Other than that, you might try Graphics Mill imaging SDK for .NET at http://imaging.aurigma.com/ (I'm not affiliated with this company).

I know this isn't much of an answer, but hopefully it sheds some light and points you in the right direction.

Brent Newbury
  • 373
  • 4
  • 12
1

You could have a look at this: Convert RGB color to CMYK?

Although this conversion is fairly subjective, hence the need for the ICC profile, it may be that you can extract that "factor" from the ICC and adjust the formula?

What is the context is which you need to convert the RGB values to CMYK?

Community
  • 1
  • 1
Mark Redman
  • 24,079
  • 20
  • 92
  • 147
  • Hi Mark and thanks for your help. I've seen this kind of formulas to convert RGB to CMYK, but I would really prefer to use an ICC profile as it is much more precise from what I've red. The context is that I am generating an image file and I would like to use only CMYK compatible colors as the image will be printed and I want no color distortion, so I want to be sure that the colors that I am using will be recognized exactly as is by a CMYK printer. I am not sure I understand your answer though? Kind regards – Bruno Mar 11 '11 at 19:37
  • @ibiza, ok, what you can do in this case then, is create the image as RGB, then use ImageMagick to convert to CMYK (which supports ICC profiles) – Mark Redman Mar 12 '11 at 13:45