• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/core/SkICC.h"
9 #include "include/private/SkFixed.h"
10 #include "src/core/SkAutoMalloc.h"
11 #include "src/core/SkColorSpacePriv.h"
12 #include "src/core/SkEndian.h"
13 #include "src/core/SkICCPriv.h"
14 #include "src/core/SkMD5.h"
15 #include "src/core/SkUtils.h"
16 #include <securec.h>
17 
18 static constexpr char kDescriptionTagBodyPrefix[12] =
19         { 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'};
20 
21 static constexpr size_t kICCDescriptionTagSize = 44;
22 
23 static_assert(kICCDescriptionTagSize ==
24               sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest), "");
25 static constexpr size_t kDescriptionTagBodySize = kICCDescriptionTagSize * 2;  // ascii->utf16be
26 
27 static_assert(SkIsAlign4(kDescriptionTagBodySize), "Description must be aligned to 4-bytes.");
28 static constexpr uint32_t kDescriptionTagHeader[7] {
29     SkEndian_SwapBE32(kTAG_TextType),                        // Type signature
30     0,                                                       // Reserved
31     SkEndian_SwapBE32(1),                                    // Number of records
32     SkEndian_SwapBE32(12),                                   // Record size (must be 12)
33     SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
34     SkEndian_SwapBE32(kDescriptionTagBodySize),              // Length of string
35     SkEndian_SwapBE32(28),                                   // Offset of string
36 };
37 
38 static constexpr uint32_t kWhitePointTag[5] {
39     SkEndian_SwapBE32(kXYZ_PCSSpace),
40     0,
41     SkEndian_SwapBE32(0x0000f6d6), // X = 0.96420 (D50)
42     SkEndian_SwapBE32(0x00010000), // Y = 1.00000 (D50)
43     SkEndian_SwapBE32(0x0000d32d), // Z = 0.82491 (D50)
44 };
45 
46 // Google Inc. 2016 (UTF-16)
47 static constexpr uint8_t kCopyrightTagBody[] = {
48         0x00, 0x47, 0x00, 0x6f,
49         0x00, 0x6f, 0x00, 0x67,
50         0x00, 0x6c, 0x00, 0x65,
51         0x00, 0x20, 0x00, 0x49,
52         0x00, 0x6e, 0x00, 0x63,
53         0x00, 0x2e, 0x00, 0x20,
54         0x00, 0x32, 0x00, 0x30,
55         0x00, 0x31, 0x00, 0x36,
56 };
57 static_assert(SkIsAlign4(sizeof(kCopyrightTagBody)), "Copyright must be aligned to 4-bytes.");
58 static constexpr uint32_t kCopyrightTagHeader[7] {
59     SkEndian_SwapBE32(kTAG_TextType),                        // Type signature
60     0,                                                       // Reserved
61     SkEndian_SwapBE32(1),                                    // Number of records
62     SkEndian_SwapBE32(12),                                   // Record size (must be 12)
63     SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
64     SkEndian_SwapBE32(sizeof(kCopyrightTagBody)),            // Length of string
65     SkEndian_SwapBE32(28),                                   // Offset of string
66 };
67 
68 // We will write a profile with the minimum nine required tags.
69 static constexpr uint32_t kICCNumEntries = 9;
70 
71 static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
72 static constexpr uint32_t kTAG_desc_Bytes = sizeof(kDescriptionTagHeader) +
73                                             kDescriptionTagBodySize;
74 static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize +
75                                              kICCNumEntries * kICCTagTableEntrySize;
76 
77 static constexpr uint32_t kTAG_XYZ_Bytes = 20;
78 static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes;
79 static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes;
80 static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes;
81 
82 static constexpr uint32_t kTAG_TRC_Bytes = 40;
83 static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes;
84 static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset;
85 static constexpr uint32_t kTAG_bTRC_Offset = kTAG_rTRC_Offset;
86 
87 static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
88 static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + kTAG_TRC_Bytes;
89 
90 static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
91 static constexpr uint32_t kTAG_cprt_Bytes = sizeof(kCopyrightTagHeader) +
92                                             sizeof(kCopyrightTagBody);
93 static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes;
94 // icc profile cicp tag size, reference ICC Chapter 10.3
95 static constexpr uint32_t kTAG_cicp_bytes = 12;
96 
97 static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes;
98 static constexpr uint32_t kICCProfileSizeWithCicp = kICCProfileSize + kTAG_cicp_bytes;
99 
100 static constexpr int TOXYZ_MATRIX_COL_ZERO = 0;
101 static constexpr int TOXYZ_MATRIX_COL_ONE = 1;
102 static constexpr int TOXYZ_MATRIX_COL_TWO = 2;
103 
104 static constexpr uint32_t kICCHeader[kICCHeaderSize / 4] {
105     SkEndian_SwapBE32(kICCProfileSize),  // Size of the profile
106     0,                                   // Preferred CMM type (ignored)
107     SkEndian_SwapBE32(0x04300000),       // Version 4.3
108     SkEndian_SwapBE32(kDisplay_Profile), // Display device profile
109     SkEndian_SwapBE32(kRGB_ColorSpace),  // RGB input color space
110     SkEndian_SwapBE32(kXYZ_PCSSpace),    // XYZ profile connection space
111     0, 0, 0,                             // Date and time (ignored)
112     SkEndian_SwapBE32(kACSP_Signature),  // Profile signature
113     0,                                   // Platform target (ignored)
114     0x00000000,                          // Flags: not embedded, can be used independently
115     0,                                   // Device manufacturer (ignored)
116     0,                                   // Device model (ignored)
117     0, 0,                                // Device attributes (ignored)
118     SkEndian_SwapBE32(1),                // Relative colorimetric rendering intent
119     SkEndian_SwapBE32(0x0000f6d6),       // D50 standard illuminant (X)
120     SkEndian_SwapBE32(0x00010000),       // D50 standard illuminant (Y)
121     SkEndian_SwapBE32(0x0000d32d),       // D50 standard illuminant (Z)
122     0,                                   // Profile creator (ignored)
123     0, 0, 0, 0,                          // Profile id checksum (ignored)
124     0, 0, 0, 0, 0, 0, 0,                 // Reserved (ignored)
125     SkEndian_SwapBE32(kICCNumEntries),   // Number of tags
126 };
127 
128 static constexpr uint32_t kICCTagTable[3 * kICCNumEntries] {
129     // Profile description
130     SkEndian_SwapBE32(kTAG_desc),
131     SkEndian_SwapBE32(kTAG_desc_Offset),
132     SkEndian_SwapBE32(kTAG_desc_Bytes),
133 
134     // rXYZ
135     SkEndian_SwapBE32(kTAG_rXYZ),
136     SkEndian_SwapBE32(kTAG_rXYZ_Offset),
137     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
138 
139     // gXYZ
140     SkEndian_SwapBE32(kTAG_gXYZ),
141     SkEndian_SwapBE32(kTAG_gXYZ_Offset),
142     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
143 
144     // bXYZ
145     SkEndian_SwapBE32(kTAG_bXYZ),
146     SkEndian_SwapBE32(kTAG_bXYZ_Offset),
147     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
148 
149     // rTRC
150     SkEndian_SwapBE32(kTAG_rTRC),
151     SkEndian_SwapBE32(kTAG_rTRC_Offset),
152     SkEndian_SwapBE32(kTAG_TRC_Bytes),
153 
154     // gTRC
155     SkEndian_SwapBE32(kTAG_gTRC),
156     SkEndian_SwapBE32(kTAG_gTRC_Offset),
157     SkEndian_SwapBE32(kTAG_TRC_Bytes),
158 
159     // bTRC
160     SkEndian_SwapBE32(kTAG_bTRC),
161     SkEndian_SwapBE32(kTAG_bTRC_Offset),
162     SkEndian_SwapBE32(kTAG_TRC_Bytes),
163 
164     // White point
165     SkEndian_SwapBE32(kTAG_wtpt),
166     SkEndian_SwapBE32(kTAG_wtpt_Offset),
167     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
168 
169     // Copyright
170     SkEndian_SwapBE32(kTAG_cprt),
171     SkEndian_SwapBE32(kTAG_cprt_Offset),
172     SkEndian_SwapBE32(kTAG_cprt_Bytes),
173 };
174 
175 // This is like SkFloatToFixed, but rounds to nearest, preserving as much accuracy as possible
176 // when going float -> fixed -> float (it has the same accuracy when going fixed -> float -> fixed).
177 // The use of double is necessary to accommodate the full potential 32-bit mantissa of the 16.16
178 // SkFixed value, and so avoiding rounding problems with float. Also, see the comment in SkFixed.h.
float_round_to_fixed(float x)179 static SkFixed float_round_to_fixed(float x) {
180     return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5));
181 }
182 
write_xyz_tag(uint32_t * ptr,const skcms_Matrix3x3 & toXYZD50,int col)183 static void write_xyz_tag(uint32_t* ptr, const skcms_Matrix3x3& toXYZD50, int col) {
184     ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
185     ptr[1] = 0;
186     ptr[2] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[0][col]));
187     ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[1][col]));
188     ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[2][col]));
189 }
190 
write_trc_tag(uint32_t * ptr,const skcms_TransferFunction & fn)191 static void write_trc_tag(uint32_t* ptr, const skcms_TransferFunction& fn) {
192     ptr[0] = SkEndian_SwapBE32(kTAG_ParaCurveType);
193     ptr[1] = 0;
194     ptr[2] = (uint32_t) (SkEndian_SwapBE16(kGABCDEF_ParaCurveType));
195     ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(fn.g));
196     ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(fn.a));
197     ptr[5] = SkEndian_SwapBE32(float_round_to_fixed(fn.b));
198     ptr[6] = SkEndian_SwapBE32(float_round_to_fixed(fn.c));
199     ptr[7] = SkEndian_SwapBE32(float_round_to_fixed(fn.d));
200     ptr[8] = SkEndian_SwapBE32(float_round_to_fixed(fn.e));
201     ptr[9] = SkEndian_SwapBE32(float_round_to_fixed(fn.f));
202 }
203 
write_cicp_tag(uint32_t * ptr,const skcms_CICP & cicp)204 static void write_cicp_tag(uint32_t* ptr, const skcms_CICP& cicp) {
205     ptr[0] = SkEndian_SwapBE32(kTAG_CICP);
206     ptr[1] = 0;
207     ptr[2] =  SkEndian_SwapBE32((((uint32_t)cicp.colour_primaries & 0xFF) << 24) |
208              (((uint32_t)cicp.transfer_characteristics & 0xFF) << 16) |
209              (((uint32_t)cicp.matrix_coefficients & 0xFF) << 8) |
210              (uint32_t)cicp.full_range_flag);
211 }
212 
nearly_equal(float x,float y)213 static bool nearly_equal(float x, float y) {
214     // A note on why I chose this tolerance:  transfer_fn_almost_equal() uses a
215     // tolerance of 0.001f, which doesn't seem to be enough to distinguish
216     // between similar transfer functions, for example: gamma2.2 and sRGB.
217     //
218     // If the tolerance is 0.0f, then this we can't distinguish between two
219     // different encodings of what is clearly the same colorspace.  Some
220     // experimentation with example files lead to this number:
221     static constexpr float kTolerance = 1.0f / (1 << 11);
222     return ::fabsf(x - y) <= kTolerance;
223 }
224 
nearly_equal(const skcms_TransferFunction & u,const skcms_TransferFunction & v)225 static bool nearly_equal(const skcms_TransferFunction& u,
226                          const skcms_TransferFunction& v) {
227     return nearly_equal(u.g, v.g)
228         && nearly_equal(u.a, v.a)
229         && nearly_equal(u.b, v.b)
230         && nearly_equal(u.c, v.c)
231         && nearly_equal(u.d, v.d)
232         && nearly_equal(u.e, v.e)
233         && nearly_equal(u.f, v.f);
234 }
235 
nearly_equal(const skcms_Matrix3x3 & u,const skcms_Matrix3x3 & v)236 static bool nearly_equal(const skcms_Matrix3x3& u, const skcms_Matrix3x3& v) {
237     for (int r = 0; r < 3; r++) {
238         for (int c = 0; c < 3; c++) {
239             if (!nearly_equal(u.vals[r][c], v.vals[r][c])) {
240                 return false;
241             }
242         }
243     }
244     return true;
245 }
246 
247 // Return nullptr if the color profile doen't have a special name.
get_color_profile_description(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)248 const char* get_color_profile_description(const skcms_TransferFunction& fn,
249                                           const skcms_Matrix3x3& toXYZD50) {
250     bool srgb_xfer = nearly_equal(fn, SkNamedTransferFn::kSRGB);
251     bool srgb_gamut = nearly_equal(toXYZD50, SkNamedGamut::kSRGB);
252     if (srgb_xfer && srgb_gamut) {
253         return "sRGB";
254     }
255     bool line_xfer = nearly_equal(fn, SkNamedTransferFn::kLinear);
256     if (line_xfer && srgb_gamut) {
257         return "Linear Transfer with sRGB Gamut";
258     }
259     bool twoDotTwo = nearly_equal(fn, SkNamedTransferFn::k2Dot2);
260     if (twoDotTwo && srgb_gamut) {
261         return "2.2 Transfer with sRGB Gamut";
262     }
263     if (twoDotTwo && nearly_equal(toXYZD50, SkNamedGamut::kAdobeRGB)) {
264         return "AdobeRGB";
265     }
266     bool display_p3 = nearly_equal(toXYZD50, SkNamedGamut::kDisplayP3);
267     if (srgb_xfer || line_xfer) {
268         if (srgb_xfer && display_p3) {
269             return "sRGB Transfer with Display P3 Gamut";
270         }
271         if (line_xfer && display_p3) {
272             return "Linear Transfer with Display P3 Gamut";
273         }
274         bool rec2020 = nearly_equal(toXYZD50, SkNamedGamut::kRec2020);
275         if (srgb_xfer && rec2020) {
276             return "sRGB Transfer with Rec-BT-2020 Gamut";
277         }
278         if (line_xfer && rec2020) {
279             return "Linear Transfer with Rec-BT-2020 Gamut";
280         }
281     }
282     return nullptr;
283 }
284 
get_color_profile_tag(char dst[kICCDescriptionTagSize],const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)285 static void get_color_profile_tag(char dst[kICCDescriptionTagSize],
286                                   const skcms_TransferFunction& fn,
287                                   const skcms_Matrix3x3& toXYZD50) {
288     SkASSERT(dst);
289     if (const char* description = get_color_profile_description(fn, toXYZD50)) {
290         SkASSERT(strlen(description) < kICCDescriptionTagSize);
291 
292         // Without these extra (), GCC would warn us something like
293         //    ... sepecified bound 44 equals destination size ...
294         // which, yeah, is exactly what we're trying to do, copy the string
295         // and zero the rest of the destination if any.  Sheesh.
296         (strncpy(dst, description, kICCDescriptionTagSize));
297         // "If the length of src is less than n, strncpy() writes additional
298         // null bytes to dest to ensure that a total of n bytes are written."
299     } else {
300         memcpy(dst, kDescriptionTagBodyPrefix, sizeof(kDescriptionTagBodyPrefix));
301         SkMD5 md5;
302         md5.write(&toXYZD50, sizeof(toXYZD50));
303         static_assert(sizeof(fn) == sizeof(float) * 7, "packed");
304         md5.write(&fn, sizeof(fn));
305         SkMD5::Digest digest = md5.finish();
306         char* ptr = dst + sizeof(kDescriptionTagBodyPrefix);
307         for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
308             uint8_t byte = digest.data[i];
309             *ptr++ = SkHexadecimalDigits::gUpper[byte >> 4];
310             *ptr++ = SkHexadecimalDigits::gUpper[byte & 0xF];
311         }
312         SkASSERT(ptr == dst + kICCDescriptionTagSize);
313     }
314 }
315 
SkWriteICCProfile(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)316 sk_sp<SkData> SkWriteICCProfile(const skcms_TransferFunction& fn,
317                                 const skcms_Matrix3x3& toXYZD50) {
318     // We can't encode HDR transfer functions in ICC
319     if (classify_transfer_fn(fn) != sRGBish_TF) {
320         return nullptr;
321     }
322 
323     SkAutoMalloc profile(kICCProfileSize);
324     uint8_t* ptr = (uint8_t*) profile.get();
325 
326     // Write profile header
327     memcpy(ptr, kICCHeader, sizeof(kICCHeader));
328     ptr += sizeof(kICCHeader);
329 
330     // Write tag table
331     memcpy(ptr, kICCTagTable, sizeof(kICCTagTable));
332     ptr += sizeof(kICCTagTable);
333 
334     // Write profile description tag
335     memcpy(ptr, kDescriptionTagHeader, sizeof(kDescriptionTagHeader));
336     ptr += sizeof(kDescriptionTagHeader);
337     {
338         char colorProfileTag[kICCDescriptionTagSize];
339         get_color_profile_tag(colorProfileTag, fn, toXYZD50);
340 
341         // ASCII --> big-endian UTF-16.
342         for (size_t i = 0; i < kICCDescriptionTagSize; i++) {
343             *ptr++ = 0;
344             *ptr++ = colorProfileTag[i];
345         }
346     }
347 
348     // Write XYZ tags
349     write_xyz_tag((uint32_t*) ptr, toXYZD50, 0);
350     ptr += kTAG_XYZ_Bytes;
351     write_xyz_tag((uint32_t*) ptr, toXYZD50, 1);
352     ptr += kTAG_XYZ_Bytes;
353     write_xyz_tag((uint32_t*) ptr, toXYZD50, 2);
354     ptr += kTAG_XYZ_Bytes;
355 
356     // Write TRC tag
357     write_trc_tag((uint32_t*) ptr, fn);
358     ptr += kTAG_TRC_Bytes;
359 
360     // Write white point tag (must be D50)
361     memcpy(ptr, kWhitePointTag, sizeof(kWhitePointTag));
362     ptr += sizeof(kWhitePointTag);
363 
364     // Write copyright tag
365     memcpy(ptr, kCopyrightTagHeader, sizeof(kCopyrightTagHeader));
366     ptr += sizeof(kCopyrightTagHeader);
367     memcpy(ptr, kCopyrightTagBody, sizeof(kCopyrightTagBody));
368     ptr += sizeof(kCopyrightTagBody);
369 
370     SkASSERT(kICCProfileSize == ptr - (uint8_t*) profile.get());
371     return SkData::MakeFromMalloc(profile.release(), kICCProfileSize);
372 }
373 
SkWriteICCProfileWithCicp(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50,const skcms_CICP & cicp)374 sk_sp<SkData> SkWriteICCProfileWithCicp(const skcms_TransferFunction& fn,
375                                         const skcms_Matrix3x3& toXYZD50,
376                                         const skcms_CICP& cicp) {
377     SkAutoMalloc profile(kICCProfileSizeWithCicp);
378     uint8_t* pProfile = (uint8_t*) profile.get();
379 
380     // Write icc profile header
381     if (memcpy_s(pProfile, sizeof(kICCHeader), kICCHeader, sizeof(kICCHeader)) != EOK) {
382         return nullptr;
383     }
384     pProfile += sizeof(kICCHeader);
385 
386     // Write tag table
387     if (memcpy_s(pProfile, sizeof(kICCTagTable), kICCTagTable, sizeof(kICCTagTable)) != EOK) {
388         return nullptr;
389     }
390     pProfile += sizeof(kICCTagTable);
391 
392     // Write profile description tag
393     if (memcpy_s(pProfile, sizeof(kDescriptionTagHeader), kDescriptionTagHeader,
394         sizeof(kDescriptionTagHeader)) != EOK) {
395         return nullptr;
396     }
397     pProfile += sizeof(kDescriptionTagHeader);
398     char colorProfileTag[kICCDescriptionTagSize];
399     get_color_profile_tag(colorProfileTag, fn, toXYZD50);
400     for (size_t i = 0; i < kICCDescriptionTagSize; i++) {
401         *pProfile++ = 0;
402         *pProfile++ = colorProfileTag[i];
403     }
404 
405     // Write XYZ tags
406     write_xyz_tag((uint32_t*) pProfile, toXYZD50, TOXYZ_MATRIX_COL_ZERO);
407     pProfile += kTAG_XYZ_Bytes;
408     write_xyz_tag((uint32_t*) pProfile, toXYZD50, TOXYZ_MATRIX_COL_ONE);
409     pProfile += kTAG_XYZ_Bytes;
410     write_xyz_tag((uint32_t*) pProfile, toXYZD50, TOXYZ_MATRIX_COL_TWO);
411     pProfile += kTAG_XYZ_Bytes;
412 
413     // Write TRC tag
414     write_trc_tag((uint32_t*) pProfile, fn);
415     pProfile += kTAG_TRC_Bytes;
416 
417     // Write white point tag (must be D50)
418     if (memcpy_s(pProfile, sizeof(kWhitePointTag), kWhitePointTag, sizeof(kWhitePointTag)) != EOK) {
419         return nullptr;
420     }
421     pProfile += sizeof(kWhitePointTag);
422 
423     // Write copyright tag
424     if (memcpy_s(pProfile, sizeof(kCopyrightTagHeader), kCopyrightTagHeader, sizeof(kCopyrightTagHeader)) != EOK) {
425         return nullptr;
426     }
427     pProfile += sizeof(kCopyrightTagHeader);
428     if (memcpy_s(pProfile, sizeof(kCopyrightTagBody), kCopyrightTagBody, sizeof(kCopyrightTagBody) != EOK)) {
429         return nullptr;
430     }
431     pProfile += sizeof(kCopyrightTagBody);
432 
433     // Write cicp tag
434     write_cicp_tag((uint32_t*) pProfile, cicp);
435     pProfile += kTAG_cicp_bytes;
436 
437     SkASSERT(kICCProfileSizeWithCicp == pProfile - (uint8_t*) profile.get());
438     return SkData::MakeFromMalloc(profile.release(), kICCProfileSizeWithCicp);
439 }
440