1 
2 
3 #include "blend.h"
4 
5 // TPoint structure
6 #include "tgeometry.h"
7 
8 // Palette - pixel functions
9 #include "tpalette.h"
10 #include "tpixelutils.h"
11 
12 #include <vector>
13 #include <memory>
14 
15 //=================================================================================
16 
17 //===========================
18 //    Blur pattern class
19 //---------------------------
20 
21 //! The BlurPattern class delineates the idea of a 'blur'
22 //! pattern from a number of random sample points taken
23 //! in a neighbourhood of the blurred pixel. The pattern
24 //! develops in a radial manner if specified, so that possible
25 //! 'obstacles' in the blur can be identified.
26 
27 class BlurPattern {
28 public:
29   typedef std::vector<TPoint> SamplePath;
30 
31   std::vector<TPoint> m_samples;
32   std::vector<SamplePath> m_samplePaths;
33 
34   BlurPattern(double distance, unsigned int samplesCount, bool radial);
~BlurPattern()35   ~BlurPattern() {}
36 };
37 
38 //---------------------------------------------------------------------------------
39 
40 // Builds the specified number of samples count, inside the specified distance
41 // from the origin. If the pattern is radial, paths to the samples points are
42 // calculated.
BlurPattern(double distance,unsigned int samplesCount,bool radial)43 BlurPattern::BlurPattern(double distance, unsigned int samplesCount,
44                          bool radial) {
45   const double randFactor = 2.0 * distance / RAND_MAX;
46 
47   m_samples.resize(samplesCount);
48 
49   // Build the samples
50   unsigned int i;
51   for (i = 0; i < samplesCount; ++i) {
52     // NOTE: The following method ensures a perfectly flat probability
53     // distribution.
54 
55     TPoint candidatePoint(tround(rand() * randFactor - distance),
56                           tround(rand() * randFactor - distance));
57     double distanceSq = sq(distance);
58     while (sq(candidatePoint.x) + sq(candidatePoint.y) > distanceSq)
59       candidatePoint = TPoint(tround(rand() * randFactor - distance),
60                               tround(rand() * randFactor - distance));
61 
62     m_samples[i] = candidatePoint;
63   }
64 
65   m_samplePaths.resize(samplesCount);
66 
67   // If necessary, build the paths
68   if (radial) {
69     for (i = 0; i < samplesCount; ++i) {
70       TPoint &sample = m_samples[i];
71 
72       int l = std::max(abs(sample.x), abs(sample.y));
73 
74       m_samplePaths[i].reserve(l);
75 
76       double dx = sample.x / (double)l;
77       double dy = sample.y / (double)l;
78 
79       double x, y;
80       int j;
81       for (j = 0, x = dx, y = dy; j < l; x += dx, y += dy, ++j)
82         m_samplePaths[i].push_back(TPoint(tround(x), tround(y)));
83     }
84   }
85 }
86 
87 //=================================================================================
88 
89 //=================================
90 //    Raster Selection classes
91 //---------------------------------
92 
93 struct SelectionData {
94   UCHAR m_selectedInk : 1;
95   UCHAR m_selectedPaint : 1;
96   UCHAR m_pureInk : 1;
97   UCHAR m_purePaint : 1;
98 };
99 
100 //=================================================================================
101 
102 // Implements an array of selection infos using bitfields. It seems that
103 // bitfields are more optimized than
104 // using raw bits and bitwise operators, and use just the double of the space
105 // required with bit arrays.
106 class SelectionArrayPtr {
107   std::unique_ptr<SelectionData[]> m_buffer;
108 
109 public:
allocate(unsigned int count)110   inline void allocate(unsigned int count) {
111     m_buffer.reset(new SelectionData[count]);
112     memset(m_buffer.get(), 0, count * sizeof(SelectionData));
113   }
114 
destroy()115   inline void destroy() { m_buffer.reset(); }
116 
data() const117   inline SelectionData *data() const { return m_buffer.get(); }
118 
data()119   inline SelectionData *data() { return m_buffer.get(); }
120 };
121 
122 //=================================================================================
123 
124 // Bitmap used to store blend color selections and pure color information.
125 class SelectionRaster {
126   SelectionArrayPtr m_selection;
127 
128   int m_wrap;
129 
130 public:
131   SelectionRaster(TRasterCM32P cm);
132 
133   void updateSelection(TRasterCM32P cm, const BlendParam &param);
134 
data() const135   SelectionData *data() const { return m_selection.data(); }
136 
data()137   SelectionData *data() { return m_selection.data(); }
138 
destroy()139   void destroy() { m_selection.destroy(); }
140 
isSelectedInk(int xy) const141   bool isSelectedInk(int xy) const {
142     return (m_selection.data() + xy)->m_selectedInk;
143   }
144 
isSelectedInk(int x,int y) const145   bool isSelectedInk(int x, int y) const {
146     return isSelectedInk(x + y * m_wrap);
147   }
148 
isSelectedPaint(int xy) const149   bool isSelectedPaint(int xy) const {
150     return (m_selection.data() + xy)->m_selectedPaint;
151   }
152 
isSelectedPaint(int x,int y) const153   bool isSelectedPaint(int x, int y) const {
154     return isSelectedPaint(x + y * m_wrap);
155   }
156 
isPureInk(int xy) const157   bool isPureInk(int xy) const { return (m_selection.data() + xy)->m_pureInk; }
158 
isPureInk(int x,int y) const159   bool isPureInk(int x, int y) const { return isPureInk(x + y * m_wrap); }
160 
isPurePaint(int xy) const161   bool isPurePaint(int xy) const {
162     return (m_selection.data() + xy)->m_purePaint;
163   }
164 
isPurePaint(int x,int y) const165   bool isPurePaint(int x, int y) const { return isPurePaint(x + y * m_wrap); }
166 
isToneColor(int xy) const167   bool isToneColor(int xy) const { return !(isPureInk(xy) || isPurePaint(xy)); }
168 
isToneColor(int x,int y) const169   bool isToneColor(int x, int y) const { return isToneColor(x + y * m_wrap); }
170 };
171 
172 //---------------------------------------------------------------------------------
173 
linearSearch(const int * v,unsigned int vSize,int k)174 inline UCHAR linearSearch(const int *v, unsigned int vSize, int k) {
175   const int *vEnd = v + vSize;
176   for (; v < vEnd; ++v)
177     if (*v == k) return 1;
178   return 0;
179 }
180 
181 //---------------------------------------------------------------------------------
182 
183 // I've seen the std::binary_search go particularly slow... perhaps it was the
184 // debug mode,
185 // but I guess this is the fastest version possible.
binarySearch(const int * v,unsigned int vSize,int k)186 inline UCHAR binarySearch(const int *v, unsigned int vSize, int k) {
187   // NOTE: v.size() > 0 due to external restrictions. See SelectionRaster's
188   // constructor.
189 
190   int a = -1, b, c = vSize;
191 
192   for (b = c >> 1; b != a; b = (a + c) >> 1) {
193     if (v[b] == k)
194       return 1;
195     else if (k < v[b])
196       c = b;
197     else
198       a = b;
199   }
200 
201   return 0;
202 }
203 
204 //---------------------------------------------------------------------------------
205 
SelectionRaster(TRasterCM32P cm)206 SelectionRaster::SelectionRaster(TRasterCM32P cm) {
207   unsigned int lx = cm->getLx(), ly = cm->getLy(), wrap = cm->getWrap();
208   unsigned int size = lx * ly;
209 
210   m_wrap = lx;
211 
212   m_selection.allocate(size);
213 
214   cm->lock();
215   TPixelCM32 *pix, *pixBegin = (TPixelCM32 *)cm->getRawData();
216 
217   SelectionData *selData = data();
218 
219   unsigned int i, j;
220   for (i = 0; i < ly; ++i) {
221     pix = pixBegin + i * wrap;
222     for (j = 0; j < lx; ++j, ++pix, ++selData) {
223       selData->m_pureInk   = pix->getTone() == 0;
224       selData->m_purePaint = pix->getTone() == 255;
225     }
226   }
227 
228   cm->unlock();
229 }
230 
231 //---------------------------------------------------------------------------------
232 
updateSelection(TRasterCM32P cm,const BlendParam & param)233 void SelectionRaster::updateSelection(TRasterCM32P cm,
234                                       const BlendParam &param) {
235   // Make a hard copy of color indexes. We do so since we absolutely prefer
236   // having them SORTED!
237   std::vector<int> cIndexes = param.colorsIndexes;
238   std::sort(cIndexes.begin(), cIndexes.end());
239 
240   unsigned int lx = cm->getLx(), ly = cm->getLy(), wrap = cm->getWrap();
241 
242   // Scan each cm pixel, looking if its ink or paint is in param's colorIndexes.
243   cm->lock();
244   TPixelCM32 *pix, *pixBegin = (TPixelCM32 *)cm->getRawData();
245 
246   SelectionData *selData = data();
247 
248   const int *v =
249       &cIndexes[0];  // NOTE: cIndexes.size() > 0 due to external check.
250   unsigned int vSize = cIndexes.size();
251   unsigned int i, j;
252 
253   // NOTE: It seems that linear searches are definitely best for small color
254   // indexes.
255   if (vSize > 50) {
256     for (i = 0; i < ly; ++i) {
257       pix = pixBegin + i * wrap;
258       for (j = 0; j < lx; ++j, ++pix, ++selData) {
259         selData->m_selectedInk   = binarySearch(v, vSize, pix->getInk());
260         selData->m_selectedPaint = binarySearch(v, vSize, pix->getPaint());
261       }
262     }
263   } else {
264     for (i = 0; i < ly; ++i) {
265       pix = pixBegin + i * wrap;
266       for (j = 0; j < lx; ++j, ++pix, ++selData) {
267         selData->m_selectedInk   = linearSearch(v, vSize, pix->getInk());
268         selData->m_selectedPaint = linearSearch(v, vSize, pix->getPaint());
269       }
270     }
271   }
272 
273   cm->unlock();
274 }
275 
276 //=================================================================================
277 
278 //========================
279 //    Blend functions
280 //------------------------
281 
282 // Pixel whose channels are doubles. Used to store intermediate values for pixel
283 // blending.
284 struct DoubleRGBMPixel {
285   double r;
286   double g;
287   double b;
288   double m;
289 
DoubleRGBMPixelDoubleRGBMPixel290   DoubleRGBMPixel() : r(0.0), g(0.0), b(0.0), m(0.0) {}
291 };
292 
293 //---------------------------------------------------------------------------------
294 
295 const double maxTone = TPixelCM32::getMaxTone();
296 
297 // Returns the ink & paint convex factors associated with passed tone.
getFactors(int tone,double & inkFactor,double & paintFactor)298 inline void getFactors(int tone, double &inkFactor, double &paintFactor) {
299   paintFactor = tone / maxTone;
300   inkFactor   = (1.0 - paintFactor);
301 }
302 
303 //---------------------------------------------------------------------------------
304 
305 // Copies the cmIn paint and ink colors to the output rasters.
buildLayers(const TRasterCM32P & cmIn,const std::vector<TPixel32> & palColors,TRaster32P & inkRaster,TRaster32P & paintRaster)306 static void buildLayers(const TRasterCM32P &cmIn,
307                         const std::vector<TPixel32> &palColors,
308                         TRaster32P &inkRaster, TRaster32P &paintRaster) {
309   // Separate cmIn by copying the ink & paint colors directly to the layer
310   // rasters.
311   TPixelCM32 *cmPix, *cmBegin = (TPixelCM32 *)cmIn->getRawData();
312   TPixel32 *inkPix   = (TPixel32 *)inkRaster->getRawData();
313   TPixel32 *paintPix = (TPixel32 *)paintRaster->getRawData();
314 
315   unsigned int i, j, lx = cmIn->getLx(), ly = cmIn->getLy(),
316                      wrap = cmIn->getWrap();
317   for (i = 0; i < ly; ++i) {
318     cmPix = cmBegin + i * wrap;
319 
320     for (j = 0; j < lx; ++j, ++cmPix, ++inkPix, ++paintPix) {
321       *inkPix   = palColors[cmPix->getInk()];
322       *paintPix = palColors[cmPix->getPaint()];
323 
324       // Should pure colors be checked...?
325     }
326   }
327 }
328 
329 //---------------------------------------------------------------------------------
330 
331 // Returns true or false whether the selectedColor is the only selectable color
332 // in the neighbourhood. If so, the blend copies it to the output layer pixel
333 // directly.
isFlatNeighbourhood(int selectedColor,const TRasterCM32P & cmIn,const TPoint & pos,const SelectionRaster & selRas,const BlurPattern & blurPattern)334 inline bool isFlatNeighbourhood(int selectedColor, const TRasterCM32P &cmIn,
335                                 const TPoint &pos,
336                                 const SelectionRaster &selRas,
337                                 const BlurPattern &blurPattern) {
338   TPixelCM32 &pix = cmIn->pixels(pos.y)[pos.x];
339   int lx = cmIn->getLx(), ly = cmIn->getLy();
340   unsigned int xy;
341 
342   TPoint samplePix;
343 
344   const TPoint *samplePoint =
345       blurPattern.m_samples.empty() ? 0 : &blurPattern.m_samples[0];
346 
347   // Read the samples to determine if they only have posSelectedColor
348   unsigned int i, samplesCount = blurPattern.m_samples.size();
349   for (i = 0; i < samplesCount; ++i, ++samplePoint) {
350     // Make sure the sample is inside the image
351     samplePix.x = pos.x + samplePoint->x;
352     samplePix.y = pos.y + samplePoint->y;
353 
354     xy = samplePix.x + lx * samplePix.y;
355 
356     if (samplePix.x < 0 || samplePix.y < 0 || samplePix.x >= lx ||
357         samplePix.y >= ly)
358       continue;
359 
360     if (!selRas.isPurePaint(xy) && selRas.isSelectedInk(xy))
361       if (cmIn->pixels(samplePix.y)[samplePix.x].getInk() != selectedColor)
362         return false;
363 
364     if (!selRas.isPureInk(xy) && selRas.isSelectedPaint(xy))
365       if (cmIn->pixels(samplePix.y)[samplePix.x].getPaint() != selectedColor)
366         return false;
367   }
368 
369   return true;
370 }
371 
372 //---------------------------------------------------------------------------------
373 
374 // Calculates the estimate of blend selection in the neighbourhood specified by
375 // blurPattern.
addSamples(const TRasterCM32P & cmIn,const TPoint & pos,const TRaster32P & inkRas,const TRaster32P & paintRas,const SelectionRaster & selRas,const BlurPattern & blurPattern,DoubleRGBMPixel & pixSum,double & factorsSum)376 inline void addSamples(const TRasterCM32P &cmIn, const TPoint &pos,
377                        const TRaster32P &inkRas, const TRaster32P &paintRas,
378                        const SelectionRaster &selRas,
379                        const BlurPattern &blurPattern, DoubleRGBMPixel &pixSum,
380                        double &factorsSum) {
381   double inkFactor, paintFactor;
382   unsigned int xy, j, l;
383   int lx = cmIn->getLx(), ly = cmIn->getLy();
384   TPixel32 *color;
385   TPoint samplePos, pathPos;
386 
387   const TPoint *samplePoint =
388       blurPattern.m_samples.empty() ? 0 : &blurPattern.m_samples[0];
389   const TPoint *pathPoint;
390 
391   unsigned int i, blurSamplesCount = blurPattern.m_samples.size();
392   for (i = 0; i < blurSamplesCount; ++i, ++samplePoint) {
393     // Add each samples contribute to the sum
394     samplePos.x = pos.x + samplePoint->x;
395     samplePos.y = pos.y + samplePoint->y;
396     if (samplePos.x < 0 || samplePos.y < 0 || samplePos.x >= lx ||
397         samplePos.y >= ly)
398       continue;
399 
400     // Ensure that each pixel on the sample's path (if any) is selected
401     l         = blurPattern.m_samplePaths[i].size();
402     pathPoint = blurPattern.m_samplePaths[i].empty()
403                     ? 0
404                     : &blurPattern.m_samplePaths[i][0];
405     for (j = 0; j < l; ++j, ++pathPoint) {
406       pathPos.x = pos.x + pathPoint->x;
407       pathPos.y = pos.y + pathPoint->y;
408       xy        = pathPos.x + lx * pathPos.y;
409 
410       if (!(selRas.isPurePaint(xy) || selRas.isSelectedInk(xy))) break;
411 
412       if (!(selRas.isPureInk(xy) || selRas.isSelectedPaint(xy))) break;
413     }
414 
415     if (j < l) continue;
416 
417     xy = samplePos.x + lx * samplePos.y;
418 
419     if (selRas.isSelectedInk(xy) && !selRas.isPurePaint(xy)) {
420       getFactors(cmIn->pixels(samplePos.y)[samplePos.x].getTone(), inkFactor,
421                  paintFactor);
422 
423       color = &inkRas->pixels(samplePos.y)[samplePos.x];
424       pixSum.r += inkFactor * color->r;
425       pixSum.g += inkFactor * color->g;
426       pixSum.b += inkFactor * color->b;
427       pixSum.m += inkFactor * color->m;
428       factorsSum += inkFactor;
429     }
430 
431     if (selRas.isSelectedPaint(xy) && !selRas.isPureInk(xy)) {
432       getFactors(cmIn->pixels(samplePos.y)[samplePos.x].getTone(), inkFactor,
433                  paintFactor);
434 
435       color = &paintRas->pixels(samplePos.y)[samplePos.x];
436       pixSum.r += paintFactor * color->r;
437       pixSum.g += paintFactor * color->g;
438       pixSum.b += paintFactor * color->b;
439       pixSum.m += paintFactor * color->m;
440       factorsSum += paintFactor;
441     }
442   }
443 }
444 
445 //---------------------------------------------------------------------------------
446 
447 typedef std::pair<TRaster32P, TRaster32P> RGBMRasterPair;
448 
449 //---------------------------------------------------------------------------------
450 
451 // Performs a single color blending. This function can be repeatedly invoked to
452 // perform multiple color blending.
doBlend(const TRasterCM32P & cmIn,RGBMRasterPair & inkLayer,RGBMRasterPair & paintLayer,const SelectionRaster & selRas,const std::vector<BlurPattern> & blurPatterns)453 inline void doBlend(const TRasterCM32P &cmIn, RGBMRasterPair &inkLayer,
454                     RGBMRasterPair &paintLayer, const SelectionRaster &selRas,
455                     const std::vector<BlurPattern> &blurPatterns) {
456   // Declare some vars
457   unsigned int blurPatternsCount = blurPatterns.size();
458   int lx = cmIn->getLx(), ly = cmIn->getLy();
459   double totalFactor;
460 
461   TPixelCM32 *cmPix, *cmBegin = (TPixelCM32 *)cmIn->getRawData();
462 
463   TPixel32 *inkIn    = (TPixel32 *)inkLayer.first->getRawData(),
464            *inkOut   = (TPixel32 *)inkLayer.second->getRawData(),
465            *paintIn  = (TPixel32 *)paintLayer.first->getRawData(),
466            *paintOut = (TPixel32 *)paintLayer.second->getRawData();
467 
468   const BlurPattern *blurPattern, *blurPatternsBegin = &blurPatterns[0];
469   bool builtSamples = false;
470 
471   DoubleRGBMPixel samplesSum;
472 
473   // For every cmIn pixel
474   TPoint pos;
475   SelectionData *selData = selRas.data();
476   cmPix                  = cmBegin;
477   for (pos.y = 0; pos.y < ly;
478        ++pos.y, cmPix = cmBegin + pos.y * cmIn->getWrap())
479     for (pos.x = 0; pos.x < lx; ++pos.x, ++inkIn, ++inkOut, ++paintIn,
480         ++paintOut, ++selData, ++cmPix) {
481       blurPattern = blurPatternsBegin + (rand() % blurPatternsCount);
482 
483       // Build the ink blend color
484       if (!selData->m_purePaint && selData->m_selectedInk) {
485         if (!builtSamples) {
486           // Build samples contributes
487           totalFactor  = 1.0;
488           samplesSum.r = samplesSum.g = samplesSum.b = samplesSum.m = 0.0;
489 
490           if (!isFlatNeighbourhood(cmPix->getInk(), cmIn, pos, selRas,
491                                    *blurPattern))
492             addSamples(cmIn, pos, inkLayer.first, paintLayer.first, selRas,
493                        *blurPattern, samplesSum, totalFactor);
494 
495           builtSamples = true;
496         }
497 
498         // Output the blended pixel
499         inkOut->r = (samplesSum.r + inkIn->r) / totalFactor;
500         inkOut->g = (samplesSum.g + inkIn->g) / totalFactor;
501         inkOut->b = (samplesSum.b + inkIn->b) / totalFactor;
502         inkOut->m = (samplesSum.m + inkIn->m) / totalFactor;
503       } else {
504         // If the color is not blended, then just copy the old layer pixel
505         *inkOut = *inkIn;
506       }
507 
508       // Build the paint blend color
509       if (!selData->m_pureInk && selData->m_selectedPaint) {
510         if (!builtSamples) {
511           // Build samples contributes
512           totalFactor  = 1.0;
513           samplesSum.r = samplesSum.g = samplesSum.b = samplesSum.m = 0.0;
514 
515           if (!isFlatNeighbourhood(cmPix->getPaint(), cmIn, pos, selRas,
516                                    *blurPattern))
517             addSamples(cmIn, pos, inkLayer.first, paintLayer.first, selRas,
518                        *blurPattern, samplesSum, totalFactor);
519 
520           builtSamples = true;
521         }
522 
523         // Output the blended pixel
524         paintOut->r = (samplesSum.r + paintIn->r) / totalFactor;
525         paintOut->g = (samplesSum.g + paintIn->g) / totalFactor;
526         paintOut->b = (samplesSum.b + paintIn->b) / totalFactor;
527         paintOut->m = (samplesSum.m + paintIn->m) / totalFactor;
528       } else {
529         // If the color is not blended, then just copy the old layer pixel
530         *paintOut = *paintIn;
531       }
532 
533       builtSamples = false;
534     }
535 }
536 
537 //---------------------------------------------------------------------------------
538 
539 typedef std::vector<BlurPattern> BlurPatternContainer;
540 
541 //---------------------------------------------------------------------------------
542 
543 /*! This function performs a group of <a> spatial color blending <\a> operations
544    on Toonz Images.
545     The BlendParam structure stores the blend options recognized by this
546    function; it includes
547     a list of the palette indexes involved in the blend operation, plus:
548     \li \b Intensity represents the \a radius of the blur operation between
549    blend colors.
550     \li \b Smoothness is the number of samples per pixel used to approximate the
551    blur.
552     <li> <b> Stop at Contour <\b> specifies if lines from pixels to neighbouring
553    samples
554          should not trespass color indexes not included in the blend operation
555    <\li>
556     The succession of input blend parameters are applied in the order.
557 */
558 
559 template <typename PIXEL>
blend(TToonzImageP ti,TRasterPT<PIXEL> rasOut,const std::vector<BlendParam> & params)560 void blend(TToonzImageP ti, TRasterPT<PIXEL> rasOut,
561            const std::vector<BlendParam> &params) {
562   assert(ti->getRaster()->getSize() == rasOut->getSize());
563 
564   // Extract the interesting raster. It should be the savebox of passed cmap,
565   // plus - if
566   // some param has the 0 index as blending color - the intensity of that blend
567   // param.
568   unsigned int i, j;
569   TRect saveBox(ti->getSavebox());
570 
571   int enlargement = 0;
572   for (i = 0; i < params.size(); ++i)
573     for (j = 0; j < params[i].colorsIndexes.size(); ++j)
574       if (params[i].colorsIndexes[j] == 0)
575         enlargement = std::max(enlargement, tceil(params[i].intensity));
576   saveBox           = saveBox.enlarge(enlargement);
577 
578   TRasterCM32P cmIn(ti->getRaster()->extract(saveBox));
579   TRasterPT<PIXEL> rasOutExtract = rasOut->extract(saveBox);
580 
581   // Ensure that cmIn and rasOut have the same size
582   unsigned int lx = cmIn->getLx(), ly = cmIn->getLy();
583 
584   // Build the pure colors infos
585   SelectionRaster selectionRaster(cmIn);
586 
587   // Now, build a little group of BlurPatterns - and for each, one for passed
588   // param.
589   // A small number of patterns per param is needed to make the pattern look not
590   // ever the same.
591   const int blurPatternsPerParam = 10;
592   std::vector<BlurPatternContainer> blurGroup(params.size());
593 
594   for (i = 0; i < params.size(); ++i) {
595     BlurPatternContainer &blurContainer = blurGroup[i];
596     blurContainer.reserve(blurPatternsPerParam);
597 
598     for (j = 0; j < blurPatternsPerParam; ++j)
599       blurContainer.push_back(BlurPattern(
600           params[i].intensity, params[i].smoothness, params[i].stopAtCountour));
601   }
602 
603   // Build the palette
604   TPalette *palette = ti->getPalette();
605   std::vector<TPixel32> paletteColors;
606   paletteColors.resize(palette->getStyleCount());
607   for (i             = 0; i < paletteColors.size(); ++i)
608     paletteColors[i] = premultiply(palette->getStyle(i)->getAverageColor());
609 
610   // Build the 4 auxiliary rasters for the blending procedure: they are ink /
611   // paint versus input / output in the blend.
612   // The output raster is reused to spare some memory - it should be, say, the
613   // inkLayer's second at the end of the overall
614   // blending procedure. It could be the first, without the necessity of
615   // clearing it before blending the layers, but things
616   // get more complicated when PIXEL is TPixel64...
617   RGBMRasterPair inkLayer, paintLayer;
618 
619   TRaster32P rasOut32P_1(lx, ly, lx, (TPixel32 *)rasOut->getRawData(), false);
620   inkLayer.first  = (params.size() % 2) ? rasOut32P_1 : TRaster32P(lx, ly);
621   inkLayer.second = (params.size() % 2) ? TRaster32P(lx, ly) : rasOut32P_1;
622 
623   if (PIXEL::maxChannelValue >= TPixel64::maxChannelValue) {
624     TRaster32P rasOut32P_2(lx, ly, lx,
625                            ((TPixel32 *)rasOut->getRawData()) + lx * ly, false);
626     paintLayer.first  = (params.size() % 2) ? rasOut32P_2 : TRaster32P(lx, ly);
627     paintLayer.second = (params.size() % 2) ? TRaster32P(lx, ly) : rasOut32P_2;
628   } else {
629     paintLayer.first  = TRaster32P(lx, ly);
630     paintLayer.second = TRaster32P(lx, ly);
631   }
632 
633   inkLayer.first->clear();
634   inkLayer.second->clear();
635   paintLayer.first->clear();
636   paintLayer.second->clear();
637 
638   // Now, we have to perform the blur of each of the cm's pixels.
639   cmIn->lock();
640   rasOut->lock();
641 
642   inkLayer.first->lock();
643   inkLayer.second->lock();
644   paintLayer.first->lock();
645   paintLayer.second->lock();
646 
647   // Convert the initial cmIn to fullcolor ink - paint layers
648   buildLayers(cmIn, paletteColors, inkLayer.first, paintLayer.first);
649 
650   // Perform the blend on separated ink - paint layers
651   for (i = 0; i < params.size(); ++i) {
652     if (params[i].colorsIndexes.size() == 0) continue;
653 
654     selectionRaster.updateSelection(cmIn, params[i]);
655     doBlend(cmIn, inkLayer, paintLayer, selectionRaster, blurGroup[i]);
656 
657     std::swap(inkLayer.first, inkLayer.second);
658     std::swap(paintLayer.first, paintLayer.second);
659   }
660 
661   // Release the unnecessary rasters
662   inkLayer.second->unlock();
663   paintLayer.second->unlock();
664   inkLayer.second   = TRaster32P();
665   paintLayer.second = TRaster32P();
666 
667   // Clear rasOut - since it was reused to spare space...
668   rasOut->clear();
669 
670   // Add the ink & paint layers on the output raster
671   double PIXELmaxChannelValue = PIXEL::maxChannelValue;
672   double toPIXELFactor =
673       PIXELmaxChannelValue / (double)TPixel32::maxChannelValue;
674   double inkFactor, paintFactor;
675   TPoint pos;
676 
677   PIXEL *outPix, *outBegin    = (PIXEL *)rasOutExtract->getRawData();
678   TPixelCM32 *cmPix, *cmBegin = (TPixelCM32 *)cmIn->getRawData();
679   int wrap = rasOutExtract->getWrap();
680 
681   TPixel32 *inkPix   = (TPixel32 *)inkLayer.first->getRawData();
682   TPixel32 *paintPix = (TPixel32 *)paintLayer.first->getRawData();
683 
684   for (i = 0; i < ly; ++i) {
685     outPix = outBegin + wrap * i;
686     cmPix  = cmBegin + wrap * i;
687     for (j = 0; j < lx; ++j, ++outPix, ++cmPix, ++inkPix, ++paintPix) {
688       getFactors(cmPix->getTone(), inkFactor, paintFactor);
689 
690       outPix->r = tcrop(
691           toPIXELFactor * (inkFactor * inkPix->r + paintFactor * paintPix->r),
692           0.0, PIXELmaxChannelValue);
693       outPix->g = tcrop(
694           toPIXELFactor * (inkFactor * inkPix->g + paintFactor * paintPix->g),
695           0.0, PIXELmaxChannelValue);
696       outPix->b = tcrop(
697           toPIXELFactor * (inkFactor * inkPix->b + paintFactor * paintPix->b),
698           0.0, PIXELmaxChannelValue);
699       outPix->m = tcrop(
700           toPIXELFactor * (inkFactor * inkPix->m + paintFactor * paintPix->m),
701           0.0, PIXELmaxChannelValue);
702     }
703   }
704 
705   inkLayer.first->unlock();
706   paintLayer.first->unlock();
707 
708   cmIn->unlock();
709   rasOut->unlock();
710 
711   // Destroy the auxiliary bitmaps
712   selectionRaster.destroy();
713 }
714 
715 template void blend<TPixel32>(TToonzImageP cmIn, TRasterPT<TPixel32> rasOut,
716                               const std::vector<BlendParam> &params);
717 template void blend<TPixel64>(TToonzImageP cmIn, TRasterPT<TPixel64> rasOut,
718                               const std::vector<BlendParam> &params);
719