1 //---------------------------------------------------------------------------------
2 //
3 //  Little Color Management System
4 //  Copyright (c) 1998-2020 Marti Maria Saguer
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the Software
11 // is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24 //---------------------------------------------------------------------------------
25 //
26 
27 #include "lcms2_internal.h"
28 
29 // Alpha copy ------------------------------------------------------------------------------------------------------------------
30 
31 // This macro return words stored as big endian
32 #define CHANGE_ENDIAN(w)    (cmsUInt16Number) ((cmsUInt16Number) ((w)<<8)|((w)>>8))
33 
34 
35 // Floor to byte, taking care of saturation
_cmsQuickSaturateByte(cmsFloat64Number d)36 cmsINLINE cmsUInt8Number _cmsQuickSaturateByte(cmsFloat64Number d)
37 {
38        d += 0.5;
39        if (d <= 0) return 0;
40        if (d >= 255.0) return 255;
41 
42        return (cmsUInt8Number) _cmsQuickFloorWord(d);
43 }
44 
45 
46 // Return the size in bytes of a given formatter
47 static
trueBytesSize(cmsUInt32Number Format)48 cmsUInt32Number trueBytesSize(cmsUInt32Number Format)
49 {
50     cmsUInt32Number fmt_bytes = T_BYTES(Format);
51 
52     // For double, the T_BYTES field returns zero
53     if (fmt_bytes == 0)
54         return sizeof(double);
55 
56     // Otherwise, it is already correct for all formats
57     return fmt_bytes;
58 }
59 
60 
61 // Several format converters
62 
63 typedef void(*cmsFormatterAlphaFn)(void* dst, const void* src);
64 
65 
66 // From 8
67 
68 static
copy8(void * dst,const void * src)69 void copy8(void* dst, const void* src)
70 {
71        memmove(dst, src, 1);
72 }
73 
74 static
from8to16(void * dst,const void * src)75 void from8to16(void* dst, const void* src)
76 {
77        cmsUInt8Number n = *(cmsUInt8Number*)src;
78        *(cmsUInt16Number*) dst = (cmsUInt16Number) FROM_8_TO_16(n);
79 }
80 
81 static
from8to16SE(void * dst,const void * src)82 void from8to16SE(void* dst, const void* src)
83 {
84     cmsUInt8Number n = *(cmsUInt8Number*)src;
85     *(cmsUInt16Number*)dst = CHANGE_ENDIAN(FROM_8_TO_16(n));
86 }
87 
88 static
from8toFLT(void * dst,const void * src)89 void from8toFLT(void* dst, const void* src)
90 {
91        *(cmsFloat32Number*)dst = (cmsFloat32Number) (*(cmsUInt8Number*)src) / 255.0f;
92 }
93 
94 static
from8toDBL(void * dst,const void * src)95 void from8toDBL(void* dst, const void* src)
96 {
97        *(cmsFloat64Number*)dst = (cmsFloat64Number) (*(cmsUInt8Number*)src) / 255.0;
98 }
99 
100 static
from8toHLF(void * dst,const void * src)101 void from8toHLF(void* dst, const void* src)
102 {
103 #ifndef CMS_NO_HALF_SUPPORT
104        cmsFloat32Number n = (*(cmsUInt8Number*)src) / 255.0f;
105        *(cmsUInt16Number*)dst = _cmsFloat2Half(n);
106 #else
107     cmsUNUSED_PARAMETER(dst);
108     cmsUNUSED_PARAMETER(src);
109 #endif
110 }
111 
112 // From 16
113 
114 static
from16to8(void * dst,const void * src)115 void from16to8(void* dst, const void* src)
116 {
117        cmsUInt16Number n = *(cmsUInt16Number*)src;
118        *(cmsUInt8Number*) dst = FROM_16_TO_8(n);
119 }
120 
121 static
from16SEto8(void * dst,const void * src)122 void from16SEto8(void* dst, const void* src)
123 {
124     cmsUInt16Number n = *(cmsUInt16Number*)src;
125     *(cmsUInt8Number*)dst = FROM_16_TO_8(CHANGE_ENDIAN(n));
126 }
127 
128 static
copy16(void * dst,const void * src)129 void copy16(void* dst, const void* src)
130 {
131        memmove(dst, src, 2);
132 }
133 
134 static
from16to16(void * dst,const void * src)135 void from16to16(void* dst, const void* src)
136 {
137     cmsUInt16Number n = *(cmsUInt16Number*)src;
138     *(cmsUInt16Number*)dst = CHANGE_ENDIAN(n);
139 }
140 
141 static
from16toFLT(void * dst,const void * src)142 void from16toFLT(void* dst, const void* src)
143 {
144        *(cmsFloat32Number*)dst = (*(cmsUInt16Number*)src) / 65535.0f;
145 }
146 
147 static
from16SEtoFLT(void * dst,const void * src)148 void from16SEtoFLT(void* dst, const void* src)
149 {
150     *(cmsFloat32Number*)dst = (CHANGE_ENDIAN(*(cmsUInt16Number*)src)) / 65535.0f;
151 }
152 
153 static
from16toDBL(void * dst,const void * src)154 void from16toDBL(void* dst, const void* src)
155 {
156        *(cmsFloat64Number*)dst = (cmsFloat64Number) (*(cmsUInt16Number*)src) / 65535.0;
157 }
158 
159 static
from16SEtoDBL(void * dst,const void * src)160 void from16SEtoDBL(void* dst, const void* src)
161 {
162     *(cmsFloat64Number*)dst = (cmsFloat64Number) (CHANGE_ENDIAN(*(cmsUInt16Number*)src)) / 65535.0;
163 }
164 
165 static
from16toHLF(void * dst,const void * src)166 void from16toHLF(void* dst, const void* src)
167 {
168 #ifndef CMS_NO_HALF_SUPPORT
169        cmsFloat32Number n = (*(cmsUInt16Number*)src) / 65535.0f;
170        *(cmsUInt16Number*)dst = _cmsFloat2Half(n);
171 #else
172     cmsUNUSED_PARAMETER(dst);
173     cmsUNUSED_PARAMETER(src);
174 #endif
175 }
176 
177 static
from16SEtoHLF(void * dst,const void * src)178 void from16SEtoHLF(void* dst, const void* src)
179 {
180 #ifndef CMS_NO_HALF_SUPPORT
181     cmsFloat32Number n = (CHANGE_ENDIAN(*(cmsUInt16Number*)src)) / 65535.0f;
182     *(cmsUInt16Number*)dst = _cmsFloat2Half(n);
183 #else
184     cmsUNUSED_PARAMETER(dst);
185     cmsUNUSED_PARAMETER(src);
186 #endif
187 }
188 // From Float
189 
190 static
fromFLTto8(void * dst,const void * src)191 void fromFLTto8(void* dst, const void* src)
192 {
193     cmsFloat32Number n = *(cmsFloat32Number*)src;
194     *(cmsUInt8Number*)dst = _cmsQuickSaturateByte(n * 255.0f);
195 }
196 
197 static
fromFLTto16(void * dst,const void * src)198 void fromFLTto16(void* dst, const void* src)
199 {
200     cmsFloat32Number n = *(cmsFloat32Number*)src;
201     *(cmsUInt16Number*)dst = _cmsQuickSaturateWord(n * 65535.0f);
202 }
203 
204 static
fromFLTto16SE(void * dst,const void * src)205 void fromFLTto16SE(void* dst, const void* src)
206 {
207     cmsFloat32Number n = *(cmsFloat32Number*)src;
208     cmsUInt16Number i = _cmsQuickSaturateWord(n * 65535.0f);
209 
210     *(cmsUInt16Number*)dst = CHANGE_ENDIAN(i);
211 }
212 
213 static
copy32(void * dst,const void * src)214 void copy32(void* dst, const void* src)
215 {
216     memmove(dst, src, sizeof(cmsFloat32Number));
217 }
218 
219 static
fromFLTtoDBL(void * dst,const void * src)220 void fromFLTtoDBL(void* dst, const void* src)
221 {
222     cmsFloat32Number n = *(cmsFloat32Number*)src;
223     *(cmsFloat64Number*)dst = (cmsFloat64Number)n;
224 }
225 
226 static
fromFLTtoHLF(void * dst,const void * src)227 void fromFLTtoHLF(void* dst, const void* src)
228 {
229 #ifndef CMS_NO_HALF_SUPPORT
230        cmsFloat32Number n = *(cmsFloat32Number*)src;
231        *(cmsUInt16Number*)dst = _cmsFloat2Half(n);
232 #else
233     cmsUNUSED_PARAMETER(dst);
234     cmsUNUSED_PARAMETER(src);
235 #endif
236 }
237 
238 
239 // From HALF
240 
241 static
fromHLFto8(void * dst,const void * src)242 void fromHLFto8(void* dst, const void* src)
243 {
244 #ifndef CMS_NO_HALF_SUPPORT
245        cmsFloat32Number n = _cmsHalf2Float(*(cmsUInt16Number*)src);
246        *(cmsUInt8Number*)dst = _cmsQuickSaturateByte(n * 255.0f);
247 #else
248     cmsUNUSED_PARAMETER(dst);
249     cmsUNUSED_PARAMETER(src);
250 #endif
251 
252 }
253 
254 static
fromHLFto16(void * dst,const void * src)255 void fromHLFto16(void* dst, const void* src)
256 {
257 #ifndef CMS_NO_HALF_SUPPORT
258        cmsFloat32Number n = _cmsHalf2Float(*(cmsUInt16Number*)src);
259        *(cmsUInt16Number*)dst = _cmsQuickSaturateWord(n * 65535.0f);
260 #else
261     cmsUNUSED_PARAMETER(dst);
262     cmsUNUSED_PARAMETER(src);
263 #endif
264 }
265 
266 static
fromHLFto16SE(void * dst,const void * src)267 void fromHLFto16SE(void* dst, const void* src)
268 {
269 #ifndef CMS_NO_HALF_SUPPORT
270     cmsFloat32Number n = _cmsHalf2Float(*(cmsUInt16Number*)src);
271     cmsUInt16Number i = _cmsQuickSaturateWord(n * 65535.0f);
272     *(cmsUInt16Number*)dst = CHANGE_ENDIAN(i);
273 #else
274     cmsUNUSED_PARAMETER(dst);
275     cmsUNUSED_PARAMETER(src);
276 #endif
277 }
278 
279 static
fromHLFtoFLT(void * dst,const void * src)280 void fromHLFtoFLT(void* dst, const void* src)
281 {
282 #ifndef CMS_NO_HALF_SUPPORT
283        *(cmsFloat32Number*)dst = _cmsHalf2Float(*(cmsUInt16Number*)src);
284 #else
285     cmsUNUSED_PARAMETER(dst);
286     cmsUNUSED_PARAMETER(src);
287 #endif
288 }
289 
290 static
fromHLFtoDBL(void * dst,const void * src)291 void fromHLFtoDBL(void* dst, const void* src)
292 {
293 #ifndef CMS_NO_HALF_SUPPORT
294        *(cmsFloat64Number*)dst = (cmsFloat64Number)_cmsHalf2Float(*(cmsUInt16Number*)src);
295 #else
296     cmsUNUSED_PARAMETER(dst);
297     cmsUNUSED_PARAMETER(src);
298 #endif
299 }
300 
301 // From double
302 static
fromDBLto8(void * dst,const void * src)303 void fromDBLto8(void* dst, const void* src)
304 {
305        cmsFloat64Number n = *(cmsFloat64Number*)src;
306        *(cmsUInt8Number*)dst = _cmsQuickSaturateByte(n * 255.0);
307 }
308 
309 static
fromDBLto16(void * dst,const void * src)310 void fromDBLto16(void* dst, const void* src)
311 {
312        cmsFloat64Number n = *(cmsFloat64Number*)src;
313        *(cmsUInt16Number*)dst = _cmsQuickSaturateWord(n * 65535.0f);
314 }
315 
316 static
fromDBLto16SE(void * dst,const void * src)317 void fromDBLto16SE(void* dst, const void* src)
318 {
319     cmsFloat64Number n = *(cmsFloat64Number*)src;
320     cmsUInt16Number  i = _cmsQuickSaturateWord(n * 65535.0f);
321     *(cmsUInt16Number*)dst = CHANGE_ENDIAN(i);
322 }
323 
324 static
fromDBLtoFLT(void * dst,const void * src)325 void fromDBLtoFLT(void* dst, const void* src)
326 {
327        cmsFloat64Number n = *(cmsFloat64Number*)src;
328        *(cmsFloat32Number*)dst = (cmsFloat32Number) n;
329 }
330 
331 static
fromDBLtoHLF(void * dst,const void * src)332 void fromDBLtoHLF(void* dst, const void* src)
333 {
334 #ifndef CMS_NO_HALF_SUPPORT
335        cmsFloat32Number n = (cmsFloat32Number) *(cmsFloat64Number*)src;
336        *(cmsUInt16Number*)dst = _cmsFloat2Half(n);
337 #else
338     cmsUNUSED_PARAMETER(dst);
339     cmsUNUSED_PARAMETER(src);
340 #endif
341 }
342 
343 static
copy64(void * dst,const void * src)344 void copy64(void* dst, const void* src)
345 {
346        memmove(dst, src, sizeof(cmsFloat64Number));
347 }
348 
349 
350 // Returns the position (x or y) of the formatter in the table of functions
351 static
FormatterPos(cmsUInt32Number frm)352 int FormatterPos(cmsUInt32Number frm)
353 {
354     cmsUInt32Number  b = T_BYTES(frm);
355 
356     if (b == 0 && T_FLOAT(frm))
357         return 5; // DBL
358 #ifndef CMS_NO_HALF_SUPPORT
359     if (b == 2 && T_FLOAT(frm))
360         return 3; // HLF
361 #endif
362     if (b == 4 && T_FLOAT(frm))
363         return 4; // FLT
364     if (b == 2 && !T_FLOAT(frm))
365     {
366         if (T_ENDIAN16(frm))
367             return 2; // 16SE
368         else
369             return 1; // 16
370     }
371     if (b == 1 && !T_FLOAT(frm))
372         return 0; // 8
373     return -1; // not recognized
374 }
375 
376 // Obtains an alpha-to-alpha function formatter
377 static
_cmsGetFormatterAlpha(cmsContext id,cmsUInt32Number in,cmsUInt32Number out)378 cmsFormatterAlphaFn _cmsGetFormatterAlpha(cmsContext id, cmsUInt32Number in, cmsUInt32Number out)
379 {
380 static cmsFormatterAlphaFn FormattersAlpha[6][6] = {
381 
382        /* from 8 */  { copy8,       from8to16,   from8to16SE,   from8toHLF,   from8toFLT,    from8toDBL    },
383        /* from 16*/  { from16to8,   copy16,      from16to16,    from16toHLF,  from16toFLT,   from16toDBL   },
384        /* from 16SE*/{ from16SEto8, from16to16,  copy16,        from16SEtoHLF,from16SEtoFLT, from16SEtoDBL },
385        /* from HLF*/ { fromHLFto8,  fromHLFto16, fromHLFto16SE, copy16,       fromHLFtoFLT,  fromHLFtoDBL  },
386        /* from FLT*/ { fromFLTto8,  fromFLTto16, fromFLTto16SE, fromFLTtoHLF, copy32,        fromFLTtoDBL  },
387        /* from DBL*/ { fromDBLto8,  fromDBLto16, fromDBLto16SE, fromDBLtoHLF, fromDBLtoFLT,  copy64 }};
388 
389         int in_n  = FormatterPos(in);
390         int out_n = FormatterPos(out);
391 
392         if (in_n < 0 || out_n < 0 || in_n > 5 || out_n > 5) {
393 
394                cmsSignalError(id, cmsERROR_UNKNOWN_EXTENSION, "Unrecognized alpha channel width");
395                return NULL;
396         }
397 
398         return FormattersAlpha[in_n][out_n];
399 }
400 
401 
402 
403 // This function computes the distance from each component to the next one in bytes.
404 static
ComputeIncrementsForChunky(cmsUInt32Number Format,cmsUInt32Number ComponentStartingOrder[],cmsUInt32Number ComponentPointerIncrements[])405 void ComputeIncrementsForChunky(cmsUInt32Number Format,
406                                 cmsUInt32Number ComponentStartingOrder[],
407                                 cmsUInt32Number ComponentPointerIncrements[])
408 {
409        cmsUInt32Number channels[cmsMAXCHANNELS];
410        cmsUInt32Number extra = T_EXTRA(Format);
411        cmsUInt32Number nchannels = T_CHANNELS(Format);
412        cmsUInt32Number total_chans = nchannels + extra;
413        cmsUInt32Number i;
414        cmsUInt32Number channelSize = trueBytesSize(Format);
415        cmsUInt32Number pixelSize = channelSize * total_chans;
416 
417 	   // Sanity check
418 	   if (total_chans <= 0 || total_chans >= cmsMAXCHANNELS)
419 		   return;
420 
421         memset(channels, 0, sizeof(channels));
422 
423        // Separation is independent of starting point and only depends on channel size
424        for (i = 0; i < extra; i++)
425               ComponentPointerIncrements[i] = pixelSize;
426 
427        // Handle do swap
428        for (i = 0; i < total_chans; i++)
429        {
430               if (T_DOSWAP(Format)) {
431                      channels[i] = total_chans - i - 1;
432               }
433               else {
434                      channels[i] = i;
435               }
436        }
437 
438        // Handle swap first (ROL of positions), example CMYK -> KCMY | 0123 -> 3012
439        if (T_SWAPFIRST(Format) && total_chans > 1) {
440 
441               cmsUInt32Number tmp = channels[0];
442               for (i = 0; i < total_chans-1; i++)
443                      channels[i] = channels[i + 1];
444 
445               channels[total_chans - 1] = tmp;
446        }
447 
448        // Handle size
449        if (channelSize > 1)
450               for (i = 0; i < total_chans; i++) {
451                      channels[i] *= channelSize;
452               }
453 
454        for (i = 0; i < extra; i++)
455               ComponentStartingOrder[i] = channels[i + nchannels];
456 }
457 
458 
459 
460 //  On planar configurations, the distance is the stride added to any non-negative
461 static
ComputeIncrementsForPlanar(cmsUInt32Number Format,cmsUInt32Number BytesPerPlane,cmsUInt32Number ComponentStartingOrder[],cmsUInt32Number ComponentPointerIncrements[])462 void ComputeIncrementsForPlanar(cmsUInt32Number Format,
463                                 cmsUInt32Number BytesPerPlane,
464                                 cmsUInt32Number ComponentStartingOrder[],
465                                 cmsUInt32Number ComponentPointerIncrements[])
466 {
467        cmsUInt32Number channels[cmsMAXCHANNELS];
468        cmsUInt32Number extra = T_EXTRA(Format);
469        cmsUInt32Number nchannels = T_CHANNELS(Format);
470        cmsUInt32Number total_chans = nchannels + extra;
471        cmsUInt32Number i;
472        cmsUInt32Number channelSize = trueBytesSize(Format);
473 
474        // Sanity check
475        if (total_chans <= 0 || total_chans >= cmsMAXCHANNELS)
476            return;
477 
478        memset(channels, 0, sizeof(channels));
479 
480        // Separation is independent of starting point and only depends on channel size
481        for (i = 0; i < extra; i++)
482               ComponentPointerIncrements[i] = channelSize;
483 
484        // Handle do swap
485        for (i = 0; i < total_chans; i++)
486        {
487               if (T_DOSWAP(Format)) {
488                      channels[i] = total_chans - i - 1;
489               }
490               else {
491                      channels[i] = i;
492               }
493        }
494 
495        // Handle swap first (ROL of positions), example CMYK -> KCMY | 0123 -> 3012
496        if (T_SWAPFIRST(Format) && total_chans > 0) {
497 
498               cmsUInt32Number tmp = channels[0];
499               for (i = 0; i < total_chans - 1; i++)
500                      channels[i] = channels[i + 1];
501 
502               channels[total_chans - 1] = tmp;
503        }
504 
505        // Handle size
506        for (i = 0; i < total_chans; i++) {
507               channels[i] *= BytesPerPlane;
508        }
509 
510        for (i = 0; i < extra; i++)
511               ComponentStartingOrder[i] = channels[i + nchannels];
512 }
513 
514 
515 
516 // Dispatcher por chunky and planar RGB
517 static
ComputeComponentIncrements(cmsUInt32Number Format,cmsUInt32Number BytesPerPlane,cmsUInt32Number ComponentStartingOrder[],cmsUInt32Number ComponentPointerIncrements[])518 void  ComputeComponentIncrements(cmsUInt32Number Format,
519                                  cmsUInt32Number BytesPerPlane,
520                                  cmsUInt32Number ComponentStartingOrder[],
521                                  cmsUInt32Number ComponentPointerIncrements[])
522 {
523        if (T_PLANAR(Format)) {
524 
525               ComputeIncrementsForPlanar(Format,  BytesPerPlane, ComponentStartingOrder, ComponentPointerIncrements);
526        }
527        else {
528               ComputeIncrementsForChunky(Format,  ComponentStartingOrder, ComponentPointerIncrements);
529        }
530 
531 }
532 
533 
534 
535 // Handles extra channels copying alpha if requested by the flags
_cmsHandleExtraChannels(_cmsTRANSFORM * p,const void * in,void * out,cmsUInt32Number PixelsPerLine,cmsUInt32Number LineCount,const cmsStride * Stride)536 void _cmsHandleExtraChannels(_cmsTRANSFORM* p, const void* in,
537                                                void* out,
538                                                cmsUInt32Number PixelsPerLine,
539                                                cmsUInt32Number LineCount,
540                                                const cmsStride* Stride)
541 {
542     cmsUInt32Number i, j, k;
543     cmsUInt32Number nExtra;
544     cmsUInt32Number SourceStartingOrder[cmsMAXCHANNELS];
545     cmsUInt32Number SourceIncrements[cmsMAXCHANNELS];
546     cmsUInt32Number DestStartingOrder[cmsMAXCHANNELS];
547     cmsUInt32Number DestIncrements[cmsMAXCHANNELS];
548 
549     cmsFormatterAlphaFn copyValueFn;
550 
551     // Make sure we need some copy
552     if (!(p->dwOriginalFlags & cmsFLAGS_COPY_ALPHA))
553         return;
554 
555     // Exit early if in-place color-management is occurring - no need to copy extra channels to themselves.
556     if (p->InputFormat == p->OutputFormat && in == out)
557         return;
558 
559     // Make sure we have same number of alpha channels. If not, just return as this should be checked at transform creation time.
560     nExtra = T_EXTRA(p->InputFormat);
561     if (nExtra != T_EXTRA(p->OutputFormat))
562         return;
563 
564     // Anything to do?
565     if (nExtra == 0)
566         return;
567 
568     // Compute the increments
569     ComputeComponentIncrements(p->InputFormat, Stride->BytesPerPlaneIn, SourceStartingOrder, SourceIncrements);
570     ComputeComponentIncrements(p->OutputFormat, Stride->BytesPerPlaneOut, DestStartingOrder, DestIncrements);
571 
572     // Check for conversions 8, 16, half, float, dbl
573     copyValueFn = _cmsGetFormatterAlpha(p->ContextID, p->InputFormat, p->OutputFormat);
574     if (copyValueFn == NULL)
575         return;
576 
577     if (nExtra == 1) { // Optimized routine for copying a single extra channel quickly
578 
579         cmsUInt8Number* SourcePtr;
580         cmsUInt8Number* DestPtr;
581 
582         cmsUInt32Number SourceStrideIncrement = 0;
583         cmsUInt32Number DestStrideIncrement = 0;
584 
585         // The loop itself
586         for (i = 0; i < LineCount; i++) {
587 
588             // Prepare pointers for the loop
589             SourcePtr = (cmsUInt8Number*)in + SourceStartingOrder[0] + SourceStrideIncrement;
590             DestPtr = (cmsUInt8Number*)out + DestStartingOrder[0] + DestStrideIncrement;
591 
592             for (j = 0; j < PixelsPerLine; j++) {
593 
594                 copyValueFn(DestPtr, SourcePtr);
595 
596                 SourcePtr += SourceIncrements[0];
597                 DestPtr += DestIncrements[0];
598             }
599 
600             SourceStrideIncrement += Stride->BytesPerLineIn;
601             DestStrideIncrement += Stride->BytesPerLineOut;
602         }
603 
604     }
605     else { // General case with more than one extra channel
606 
607         cmsUInt8Number* SourcePtr[cmsMAXCHANNELS];
608         cmsUInt8Number* DestPtr[cmsMAXCHANNELS];
609 
610         cmsUInt32Number SourceStrideIncrements[cmsMAXCHANNELS];
611         cmsUInt32Number DestStrideIncrements[cmsMAXCHANNELS];
612 
613         memset(SourceStrideIncrements, 0, sizeof(SourceStrideIncrements));
614         memset(DestStrideIncrements, 0, sizeof(DestStrideIncrements));
615 
616         // The loop itself
617         for (i = 0; i < LineCount; i++) {
618 
619             // Prepare pointers for the loop
620             for (j = 0; j < nExtra; j++) {
621 
622                 SourcePtr[j] = (cmsUInt8Number*)in + SourceStartingOrder[j] + SourceStrideIncrements[j];
623                 DestPtr[j] = (cmsUInt8Number*)out + DestStartingOrder[j] + DestStrideIncrements[j];
624             }
625 
626             for (j = 0; j < PixelsPerLine; j++) {
627 
628                 for (k = 0; k < nExtra; k++) {
629 
630                     copyValueFn(DestPtr[k], SourcePtr[k]);
631 
632                     SourcePtr[k] += SourceIncrements[k];
633                     DestPtr[k] += DestIncrements[k];
634                 }
635             }
636 
637             for (j = 0; j < nExtra; j++) {
638 
639                 SourceStrideIncrements[j] += Stride->BytesPerLineIn;
640                 DestStrideIncrements[j] += Stride->BytesPerLineOut;
641             }
642         }
643     }
644 }
645 
646 
647