| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class BlurPattern |
| { |
| public: |
| typedef std::vector<TPoint> SamplePath; |
| |
| std::vector<TPoint> m_samples; |
| std::vector<SamplePath> m_samplePaths; |
| |
| BlurPattern(double distance, unsigned int samplesCount, bool radial); |
| ~BlurPattern() {} |
| }; |
| |
| |
| |
| |
| |
| |
| BlurPattern::BlurPattern(double distance, unsigned int samplesCount, bool radial) |
| { |
| const double randFactor = 2.0 * distance / RAND_MAX; |
| |
| m_samples.resize(samplesCount); |
| |
| |
| unsigned int i; |
| for (i = 0; i < samplesCount; ++i) { |
| |
| |
| TPoint candidatePoint(tround(rand() * randFactor - distance), tround(rand() * randFactor - distance)); |
| double distanceSq = sq(distance); |
| while (sq(candidatePoint.x) + sq(candidatePoint.y) > distanceSq) |
| candidatePoint = TPoint(tround(rand() * randFactor - distance), tround(rand() * randFactor - distance)); |
| |
| m_samples[i] = candidatePoint; |
| } |
| |
| m_samplePaths.resize(samplesCount); |
| |
| |
| if (radial) { |
| for (i = 0; i < samplesCount; ++i) { |
| TPoint &sample = m_samples[i]; |
| |
| int l = tmax(abs(sample.x), abs(sample.y)); |
| |
| m_samplePaths[i].reserve(l); |
| |
| double dx = sample.x / (double)l; |
| double dy = sample.y / (double)l; |
| |
| double x, y; |
| int j; |
| for (j = 0, x = dx, y = dy; j < l; x += dx, y += dy, ++j) |
| m_samplePaths[i].push_back(TPoint(tround(x), tround(y))); |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| struct SelectionData { |
| UCHAR m_selectedInk : 1; |
| UCHAR m_selectedPaint : 1; |
| UCHAR m_pureInk : 1; |
| UCHAR m_purePaint : 1; |
| }; |
| |
| |
| |
| |
| |
| class SelectionArrayPtr |
| { |
| SelectionData *m_buffer; |
| |
| public: |
| SelectionArrayPtr() : m_buffer(0) {} |
| |
| inline void allocate(unsigned int count) |
| { |
| m_buffer = new SelectionData[count]; |
| memset(m_buffer, 0, count * sizeof(SelectionData)); |
| } |
| |
| inline void destroy() |
| { |
| delete[] m_buffer; |
| m_buffer = 0; |
| } |
| |
| inline SelectionData *data() const |
| { |
| return m_buffer; |
| } |
| |
| inline SelectionData *data() |
| { |
| return m_buffer; |
| } |
| }; |
| |
| |
| |
| |
| class SelectionRaster |
| { |
| SelectionArrayPtr m_selection; |
| |
| int m_wrap; |
| |
| public: |
| SelectionRaster(TRasterCM32P cm); |
| |
| void updateSelection(TRasterCM32P cm, const BlendParam ¶m); |
| |
| SelectionData *data() const |
| { |
| return m_selection.data(); |
| } |
| |
| SelectionData *data() |
| { |
| return m_selection.data(); |
| } |
| |
| void destroy() |
| { |
| m_selection.destroy(); |
| } |
| |
| bool isSelectedInk(int xy) const |
| { |
| return (m_selection.data() + xy)->m_selectedInk; |
| } |
| |
| bool isSelectedInk(int x, int y) const |
| { |
| return isSelectedInk(x + y * m_wrap); |
| } |
| |
| bool isSelectedPaint(int xy) const |
| { |
| return (m_selection.data() + xy)->m_selectedPaint; |
| } |
| |
| bool isSelectedPaint(int x, int y) const |
| { |
| return isSelectedPaint(x + y * m_wrap); |
| } |
| |
| bool isPureInk(int xy) const |
| { |
| return (m_selection.data() + xy)->m_pureInk; |
| } |
| |
| bool isPureInk(int x, int y) const |
| { |
| return isPureInk(x + y * m_wrap); |
| } |
| |
| bool isPurePaint(int xy) const |
| { |
| return (m_selection.data() + xy)->m_purePaint; |
| } |
| |
| bool isPurePaint(int x, int y) const |
| { |
| return isPurePaint(x + y * m_wrap); |
| } |
| |
| bool isToneColor(int xy) const |
| { |
| return !(isPureInk(xy) || isPurePaint(xy)); |
| } |
| |
| bool isToneColor(int x, int y) const |
| { |
| return isToneColor(x + y * m_wrap); |
| } |
| }; |
| |
| |
| |
| inline UCHAR linearSearch(const int *v, unsigned int vSize, int k) |
| { |
| const int *vEnd = v + vSize; |
| for (; v < vEnd; ++v) |
| if (*v == k) |
| return 1; |
| return 0; |
| } |
| |
| |
| |
| |
| |
| inline UCHAR binarySearch(const int *v, unsigned int vSize, int k) |
| { |
| |
| |
| int a = -1, b, c = vSize; |
| |
| for (b = c >> 1; b != a; b = (a + c) >> 1) { |
| if (v[b] == k) |
| return 1; |
| else if (k < v[b]) |
| c = b; |
| else |
| a = b; |
| } |
| |
| return 0; |
| } |
| |
| |
| |
| SelectionRaster::SelectionRaster(TRasterCM32P cm) |
| { |
| unsigned int lx = cm->getLx(), ly = cm->getLy(), wrap = cm->getWrap(); |
| unsigned int size = lx * ly; |
| |
| m_wrap = lx; |
| |
| m_selection.allocate(size); |
| |
| cm->lock(); |
| TPixelCM32 *pix, *pixBegin = (TPixelCM32 *)cm->getRawData(); |
| |
| SelectionData *selData = data(); |
| |
| unsigned int i, j; |
| for (i = 0; i < ly; ++i) { |
| pix = pixBegin + i * wrap; |
| for (j = 0; j < lx; ++j, ++pix, ++selData) { |
| selData->m_pureInk = pix->getTone() == 0; |
| selData->m_purePaint = pix->getTone() == 255; |
| } |
| } |
| |
| cm->unlock(); |
| } |
| |
| |
| |
| void SelectionRaster::updateSelection(TRasterCM32P cm, const BlendParam ¶m) |
| { |
| |
| |
| std::vector<int> cIndexes = param.colorsIndexes; |
| std::sort(cIndexes.begin(), cIndexes.end()); |
| |
| unsigned int lx = cm->getLx(), ly = cm->getLy(), wrap = cm->getWrap(); |
| |
| |
| cm->lock(); |
| TPixelCM32 *pix, *pixBegin = (TPixelCM32 *)cm->getRawData(); |
| |
| SelectionData *selData = data(); |
| |
| const int *v = &cIndexes[0]; |
| unsigned int vSize = cIndexes.size(); |
| unsigned int i, j; |
| |
| |
| if (vSize > 50) { |
| for (i = 0; i < ly; ++i) { |
| pix = pixBegin + i * wrap; |
| for (j = 0; j < lx; ++j, ++pix, ++selData) { |
| selData->m_selectedInk = binarySearch(v, vSize, pix->getInk()); |
| selData->m_selectedPaint = binarySearch(v, vSize, pix->getPaint()); |
| } |
| } |
| } else { |
| for (i = 0; i < ly; ++i) { |
| pix = pixBegin + i * wrap; |
| for (j = 0; j < lx; ++j, ++pix, ++selData) { |
| selData->m_selectedInk = linearSearch(v, vSize, pix->getInk()); |
| selData->m_selectedPaint = linearSearch(v, vSize, pix->getPaint()); |
| } |
| } |
| } |
| |
| cm->unlock(); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| struct DoubleRGBMPixel { |
| double r; |
| double g; |
| double b; |
| double m; |
| |
| DoubleRGBMPixel() : r(0.0), g(0.0), b(0.0), m(0.0) {} |
| }; |
| |
| |
| |
| const double maxTone = TPixelCM32::getMaxTone(); |
| |
| |
| inline void getFactors(int tone, double &inkFactor, double &paintFactor) |
| { |
| paintFactor = tone / maxTone; |
| inkFactor = (1.0 - paintFactor); |
| } |
| |
| |
| |
| |
| void buildLayers( |
| const TRasterCM32P &cmIn, const std::vector<TPixel32> &palColors, |
| TRaster32P &inkRaster, TRaster32P &paintRaster) |
| { |
| |
| TPixelCM32 *cmPix, *cmBegin = (TPixelCM32 *)cmIn->getRawData(); |
| TPixel32 *inkPix = (TPixel32 *)inkRaster->getRawData(); |
| TPixel32 *paintPix = (TPixel32 *)paintRaster->getRawData(); |
| |
| unsigned int i, j, lx = cmIn->getLx(), ly = cmIn->getLy(), wrap = cmIn->getWrap(); |
| for (i = 0; i < ly; ++i) { |
| cmPix = cmBegin + i * wrap; |
| |
| for (j = 0; j < lx; ++j, ++cmPix, ++inkPix, ++paintPix) { |
| *inkPix = palColors[cmPix->getInk()]; |
| *paintPix = palColors[cmPix->getPaint()]; |
| |
| |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| inline bool isFlatNeighbourhood( |
| int selectedColor, |
| const TRasterCM32P &cmIn, const TPoint &pos, |
| const SelectionRaster &selRas, |
| const BlurPattern &blurPattern) |
| { |
| TPixelCM32 &pix = cmIn->pixels(pos.y)[pos.x]; |
| int lx = cmIn->getLx(), ly = cmIn->getLy(); |
| unsigned int xy; |
| |
| TPoint samplePix; |
| |
| const TPoint *samplePoint = blurPattern.m_samples.empty() ? 0 : &blurPattern.m_samples[0]; |
| |
| |
| unsigned int i, samplesCount = blurPattern.m_samples.size(); |
| for (i = 0; i < samplesCount; ++i, ++samplePoint) { |
| |
| samplePix.x = pos.x + samplePoint->x; |
| samplePix.y = pos.y + samplePoint->y; |
| |
| xy = samplePix.x + lx * samplePix.y; |
| |
| if (samplePix.x < 0 || samplePix.y < 0 || samplePix.x >= lx || samplePix.y >= ly) |
| continue; |
| |
| if (!selRas.isPurePaint(xy) && selRas.isSelectedInk(xy)) |
| if (cmIn->pixels(samplePix.y)[samplePix.x].getInk() != selectedColor) |
| return false; |
| |
| if (!selRas.isPureInk(xy) && selRas.isSelectedPaint(xy)) |
| if (cmIn->pixels(samplePix.y)[samplePix.x].getPaint() != selectedColor) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| |
| |
| |
| inline void addSamples( |
| const TRasterCM32P &cmIn, const TPoint &pos, |
| const TRaster32P &inkRas, const TRaster32P &paintRas, |
| const SelectionRaster &selRas, |
| const BlurPattern &blurPattern, |
| DoubleRGBMPixel &pixSum, double &factorsSum) |
| { |
| double inkFactor, paintFactor; |
| unsigned int xy, j, l; |
| int lx = cmIn->getLx(), ly = cmIn->getLy(); |
| TPixel32 *color; |
| TPoint samplePos, pathPos; |
| |
| const TPoint *samplePoint = blurPattern.m_samples.empty() ? 0 : &blurPattern.m_samples[0]; |
| const TPoint *pathPoint; |
| |
| unsigned int i, blurSamplesCount = blurPattern.m_samples.size(); |
| for (i = 0; i < blurSamplesCount; ++i, ++samplePoint) { |
| |
| samplePos.x = pos.x + samplePoint->x; |
| samplePos.y = pos.y + samplePoint->y; |
| if (samplePos.x < 0 || samplePos.y < 0 || samplePos.x >= lx || samplePos.y >= ly) |
| continue; |
| |
| |
| l = blurPattern.m_samplePaths[i].size(); |
| pathPoint = blurPattern.m_samplePaths[i].empty() ? 0 : &blurPattern.m_samplePaths[i][0]; |
| for (j = 0; j < l; ++j, ++pathPoint) { |
| pathPos.x = pos.x + pathPoint->x; |
| pathPos.y = pos.y + pathPoint->y; |
| xy = pathPos.x + lx * pathPos.y; |
| |
| if (!(selRas.isPurePaint(xy) || selRas.isSelectedInk(xy))) |
| break; |
| |
| if (!(selRas.isPureInk(xy) || selRas.isSelectedPaint(xy))) |
| break; |
| } |
| |
| if (j < l) |
| continue; |
| |
| xy = samplePos.x + lx * samplePos.y; |
| |
| if (selRas.isSelectedInk(xy) && !selRas.isPurePaint(xy)) { |
| getFactors(cmIn->pixels(samplePos.y)[samplePos.x].getTone(), inkFactor, paintFactor); |
| |
| color = &inkRas->pixels(samplePos.y)[samplePos.x]; |
| pixSum.r += inkFactor * color->r; |
| pixSum.g += inkFactor * color->g; |
| pixSum.b += inkFactor * color->b; |
| pixSum.m += inkFactor * color->m; |
| factorsSum += inkFactor; |
| } |
| |
| if (selRas.isSelectedPaint(xy) && !selRas.isPureInk(xy)) { |
| getFactors(cmIn->pixels(samplePos.y)[samplePos.x].getTone(), inkFactor, paintFactor); |
| |
| color = &paintRas->pixels(samplePos.y)[samplePos.x]; |
| pixSum.r += paintFactor * color->r; |
| pixSum.g += paintFactor * color->g; |
| pixSum.b += paintFactor * color->b; |
| pixSum.m += paintFactor * color->m; |
| factorsSum += paintFactor; |
| } |
| } |
| } |
| |
| |
| |
| typedef std::pair<TRaster32P, TRaster32P> RGBMRasterPair; |
| |
| |
| |
| |
| |
| inline void doBlend( |
| const TRasterCM32P &cmIn, |
| RGBMRasterPair &inkLayer, RGBMRasterPair &paintLayer, |
| const SelectionRaster &selRas, |
| const std::vector<BlurPattern> &blurPatterns) |
| { |
| |
| unsigned int blurPatternsCount = blurPatterns.size(); |
| int lx = cmIn->getLx(), ly = cmIn->getLy(); |
| double totalFactor; |
| |
| TPixelCM32 *cmPix, *cmBegin = (TPixelCM32 *)cmIn->getRawData(); |
| |
| TPixel32 |
| *inkIn = (TPixel32 *)inkLayer.first->getRawData(), |
| *inkOut = (TPixel32 *)inkLayer.second->getRawData(), |
| *paintIn = (TPixel32 *)paintLayer.first->getRawData(), |
| *paintOut = (TPixel32 *)paintLayer.second->getRawData(); |
| |
| const BlurPattern *blurPattern, *blurPatternsBegin = &blurPatterns[0]; |
| bool builtSamples = false; |
| |
| DoubleRGBMPixel samplesSum; |
| |
| |
| TPoint pos; |
| SelectionData *selData = selRas.data(); |
| cmPix = cmBegin; |
| for (pos.y = 0; pos.y < ly; ++pos.y, cmPix = cmBegin + pos.y * cmIn->getWrap()) |
| for (pos.x = 0; pos.x < lx; ++pos.x, ++inkIn, ++inkOut, ++paintIn, ++paintOut, ++selData, ++cmPix) { |
| blurPattern = blurPatternsBegin + (rand() % blurPatternsCount); |
| |
| |
| if (!selData->m_purePaint && selData->m_selectedInk) { |
| if (!builtSamples) { |
| |
| totalFactor = 1.0; |
| samplesSum.r = samplesSum.g = samplesSum.b = samplesSum.m = 0.0; |
| |
| if (!isFlatNeighbourhood(cmPix->getInk(), cmIn, pos, selRas, *blurPattern)) |
| addSamples(cmIn, pos, inkLayer.first, paintLayer.first, selRas, *blurPattern, |
| samplesSum, totalFactor); |
| |
| builtSamples = true; |
| } |
| |
| |
| inkOut->r = (samplesSum.r + inkIn->r) / totalFactor; |
| inkOut->g = (samplesSum.g + inkIn->g) / totalFactor; |
| inkOut->b = (samplesSum.b + inkIn->b) / totalFactor; |
| inkOut->m = (samplesSum.m + inkIn->m) / totalFactor; |
| } else { |
| |
| *inkOut = *inkIn; |
| } |
| |
| |
| if (!selData->m_pureInk && selData->m_selectedPaint) { |
| if (!builtSamples) { |
| |
| totalFactor = 1.0; |
| samplesSum.r = samplesSum.g = samplesSum.b = samplesSum.m = 0.0; |
| |
| if (!isFlatNeighbourhood(cmPix->getPaint(), cmIn, pos, selRas, *blurPattern)) |
| addSamples(cmIn, pos, inkLayer.first, paintLayer.first, selRas, *blurPattern, |
| samplesSum, totalFactor); |
| |
| builtSamples = true; |
| } |
| |
| |
| paintOut->r = (samplesSum.r + paintIn->r) / totalFactor; |
| paintOut->g = (samplesSum.g + paintIn->g) / totalFactor; |
| paintOut->b = (samplesSum.b + paintIn->b) / totalFactor; |
| paintOut->m = (samplesSum.m + paintIn->m) / totalFactor; |
| } else { |
| |
| *paintOut = *paintIn; |
| } |
| |
| builtSamples = false; |
| } |
| } |
| |
| |
| |
| typedef std::vector<BlurPattern> BlurPatternContainer; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| template <typename PIXEL> |
| void blend(TToonzImageP ti, TRasterPT<PIXEL> rasOut, const std::vector<BlendParam> ¶ms) |
| { |
| assert(ti->getRaster()->getSize() == rasOut->getSize()); |
| |
| |
| |
| unsigned int i, j; |
| TRect saveBox(ti->getSavebox()); |
| |
| int enlargement = 0; |
| for (i = 0; i < params.size(); ++i) |
| for (j = 0; j < params[i].colorsIndexes.size(); ++j) |
| if (params[i].colorsIndexes[j] == 0) |
| enlargement = tmax(enlargement, tceil(params[i].intensity)); |
| saveBox = saveBox.enlarge(enlargement); |
| |
| TRasterCM32P cmIn(ti->getRaster()->extract(saveBox)); |
| TRasterPT<PIXEL> rasOutExtract = rasOut->extract(saveBox); |
| |
| |
| unsigned int lx = cmIn->getLx(), ly = cmIn->getLy(); |
| |
| |
| SelectionRaster selectionRaster(cmIn); |
| |
| |
| |
| const int blurPatternsPerParam = 10; |
| std::vector<BlurPatternContainer> blurGroup(params.size()); |
| |
| for (i = 0; i < params.size(); ++i) { |
| BlurPatternContainer &blurContainer = blurGroup[i]; |
| blurContainer.reserve(blurPatternsPerParam); |
| |
| for (j = 0; j < blurPatternsPerParam; ++j) |
| blurContainer.push_back(BlurPattern(params[i].intensity, params[i].smoothness, params[i].stopAtCountour)); |
| } |
| |
| |
| TPalette *palette = ti->getPalette(); |
| std::vector<TPixel32> paletteColors; |
| paletteColors.resize(palette->getStyleCount()); |
| for (i = 0; i < paletteColors.size(); ++i) |
| paletteColors[i] = premultiply(palette->getStyle(i)->getAverageColor()); |
| |
| |
| |
| |
| |
| RGBMRasterPair inkLayer, paintLayer; |
| |
| TRaster32P rasOut32P_1(lx, ly, lx, (TPixel32 *)rasOut->getRawData(), false); |
| inkLayer.first = (params.size() % 2) ? rasOut32P_1 : TRaster32P(lx, ly); |
| inkLayer.second = (params.size() % 2) ? TRaster32P(lx, ly) : rasOut32P_1; |
| |
| if (PIXEL::maxChannelValue >= TPixel64::maxChannelValue) { |
| TRaster32P rasOut32P_2(lx, ly, lx, ((TPixel32 *)rasOut->getRawData()) + lx * ly, false); |
| paintLayer.first = (params.size() % 2) ? rasOut32P_2 : TRaster32P(lx, ly); |
| paintLayer.second = (params.size() % 2) ? TRaster32P(lx, ly) : rasOut32P_2; |
| } else { |
| paintLayer.first = TRaster32P(lx, ly); |
| paintLayer.second = TRaster32P(lx, ly); |
| } |
| |
| inkLayer.first->clear(); |
| inkLayer.second->clear(); |
| paintLayer.first->clear(); |
| paintLayer.second->clear(); |
| |
| |
| cmIn->lock(); |
| rasOut->lock(); |
| |
| inkLayer.first->lock(); |
| inkLayer.second->lock(); |
| paintLayer.first->lock(); |
| paintLayer.second->lock(); |
| |
| |
| buildLayers(cmIn, paletteColors, inkLayer.first, paintLayer.first); |
| |
| |
| for (i = 0; i < params.size(); ++i) { |
| if (params[i].colorsIndexes.size() == 0) |
| continue; |
| |
| selectionRaster.updateSelection(cmIn, params[i]); |
| doBlend(cmIn, inkLayer, paintLayer, selectionRaster, blurGroup[i]); |
| |
| tswap(inkLayer.first, inkLayer.second); |
| tswap(paintLayer.first, paintLayer.second); |
| } |
| |
| |
| inkLayer.second->unlock(); |
| paintLayer.second->unlock(); |
| inkLayer.second = TRaster32P(); |
| paintLayer.second = TRaster32P(); |
| |
| |
| rasOut->clear(); |
| |
| |
| double PIXELmaxChannelValue = PIXEL::maxChannelValue; |
| double toPIXELFactor = PIXELmaxChannelValue / (double)TPixel32::maxChannelValue; |
| double inkFactor, paintFactor; |
| TPoint pos; |
| |
| PIXEL *outPix, *outBegin = (PIXEL *)rasOutExtract->getRawData(); |
| TPixelCM32 *cmPix, *cmBegin = (TPixelCM32 *)cmIn->getRawData(); |
| int wrap = rasOutExtract->getWrap(); |
| |
| TPixel32 *inkPix = (TPixel32 *)inkLayer.first->getRawData(); |
| TPixel32 *paintPix = (TPixel32 *)paintLayer.first->getRawData(); |
| |
| for (i = 0; i < ly; ++i) { |
| outPix = outBegin + wrap * i; |
| cmPix = cmBegin + wrap * i; |
| for (j = 0; j < lx; ++j, ++outPix, ++cmPix, ++inkPix, ++paintPix) { |
| getFactors(cmPix->getTone(), inkFactor, paintFactor); |
| |
| outPix->r = tcrop(toPIXELFactor * (inkFactor * inkPix->r + paintFactor * paintPix->r), 0.0, PIXELmaxChannelValue); |
| outPix->g = tcrop(toPIXELFactor * (inkFactor * inkPix->g + paintFactor * paintPix->g), 0.0, PIXELmaxChannelValue); |
| outPix->b = tcrop(toPIXELFactor * (inkFactor * inkPix->b + paintFactor * paintPix->b), 0.0, PIXELmaxChannelValue); |
| outPix->m = tcrop(toPIXELFactor * (inkFactor * inkPix->m + paintFactor * paintPix->m), 0.0, PIXELmaxChannelValue); |
| } |
| } |
| |
| inkLayer.first->unlock(); |
| paintLayer.first->unlock(); |
| |
| cmIn->unlock(); |
| rasOut->unlock(); |
| |
| |
| selectionRaster.destroy(); |
| } |
| |
| template void blend<TPixel32>(TToonzImageP cmIn, TRasterPT<TPixel32> rasOut, const std::vector<BlendParam> ¶ms); |
| template void blend<TPixel64>(TToonzImageP cmIn, TRasterPT<TPixel64> rasOut, const std::vector<BlendParam> ¶ms); |
| |