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 ¶m);
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 ¶m) {
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> ¶ms) {
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> ¶ms);
717 template void blend<TPixel64>(TToonzImageP cmIn, TRasterPT<TPixel64> rasOut,
718 const std::vector<BlendParam> ¶ms);
719