{"id":3156,"date":"2019-03-05T13:05:31","date_gmt":"2019-03-05T18:05:31","guid":{"rendered":"https:\/\/blogs.mathworks.com\/steve\/?p=3156"},"modified":"2019-11-01T22:35:49","modified_gmt":"2019-11-02T02:35:49","slug":"converting-from-srgb-to-swop-cmyk","status":"publish","type":"post","link":"https:\/\/blogs.mathworks.com\/steve\/2019\/03\/05\/converting-from-srgb-to-swop-cmyk\/","title":{"rendered":"Converting from sRGB to SWOP CMYK"},"content":{"rendered":"<div class=\"content\"><p>An Image Processing Toolbox user recently reported to us good results for using <tt>makecform<\/tt> and <tt>applycform<\/tt> to convert colors from sRGB to CMYK. Here's the code they used:<\/p><pre>c = makecform('srgb2cmyk');\r\ncmyk_colors = applycform(srgb_colors,c);<\/pre><p>The user asked if we could provide formulas for the conversion, but we could not. The conversion process involves several computation steps. One of the steps is a specialized interpolation involving a four-dimensional lookup table, for which there is no formula.<\/p><p>If one is very familiar with the internals of <tt>makecform<\/tt> and <tt>applycform<\/tt>, as well as with the organization of code within the toolbox, one could work out the exact steps of the computation. But that's only about four people. So, I thought I would decode the steps here and take you through it.<\/p><p>Before diving in too deep, though, I first want to observe that there is no single standard way to convert to CMYK. That's because the conversion depends strongly on several fundamental factors, including:<\/p><div><ul><li>the reflectance properties of a specific set of ink pigments<\/li><li>how the spread of ink dots on a particular type of paper<\/li><li>heuristics that control exactly where and how to lay down the black (K) ink<\/li><li>how to adjust input colors that cannot be exactly reproduced in the output CMYK space<\/li><\/ul><\/div><p>These factors make the conversion depend on the characteristics of a specific device.<\/p><p>If that's the case, though, then what is <tt>applycform<\/tt> doing? It is performing conversion based on the information contained in two <i>ICC profiles<\/i>, sRGB.icm and swopcmyk.icm. ICC stands for International Color Consortium, and an ICC profile contains computational recipes for converting device colors (or a standard color space such as sRGB) to a common working color space, called a <i>connection space<\/i>. The connection space is either CIEXYZ or CIELAB.<\/p><p>Here's a peek at the contents of sRGB.icm:<\/p><pre class=\"codeinput\">srgb_profile = iccread(<span class=\"string\">'sRGB.icm'<\/span>);\r\nsrgb_profile.Header\r\n<\/pre><pre class=\"codeoutput\">\r\nans = \r\n\r\n  struct with fields:\r\n\r\n                  Size: 3124\r\n               CMMType: 'appl'\r\n               Version: '2.1.0'\r\n           DeviceClass: 'display'\r\n            ColorSpace: 'RGB'\r\n       ConnectionSpace: 'XYZ'\r\n          CreationDate: '03-Dec-1999 17:30:00'\r\n             Signature: 'acsp'\r\n       PrimaryPlatform: 'Apple'\r\n                 Flags: 0\r\n            IsEmbedded: 0\r\n         IsIndependent: 1\r\n    DeviceManufacturer: 'IEC '\r\n           DeviceModel: 'sRGB'\r\n            Attributes: 0\r\n        IsTransparency: 0\r\n               IsMatte: 0\r\n            IsNegative: 0\r\n       IsBlackandWhite: 0\r\n       RenderingIntent: 'perceptual'\r\n            Illuminant: [0.9642 1 0.8249]\r\n               Creator: 'HP  '\r\n\r\n<\/pre><p>This profile specifies the computations for converting between sRGB and CIEXYZ.<\/p><p>Here's a look at the second profile, swopcmyk.icm:<\/p><pre class=\"codeinput\">cmyk_profile = iccread(<span class=\"string\">'swopcmyk.icm'<\/span>);\r\ncmyk_profile.Header\r\n<\/pre><pre class=\"codeoutput\">\r\nans = \r\n\r\n  struct with fields:\r\n\r\n                  Size: 503780\r\n               CMMType: 'appl'\r\n               Version: '4.0.0'\r\n           DeviceClass: 'output'\r\n            ColorSpace: 'CMYK'\r\n       ConnectionSpace: 'Lab'\r\n          CreationDate: '14-Apr-2006 02:00:00'\r\n             Signature: 'acsp'\r\n       PrimaryPlatform: 'Apple'\r\n                 Flags: 131072\r\n            IsEmbedded: 0\r\n         IsIndependent: 1\r\n    DeviceManufacturer: '    '\r\n           DeviceModel: '    '\r\n            Attributes: 0\r\n        IsTransparency: 0\r\n               IsMatte: 0\r\n            IsNegative: 0\r\n       IsBlackandWhite: 0\r\n       RenderingIntent: 'relative colorimetric'\r\n            Illuminant: [0.9642 1 0.8249]\r\n               Creator: '    '\r\n             ProfileID: [1&times;16 uint8]\r\n\r\n<\/pre><p>This profile specifies the computations for converting between something called SWOP CMYK and CIELAB. SWOP (Specifications for Web Offset Printing), now part of Idealliance, was a U.S. organization that created common industry specifications that are widely used in the U.S. print processes and materials.<\/p><p>Enough background. To see how the computation is actually performed, we need to dig (deep) into the internals of the struct returned by <tt>makecform<\/tt>.<\/p><pre class=\"codeinput\">cform = makecform(<span class=\"string\">'srgb2cmyk'<\/span>)\r\n<\/pre><pre class=\"codeoutput\">\r\ncform = \r\n\r\n  struct with fields:\r\n\r\n            c_func: @applyiccsequence\r\n     ColorSpace_in: 'RGB'\r\n    ColorSpace_out: 'CMYK'\r\n          encoding: 'uint16'\r\n             cdata: [1&times;1 struct]\r\n\r\n<\/pre><p>Notice the function handle, <tt>@applyiccsequence<\/tt>. Most of the function handles we're going to see are for \"private\" toolbox functions. That means they are not directly callable, but you <b>can<\/b> look inside them to see what they're doing. Use <tt>which -all<\/tt> to see where <tt>applyiccsequence<\/tt> is located.<\/p><pre class=\"codeinput\">which <span class=\"string\">-all<\/span> <span class=\"string\">applyiccsequence<\/span>\r\n<\/pre><pre class=\"codeoutput\">\/Applications\/MATLAB_R2018b.app\/toolbox\/images\/colorspaces\/private\/applyiccsequence.m  % Private to colorspaces\r\n<\/pre><p>Then you can type<\/p><pre>edit private\/applyiccsequence<\/pre><p>to load the MATLAB source file into the editor for your inspection. This particular source file is not very interesting; it just applies a sequence of computational steps. The data used by the function handle is in the <tt>cdata<\/tt> field of the struct.<\/p><pre class=\"codeinput\">cform.cdata.sequence\r\n<\/pre><pre class=\"codeoutput\">\r\nans = \r\n\r\n  struct with fields:\r\n\r\n         source: [1&times;1 struct]\r\n      fix_black: [1&times;1 struct]\r\n        fix_pcs: [1&times;1 struct]\r\n    destination: [1&times;1 struct]\r\n\r\n<\/pre><p>The four fields mentioned represent four steps:<\/p><div><ol><li>Convert from the \"source\" colorspace (sRGB) to the connection colorspace (which is CIEXYZ for the sRGB.icm profile).<\/li><li>Apply a blackpoint adjustment step that is needed because the two ICC profiles have different versions.<\/li><li>Convert from CIEXYZ to CIELAB, which is needed because the two profiles are using different connection spaces.<\/li><li>Convert from CIELAB to the \"destination\" colorspace (SWOP CMYK).<\/li><\/ol><\/div><h4>Step 1: Convert from source colorspace to CIEXYZ<a name=\"03725731-056a-4227-aa28-7419e2b9fc79\"><\/a><\/h4><p>This step is achieved using the private function <tt>applymattrc_fwd<\/tt> and parameters stored in the nested <tt>cdata<\/tt> struct.<\/p><pre class=\"codeinput\">cform.cdata.sequence.source\r\n<\/pre><pre class=\"codeoutput\">\r\nans = \r\n\r\n  struct with fields:\r\n\r\n            c_func: @applymattrc_fwd\r\n     ColorSpace_in: 'rgb'\r\n    ColorSpace_out: 'xyz'\r\n          encoding: 'double'\r\n             cdata: [1&times;1 struct]\r\n\r\n<\/pre><pre class=\"codeinput\">mattrc = cform.cdata.sequence.source.cdata.MatTRC\r\n<\/pre><pre class=\"codeoutput\">\r\nmattrc = \r\n\r\n  struct with fields:\r\n\r\n      RedColorant: [0.4361 0.2225 0.0139]\r\n    GreenColorant: [0.3851 0.7169 0.0971]\r\n     BlueColorant: [0.1431 0.0606 0.7141]\r\n           RedTRC: [1024&times;1 uint16]\r\n         GreenTRC: [1024&times;1 uint16]\r\n          BlueTRC: [1024&times;1 uint16]\r\n\r\n<\/pre><p>The \"MatTRC\" computation applies a \"tone-response curve\" to each of the input color components, and then it multiplies by a matrix formed from <tt>RedColorant<\/tt>, <tt>GreenColorant<\/tt>, and <tt>BlueColorant<\/tt>. Here are the tone response curves.<\/p>\r\n<pre class=\"codeoutput\">\r\nx = linspace(0,1,length(mattrc.RedTRC));\r\nsubplot(2,2,1)\r\nplot(x,double(mattrc.RedTRC)\/65535)\r\ntitle(<span class=\"string\">'Red TRC'<\/span>)\r\nsubplot(2,2,2)\r\nplot(x,double(mattrc.GreenTRC)\/65535)\r\ntitle(<span class=\"string\">'Green TRC'<\/span>)\r\nsubplot(2,2,3)\r\nplot(x,double(mattrc.BlueTRC)\/65535)\r\ntitle(<span class=\"string\">'Blue TRC'<\/span>)\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"http:\/\/blogs.mathworks.com\/steve\/files\/rgb_cmyk_conversion_01.png\" alt=\"\"> <h4>Step 2: Adjust the blackpoint.<a name=\"fc843a47-d5d0-49ef-8afd-61dca710ab13\"><\/a><\/h4><p>This step is performed using the private function <tt>applyblackpoint<\/tt> and the parameters in the nested <tt>cdata<\/tt> struct.<\/p><pre class=\"codeinput\">cform.cdata.sequence.fix_black\r\n<\/pre>\r\n<pre class=\"codeoutput\">\r\nans = \r\n\r\n  struct with fields:\r\n\r\n            c_func: @applyblackpoint\r\n     ColorSpace_in: 'xyz'\r\n    ColorSpace_out: 'xyz'\r\n          encoding: 'double'\r\n             cdata: [1&times;1 struct]\r\n\r\n<\/pre><pre class=\"codeinput\">cform.cdata.sequence.fix_black.cdata\r\n<\/pre><pre class=\"codeoutput\">\r\nans = \r\n\r\n  struct with fields:\r\n\r\n    colorspace: 'xyz'\r\n     upconvert: 1\r\n\r\n<\/pre><p>For these parameters, I can see from reading <tt>applyblackpoint<\/tt> that the specific computation performed is:<\/p><pre>out = (0.0034731 * whitepoint) + (0.9965269 * in)<\/pre><h4>Step 3: Convert from CIEXYZ to CIELAB<a name=\"38435764-2cf5-4bb3-87dd-0ae273110af7\"><\/a><\/h4><p>Here are the specifics:<\/p><pre class=\"codeinput\">cform.cdata.sequence.fix_pcs\r\n<\/pre><pre class=\"codeoutput\">\r\nans = \r\n\r\n  struct with fields:\r\n\r\n            c_func: @xyz2lab\r\n     ColorSpace_in: 'xyz'\r\n    ColorSpace_out: 'lab'\r\n          encoding: 'double'\r\n             cdata: [1&times;1 struct]\r\n\r\n<\/pre><pre class=\"codeinput\">cform.cdata.sequence.fix_pcs.cdata\r\n<\/pre><pre class=\"codeoutput\">\r\nans = \r\n\r\n  struct with fields:\r\n\r\n    whitepoint: [0.9642 1 0.8249]\r\n\r\n<\/pre><p>The function that is used here, <tt>xyz2lab<\/tt> is not the documented, user-callable toolbox, but a private version of it. As with the other private functions, you can see it by typing:<\/p><pre>edit private\/xyz2lab<\/pre><p>The whitepoint parameter for the computation is the standard whitepoint value used for ICC profile computations:<\/p><pre class=\"codeinput\">whitepoint(<span class=\"string\">'icc'<\/span>)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n    0.9642    1.0000    0.8249\r\n\r\n<\/pre><h4>Step 4: Convert from CIELAB to the destination space, SWOP CMYK<a name=\"e31e03aa-20ce-48ed-889b-2f941e6dcf0e\"><\/a><\/h4><p>This turns out to the most complicated step. Here is the computational function handle and the parameters used:<\/p><pre class=\"codeinput\">cform.cdata.sequence.destination.c_func\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n  function_handle with value:\r\n\r\n    @applyclut\r\n\r\n<\/pre><pre class=\"codeinput\">cform.cdata.sequence.destination.cdata.luttag\r\n<\/pre><pre class=\"codeoutput\">\r\nans = \r\n\r\n  struct with fields:\r\n\r\n             MFT: 4\r\n       PreShaper: {[256&times;1 uint16]  [256&times;1 uint16]  [256&times;1 uint16]}\r\n       PreMatrix: [3&times;4 double]\r\n     InputTables: {[256&times;1 uint16]  [256&times;1 uint16]  [256&times;1 uint16]}\r\n            CLUT: [21&times;21&times;21&times;4 uint16]\r\n    OutputTables: {1&times;4 cell}\r\n      PostMatrix: []\r\n      PostShaper: []\r\n\r\n<\/pre><p>You really have to step through the execution of <tt>applyclut<\/tt> in the debugger to see each of about 6 substeps. In this case it turns out that only 2 of the substeps actually have an effect. One is a simple scaling to account for encoding differences between the two ICC profiles:<\/p><pre>out = in * (257 \/ 256);<\/pre><p>The second substep that matters is the big one, and it uses this 4-D lookup table:<\/p><pre class=\"codeinput\">size(cform.cdata.sequence.destination.cdata.luttag.CLUT)\r\n<\/pre><pre class=\"codeoutput\">\r\nans =\r\n\r\n    21    21    21     4\r\n\r\n<\/pre><p>This is really 4 different 3-D lookup tables packed into one array. The three dimensions of each lookup table correspond to the three components of the CIELAB space (L*, a*, and b*), and the 4 different tables correspond to the four components of the output space (C, M, Y, and K). The <tt>applyclut<\/tt> file contains a local function, <tt>clutinterp_tet3<\/tt>, that performs <i>tetrahedral interpolation<\/i>. See my <a href=\"https:\/\/blogs.mathworks.com\/steve\/2006\/11\/24\/tetrahedral-interpolation-for-colorspace-conversion\/\">24-Nov-2006 blog post<\/a> for a discussion of tetrahedral interpolation for color-space conversion.<\/p><p>All of the other steps do have relatively simple formulas associated with them, but this last step, based on these multidimensional lookup tables, does not.<\/p><p>Now you know, more or less, the computational steps for converting this orange color to SWOP CMYK.<\/p><pre class=\"codeinput\">orange_rgb = [215 136 37]\/255;\r\nclf\r\npatch([0 1 1 0 0],[0 0 1 1 0],orange_rgb);\r\n<\/pre><img decoding=\"async\" vspace=\"5\" hspace=\"5\" src=\"http:\/\/blogs.mathworks.com\/steve\/files\/rgb_cmyk_conversion_02.png\" alt=\"\"> <pre class=\"codeinput\">orange_cmyk = applycform(orange_rgb,cform)\r\n<\/pre><pre class=\"codeoutput\">\r\norange_cmyk =\r\n\r\n    0.0286    0.4748    0.8917    0.1153\r\n\r\n<\/pre><p>That's just a tiny bit of cyan, a fair amount of magenta, a lot of yellow, and small amount of black.<\/p><script language=\"JavaScript\"> <!-- \r\n    function grabCode_16e9ed00c4d94369a5c50ca603f421a9() {\r\n        \/\/ Remember the title so we can use it in the new page\r\n        title = document.title;\r\n\r\n        \/\/ Break up these strings so that their presence\r\n        \/\/ in the Javascript doesn't mess up the search for\r\n        \/\/ the MATLAB code.\r\n        t1='16e9ed00c4d94369a5c50ca603f421a9 ' + '##### ' + 'SOURCE BEGIN' + ' #####';\r\n        t2='##### ' + 'SOURCE END' + ' #####' + ' 16e9ed00c4d94369a5c50ca603f421a9';\r\n    \r\n        b=document.getElementsByTagName('body')[0];\r\n        i1=b.innerHTML.indexOf(t1)+t1.length;\r\n        i2=b.innerHTML.indexOf(t2);\r\n \r\n        code_string = b.innerHTML.substring(i1, i2);\r\n        code_string = code_string.replace(\/REPLACE_WITH_DASH_DASH\/g,'--');\r\n\r\n        \/\/ Use \/x3C\/g instead of the less-than character to avoid errors \r\n        \/\/ in the XML parser.\r\n        \/\/ Use '\\x26#60;' instead of '<' so that the XML parser\r\n        \/\/ doesn't go ahead and substitute the less-than character. \r\n        code_string = code_string.replace(\/\\x3C\/g, '\\x26#60;');\r\n\r\n        copyright = 'Copyright 2019 The MathWorks, Inc.';\r\n\r\n        w = window.open();\r\n        d = w.document;\r\n        d.write('<pre>\\n');\r\n        d.write(code_string);\r\n\r\n        \/\/ Add copyright line at the bottom if specified.\r\n        if (copyright.length > 0) {\r\n            d.writeln('');\r\n            d.writeln('%%');\r\n            if (copyright.length > 0) {\r\n                d.writeln('% _' + copyright + '_');\r\n            }\r\n        }\r\n\r\n        d.write('<\/pre>\\n');\r\n\r\n        d.title = title + ' (MATLAB code)';\r\n        d.close();\r\n    }   \r\n     --> <\/script><p style=\"text-align: right; font-size: xx-small; font-weight:lighter;   font-style: italic; color: gray\"><br><a href=\"javascript:grabCode_16e9ed00c4d94369a5c50ca603f421a9()\"><span style=\"font-size: x-small;        font-style: italic;\">Get \r\n      the MATLAB code <noscript>(requires JavaScript)<\/noscript><\/span><\/a><br><br>\r\n      Published with MATLAB&reg; R2018b<br><\/p><\/div><!--\r\n16e9ed00c4d94369a5c50ca603f421a9 ##### SOURCE BEGIN #####\r\n%%\r\n% An Image Processing Toolbox user recently reported to us good results\r\n% for using |makecform| and |applycform| to convert colors from sRGB to\r\n% CMYK. Here's the code they used:\r\n%\r\n%  c = makecform('srgb2cmyk');\r\n%  cmyk_colors = applycform(srgb_colors,c);\r\n%\r\n% The user asked if we could provide formulas for the conversion, but we\r\n% could not. The conversion process involves several computation steps.\r\n% One of the steps is a specialized interpolation involving a\r\n% four-dimensional lookup table, for which there is no formula.\r\n%\r\n% If one is very familiar with the internals of |makecform| and\r\n% |applycform|, as well as with the organization of code within the\r\n% toolbox, one could work out the exact steps of the computation. But\r\n% that's only about four people. So, I thought I would decode the steps\r\n% here and take you through it.\r\n%\r\n% Before diving in too deep, though, I first want to observe that there\r\n% is no single standard way to convert to CMYK. That's because the conversion \r\n% depends strongly on several fundamental factors, including:\r\n% \r\n% * the reflectance properties of a specific set of ink pigments\r\n% * how the spread of ink dots on a particular type of paper\r\n% * heuristics that control exactly where and how to lay down the black\r\n% (K) ink\r\n% * how to adjust input colors that cannot be exactly reproduced in the\r\n% output CMYK space\r\n%\r\n% These factors make the conversion depend on the characteristics of a\r\n% specific device.\r\n%\r\n% If that's the case, though, then what is |applycform| doing? It is\r\n% performing conversion based on the information contained in two _ICC\r\n% profiles_, sRGB.icm and swopcmyk.icm. ICC stands for International\r\n% Color Consortium, and an ICC profile contains computational recipes\r\n% for converting device colors (or a standard color space such as sRGB)\r\n% to a common working color space, called a _connection space_. The \r\n% connection space is either CIEXYZ or CIELAB.\r\n%\r\n% Here's a peek at the contents of sRGB.icm:\r\n\r\nsrgb_profile = iccread('sRGB.icm');\r\nsrgb_profile.Header\r\n\r\n%%\r\n% This profile specifies the computations for converting between sRGB\r\n% and CIEXYZ.\r\n%\r\n% Here's a look at the second profile, swopcmyk.icm:\r\n\r\ncmyk_profile = iccread('swopcmyk.icm');\r\ncmyk_profile.Header\r\n\r\n%%\r\n% This profile specifies the computations for converting between\r\n% something called SWOP CMYK and CIELAB. SWOP (Specifications for Web\r\n% Offset Printing), now part of Idealliance, was a U.S. organization\r\n% that created common industry specifications that are widely used in\r\n% the U.S. print processes and materials.\r\n%\r\n% Enough background. To see how the computation is actually performed,\r\n% we need to dig (deep) into the internals of the struct returned by\r\n% |makecform|.\r\n\r\ncform = makecform('srgb2cmyk')\r\n\r\n%%\r\n% Notice the function handle, |@applyiccsequence|. Most of the function\r\n% handles we're going to see are for \"private\" toolbox functions. That\r\n% means they are not directly callable, but you *can* look inside them\r\n% to see what they're doing. Use |which -all| to see where\r\n% |applyiccsequence| is located.\r\n\r\nwhich -all applyiccsequence\r\n\r\n%%\r\n% Then you can type\r\n%\r\n%  edit private\/applyiccsequence\r\n%\r\n% to load the MATLAB source file into the editor for your inspection.\r\n% This particular source file is not very interesting; it just applies a\r\n% sequence of computational steps. The data used by the function handle\r\n% is in the |cdata| field of the struct.\r\n\r\ncform.cdata.sequence\r\n\r\n%%\r\n% The four fields mentioned represent four steps:\r\n%\r\n% # Convert from the \"source\" colorspace (sRGB) to the connection\r\n% colorspace (which is CIEXYZ for the sRGB.icm profile).\r\n% # Apply a blackpoint adjustment step that is needed because the two\r\n% ICC profiles have different versions.\r\n% # Convert from CIEXYZ to CIELAB, which is needed because the two\r\n% profiles are using different connection spaces.\r\n% # Convert from CIELAB to the \"destination\" colorspace (SWOP CMYK).\r\n%\r\n%% Step 1: Convert from source colorspace to CIEXYZ\r\n% This step is achieved\r\n% using the private function |applymattrc_fwd| and parameters stored in\r\n% the nested |cdata| struct.\r\n\r\ncform.cdata.sequence.source\r\n\r\n%%\r\nmattrc = cform.cdata.sequence.source.cdata.MatTRC\r\n\r\n%%\r\n% The \"MatTRC\" computation applies a \"tone-response curve\" to each of\r\n% the input color components, and then it multiplies by a matrix formed\r\n% from |RedColorant|, |GreenColorant|, and |BlueColorant|. Here are the\r\n% tone response curves.\r\n\r\nx = linspace(0,1,length(mattrc.RedTRC));\r\nsubplot(2,2,1)\r\nplot(x,double(mattrc.RedTRC)\/65535)\r\ntitle('Red TRC')\r\nsubplot(2,2,2)\r\nplot(x,double(mattrc.GreenTRC)\/65535)\r\ntitle('Green TRC')\r\nsubplot(2,2,3)\r\nplot(x,double(mattrc.BlueTRC)\/65535)\r\ntitle('Blue TRC')\r\n\r\n%% Step 2: Adjust the blackpoint.\r\n% This step is performed using the private function |applyblackpoint|\r\n% and the parameters in the nested |cdata| struct.\r\n\r\ncform.cdata.sequence.fix_black\r\n\r\n%%\r\n\r\ncform.cdata.sequence.fix_black.cdata\r\n\r\n%%\r\n% For these parameters, I can see from reading |applyblackpoint| that\r\n% the specific computation performed is:\r\n%\r\n%  out = (0.0034731 * whitepoint) + (0.9965269 * in)\r\n\r\n%% Step 3: Convert from CIEXYZ to CIELAB\r\n% Here are the specifics:\r\n\r\ncform.cdata.sequence.fix_pcs\r\n\r\n%%\r\ncform.cdata.sequence.fix_pcs.cdata\r\n\r\n%%\r\n% The function that is used here, |xyz2lab| is not the documented,\r\n% user-callable toolbox, but a private version of it. As with the other\r\n% private functions, you can see it by typing:\r\n%\r\n%  edit private\/xyz2lab\r\n%\r\n% The whitepoint parameter for the\r\n% computation is the standard whitepoint value used for ICC profile\r\n% computations:\r\n\r\nwhitepoint('icc')\r\n\r\n%% Step 4: Convert from CIELAB to the destination space, SWOP CMYK\r\n% This turns out to the most complicated step. Here is the computational\r\n% function handle and the parameters used:\r\n\r\ncform.cdata.sequence.destination.c_func\r\n\r\n%%\r\n\r\ncform.cdata.sequence.destination.cdata.luttag\r\n\r\n%%\r\n% You really have to step through the execution of |applyclut| in the\r\n% debugger to see each of about 6 substeps. In this case it turns out\r\n% that only 2 of the substeps actually have an effect. One is a simple\r\n% scaling to account for encoding differences between the two ICC\r\n% profiles:\r\n%\r\n%  out = in * (257 \/ 256);\r\n%\r\n% The second substep that matters is the big one, and it uses this 4-D\r\n% lookup table:\r\n\r\nsize(cform.cdata.sequence.destination.cdata.luttag.CLUT)\r\n\r\n%%\r\n% This is really 4 different 3-D lookup tables packed into one array.\r\n% The three dimensions of each lookup table correspond to the three\r\n% components of the CIELAB space (L*, a*, and b*), and the 4 different\r\n% tables correspond to the four components of the output space (C, M, Y,\r\n% and K). The |applyclut| file contains a local function,\r\n% |clutinterp_tet3|, that performs _tetrahedral interpolation_. See my\r\n% <https:\/\/blogs.mathworks.com\/steve\/2006\/11\/24\/tetrahedral-interpolation-for-colorspace-conversion\/ \r\n% 24-Nov-2006 blog post> for a discussion of tetrahedral interpolation\r\n% for color-space conversion.\r\n%\r\n% All of the other steps do have relatively simple formulas associated\r\n% with them, but this last step, based on these multidimensional lookup\r\n% tables, does not.\r\n%\r\n% Now you know, more or less, the computational steps for converting\r\n% this orange color to SWOP CMYK.\r\n\r\norange_rgb = [215 136 37]\/255;\r\nclf\r\npatch([0 1 1 0 0],[0 0 1 1 0],orange_rgb);\r\n\r\n%%\r\norange_cmyk = applycform(orange_rgb,cform)\r\n\r\n%%\r\n% That's just a tiny bit of cyan, a fair amount of magenta, a lot of\r\n% yellow, and small amount of black.\r\n##### SOURCE END ##### 16e9ed00c4d94369a5c50ca603f421a9\r\n-->","protected":false},"excerpt":{"rendered":"<div class=\"overview-image\"><img src=\"https:\/\/blogs.mathworks.com\/steve\/files\/rgb_cmyk_conversion_01.png\" class=\"img-responsive attachment-post-thumbnail size-post-thumbnail wp-post-image\" alt=\"\" decoding=\"async\" loading=\"lazy\" \/><\/div><p>An Image Processing Toolbox user recently reported to us good results for using makecform and applycform to convert colors from sRGB to CMYK. Here's the code they used:c =... <a class=\"read-more\" href=\"https:\/\/blogs.mathworks.com\/steve\/2019\/03\/05\/converting-from-srgb-to-swop-cmyk\/\">read more >><\/a><\/p>","protected":false},"author":42,"featured_media":3160,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[114,178,402,1233,530,705,32,112,210,68,190,72,52,827,1081],"_links":{"self":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3156"}],"collection":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/users\/42"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/comments?post=3156"}],"version-history":[{"count":5,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3156\/revisions"}],"predecessor-version":[{"id":3297,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/posts\/3156\/revisions\/3297"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media\/3160"}],"wp:attachment":[{"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/media?parent=3156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/categories?post=3156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.mathworks.com\/steve\/wp-json\/wp\/v2\/tags?post=3156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}