//this one incorporate the preprocessColors and the finalize function; used for swatch.(tipically on very small rasters) TRasterP TCleanupper::processColors(const TRasterP &rin) { if (m_parameters->m_lineProcessingMode == lpNone) return rin; TRasterCM32P rcm = TRasterCM32P(rin->getSize()); if (!rcm) { assert(!"failed finalRas allocation!"); return TRasterCM32P(); } // Copy current cleanup palette to parameters' colors m_parameters->m_colors.update(m_parameters->m_cleanupPalette.getPointer(), m_parameters->m_noAntialias); bool toGr8 = (m_parameters->m_lineProcessingMode == lpGrey); if (toGr8) { //No (color) processing. Not even thresholding. This just means that all the important //stuff here is made in the brightness/contrast stage... //NOTE: Most of the color processing should be DISABLED in this case!! //finalRas->clear(); rin->lock(); rcm->lock(); if (TRasterGR8P(rin)) { UCHAR *rowin = rin->getRawData(); TUINT32 *rowout = reinterpret_cast<TUINT32 *>(rcm->getRawData()); for (int i = 0; i < rin->getLy(); i++) { for (int j = 0; j < rin->getLx(); j++) *rowout++ = *rowin++; //Direct copy for now... :( rowin += rin->getWrap() - rin->getLx(); rowout += rcm->getWrap() - rcm->getLx(); } } else { TPixel32 *rowin = reinterpret_cast<TPixel32 *>(rin->getRawData()); TUINT32 *rowout = reinterpret_cast<TUINT32 *>(rcm->getRawData()); for (int i = 0; i < rin->getLy(); i++) { for (int j = 0; j < rin->getLx(); j++) *rowout++ = TPixelGR8::from(*rowin++).value; rowin += rin->getWrap() - rin->getLx(); rowout += rcm->getWrap() - rcm->getLx(); } } rin->unlock(); rcm->unlock(); } else { assert(TRaster32P(rin)); preprocessColors(rcm, rin, m_parameters->m_colors); } //outImg->setDpi(outDpi.x, outDpi.y); CleanupPreprocessedImage cpi(m_parameters, TToonzImageP(rcm, rcm->getBounds()), toGr8); cpi.m_autocentered = true; TRaster32P rout = TRaster32P(rin->getSize()); finalize(rout, &cpi); return rout; }
TPoint nearestInk(const TRasterCM32P &r, const TPoint &p, int ray) { int i, j; TPixelCM32 *buf = (TPixelCM32 *)r->getRawData(); for (j = std::max(p.y - ray, 0); j <= std::min(p.y + ray, r->getLy() - 1); j++) for (i = std::max(p.x - ray, 0); i <= std::min(p.x + ray, r->getLx() - 1); i++) if (!(buf + j * r->getWrap() + i)->isPurePaint()) return TPoint(i, j); return TPoint(-1, -1); }
void inkFill(const TRasterCM32P &r, const TPoint &pin, int ink, int searchRay, TTileSaverCM32 *saver, TRect *insideRect) { r->lock(); TPixelCM32 *pixels = (TPixelCM32 *)r->getRawData(); int oldInk; TPoint p = pin; if ((pixels + p.y * r->getWrap() + p.x)->isPurePaint() && (searchRay == 0 || (p = nearestInk(r, p, searchRay)) == TPoint(-1, -1))) { r->unlock(); return; } TPixelCM32 *pix = pixels + (p.y * r->getWrap() + p.x); if (pix->getInk() == ink) { r->unlock(); return; } oldInk = pix->getInk(); std::stack<TPoint> seeds; seeds.push(p); while (!seeds.empty()) { p = seeds.top(); seeds.pop(); if (!r->getBounds().contains(p)) continue; if (insideRect && !insideRect->contains(p)) continue; TPixelCM32 *pix = pixels + (p.y * r->getWrap() + p.x); if (pix->isPurePaint() || pix->getInk() != oldInk) continue; if (saver) saver->save(p); pix->setInk(ink); seeds.push(TPoint(p.x - 1, p.y - 1)); seeds.push(TPoint(p.x - 1, p.y)); seeds.push(TPoint(p.x - 1, p.y + 1)); seeds.push(TPoint(p.x, p.y - 1)); seeds.push(TPoint(p.x, p.y + 1)); seeds.push(TPoint(p.x + 1, p.y - 1)); seeds.push(TPoint(p.x + 1, p.y)); seeds.push(TPoint(p.x + 1, p.y + 1)); } r->unlock(); }
InkSegmenter(const TRasterCM32P &r, float growFactor, TTileSaverCM32 *saver) : m_r(r) , m_lx(r->getLx()) , m_ly(r->getLy()) , m_wrap(r->getWrap()) , m_buf((TPixelCM32 *)r->getRawData()) , m_bBox(r->getBounds()) , m_saver(saver) , m_growFactor(growFactor) { m_displaceVector[0] = -m_wrap - 1; m_displaceVector[1] = -m_wrap; m_displaceVector[2] = -m_wrap + 1; m_displaceVector[3] = -1; m_displaceVector[4] = +1; m_displaceVector[5] = m_wrap - 1; m_displaceVector[6] = m_wrap; m_displaceVector[7] = m_wrap + 1; }
void SelectionRaster::updateSelection(TRasterCM32P cm, const BlendParam ¶m) { // Make a hard copy of color indexes. We do so since we absolutely prefer // having them SORTED! std::vector<int> cIndexes = param.colorsIndexes; std::sort(cIndexes.begin(), cIndexes.end()); unsigned int lx = cm->getLx(), ly = cm->getLy(), wrap = cm->getWrap(); // Scan each cm pixel, looking if its ink or paint is in param's colorIndexes. cm->lock(); TPixelCM32 *pix, *pixBegin = (TPixelCM32 *)cm->getRawData(); SelectionData *selData = data(); const int *v = &cIndexes[0]; // NOTE: cIndexes.size() > 0 due to external check. unsigned int vSize = cIndexes.size(); unsigned int i, j; // NOTE: It seems that linear searches are definitely best for small color // indexes. 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(); }
// Copies the cmIn paint and ink colors to the output rasters. void buildLayers(const TRasterCM32P &cmIn, const std::vector<TPixel32> &palColors, TRaster32P &inkRaster, TRaster32P &paintRaster) { // Separate cmIn by copying the ink & paint colors directly to the layer // rasters. 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()]; // Should pure colors be checked...? } } }
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(); }
// Performs a single color blending. This function can be repeatedly invoked to // perform multiple color blending. inline void doBlend(const TRasterCM32P &cmIn, RGBMRasterPair &inkLayer, RGBMRasterPair &paintLayer, const SelectionRaster &selRas, const std::vector<BlurPattern> &blurPatterns) { // Declare some vars 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; // For every cmIn pixel 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); // Build the ink blend color if (!selData->m_purePaint && selData->m_selectedInk) { if (!builtSamples) { // Build samples contributes 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; } // Output the blended pixel 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 { // If the color is not blended, then just copy the old layer pixel *inkOut = *inkIn; } // Build the paint blend color if (!selData->m_pureInk && selData->m_selectedPaint) { if (!builtSamples) { // Build samples contributes 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; } // Output the blended pixel 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 { // If the color is not blended, then just copy the old layer pixel *paintOut = *paintIn; } builtSamples = false; } }
CleanupPreprocessedImage *TCleanupper::process( TRasterImageP &image, bool first_image, TRasterImageP &onlyResampledImage, bool isCameraTest, bool returnResampled, bool onlyForSwatch, TAffine *resampleAff) { TAffine aff; double blur; TDimension outDim(0, 0); TPointD outDpi; bool isSameDpi = false; bool autocentered = getResampleValues(image, aff, blur, outDim, outDpi, isCameraTest, isSameDpi); if (m_parameters->m_autocenterType != AUTOCENTER_NONE && !autocentered) DVGui::MsgBox(DVGui::WARNING, QObject::tr("The autocentering failed on the current drawing.")); bool fromGr8 = (bool)TRasterGR8P(image->getRaster()); bool toGr8 = (m_parameters->m_lineProcessingMode == lpGrey); // If necessary, perform auto-adjust if (!isCameraTest && m_parameters->m_lineProcessingMode != lpNone && toGr8 && m_parameters->m_autoAdjustMode != AUTO_ADJ_NONE && !onlyForSwatch) { static int ref_cum[256]; UCHAR lut[256]; int cum[256]; double x0_src_f, y0_src_f, x1_src_f, y1_src_f; int x0_src, y0_src, x1_src, y1_src; //cleanup_message("Autoadjusting... \n"); TAffine inv = aff.inv(); x0_src_f = affMV1(inv, 0, 0); y0_src_f = affMV2(inv, 0, 0); x1_src_f = affMV1(inv, outDim.lx - 1, outDim.ly - 1); y1_src_f = affMV2(inv, outDim.lx - 1, outDim.ly - 1); x0_src = tround(x0_src_f); y0_src = tround(y0_src_f); x1_src = tround(x1_src_f); y1_src = tround(y1_src_f); set_autoadjust_window(x0_src, y0_src, x1_src, y1_src); if (!TRasterGR8P(image->getRaster())) { //Auto-adjusting a 32-bit image. This means that a white background must be introduced first. TRaster32P ras32(image->getRaster()->clone()); TRop::addBackground(ras32, TPixel32::White); image = TRasterImageP(ras32); //old image is released here ras32 = TRaster32P(); TRasterGR8P rgr(image->getRaster()->getSize()); TRop::copy(rgr, image->getRaster()); //This is now legit. It was NOT before the clone, since the original could be cached. image->setRaster(rgr); } switch (m_parameters->m_autoAdjustMode) { case AUTO_ADJ_HISTOGRAM: { if (first_image) { build_gr_cum(image, ref_cum); } else { build_gr_cum(image, cum); build_gr_lut(ref_cum, cum, lut); apply_lut(image, lut); } } CASE AUTO_ADJ_HISTO_L : histo_l_algo(image, first_image); CASE AUTO_ADJ_BLACK_EQ : black_eq_algo(image); CASE AUTO_ADJ_NONE : DEFAULT : assert(false); } } fromGr8 = (bool)TRasterGR8P(image->getRaster()); //may have changed type due to auto-adjust assert(returnResampled || !onlyForSwatch); //if onlyForSwatch, then returnResampled // Allocate output colormap raster TRasterCM32P finalRas; if (!onlyForSwatch) { finalRas = TRasterCM32P(outDim); if (!finalRas) { TImageCache::instance()->outputMap(outDim.lx * outDim.ly * 4, "C:\\cachelog"); assert(!"failed finalRas allocation!"); return 0; } } // In case the input raster was a greymap, we cannot reutilize finalRas's buffer to transform the final // fullcolor pixels to colormap pixels directly (1 32-bit pixel would hold 4 8-bit pixels) - therefore, // a secondary greymap is allocated. //NOTE: This should be considered obsolete? By using TRop::resample( <TRaster32P& instance> , ...) we //should get the same effect!! TRasterP tmp_ras; if (returnResampled || (fromGr8 && toGr8)) { if (fromGr8 && toGr8) tmp_ras = TRasterGR8P(outDim); else tmp_ras = TRaster32P(outDim); if (!tmp_ras) { TImageCache::instance()->outputMap(outDim.lx * outDim.ly * 4, "C:\\cachelog"); assert(!"failed tmp_ras allocation!"); return 0; } } else //if finalRas is allocated, and the intermediate raster has to be 32-bit, we can perform pixel //conversion directly on the same output buffer tmp_ras = TRaster32P(outDim.lx, outDim.ly, outDim.lx, (TPixel32 *)finalRas->getRawData()); TRop::ResampleFilterType flt_type; if (isSameDpi) flt_type = TRop::ClosestPixel; //NearestNeighbor else if (isCameraTest) flt_type = TRop::Triangle; else flt_type = TRop::Hann2; TRop::resample(tmp_ras, image->getRaster(), aff, flt_type, blur); if ((TRaster32P)tmp_ras) //Add white background to deal with semitransparent pixels TRop::addBackground(tmp_ras, TPixel32::White); if (resampleAff) *resampleAff = aff; image->getRaster()->unlock(); image = TRasterImageP(); if (returnResampled) { onlyResampledImage = TRasterImageP(tmp_ras); onlyResampledImage->setDpi(outDpi.x, outDpi.y); } if (onlyForSwatch) return 0; assert(finalRas); // Copy current cleanup palette to parameters' colors m_parameters->m_colors.update(m_parameters->m_cleanupPalette.getPointer(), m_parameters->m_noAntialias); if (toGr8) { //No (color) processing. Not even thresholding. This just means that all the important //stuff here is made in the brightness/contrast stage... //NOTE: Most of the color processing should be DISABLED in this case!! tmp_ras->lock(); finalRas->lock(); assert(tmp_ras->getSize() == finalRas->getSize()); assert(tmp_ras->getLx() == tmp_ras->getWrap()); assert(finalRas->getLx() == finalRas->getWrap()); int pixCount = outDim.lx * outDim.ly; if (fromGr8) { UCHAR *rowin = tmp_ras->getRawData(); TUINT32 *rowout = reinterpret_cast<TUINT32 *>(finalRas->getRawData()); for (int i = 0; i < pixCount; i++) *rowout++ = *rowin++; //Direct copy for now... :( } else { TPixel32 *rowin = reinterpret_cast<TPixel32 *>(tmp_ras->getRawData()); TUINT32 *rowout = reinterpret_cast<TUINT32 *>(finalRas->getRawData()); for (int i = 0; i < pixCount; i++) *rowout++ = TPixelGR8::from(*rowin++).value; } tmp_ras->unlock(); finalRas->unlock(); } else { //WARNING: finalRas and tmp_ras may share the SAME buffer! assert(TRaster32P(tmp_ras)); preprocessColors(finalRas, tmp_ras, m_parameters->m_colors); } TToonzImageP final; final = TToonzImageP(finalRas, finalRas->getBounds());