void Gui::debugImage(const Image* image, const RectI& roi, const QString & filename ) { if (image->getBitDepth() != eImageBitDepthFloat) { qDebug() << "Debug image only works on float images."; return; } RectI renderWindow; RectI bounds = image->getBounds(); if ( roi.isNull() ) { renderWindow = bounds; } else { if ( !roi.intersect(bounds, &renderWindow) ) { qDebug() << "The RoI does not interesect the bounds of the image."; return; } } QImage output(renderWindow.width(), renderWindow.height(), QImage::Format_ARGB32); const Color::Lut* lut = Color::LutManager::sRGBLut(); lut->validate(); Image::ReadAccess acc = image->getReadRights(); const float* from = (const float*)acc.pixelAt( renderWindow.left(), renderWindow.bottom() ); assert(from); int srcNComps = (int)image->getComponentsCount(); int srcRowElements = srcNComps * bounds.width(); for ( int y = renderWindow.height() - 1; y >= 0; --y, from += ( srcRowElements - srcNComps * renderWindow.width() ) ) { QRgb* dstPixels = (QRgb*)output.scanLine(y); assert(dstPixels); unsigned error_r = 0x80; unsigned error_g = 0x80; unsigned error_b = 0x80; for (int x = 0; x < renderWindow.width(); ++x, from += srcNComps, ++dstPixels) { float r, g, b, a; switch (srcNComps) { case 1: r = g = b = *from; a = 1; break; case 2: r = *from; g = *(from + 1); b = 0; a = 1; break; case 3: r = *from; g = *(from + 1); b = *(from + 2); a = 1; break; case 4: r = *from; g = *(from + 1); b = *(from + 2); a = *(from + 3); break; default: assert(false); return; } error_r = (error_r & 0xff) + lut->toColorSpaceUint8xxFromLinearFloatFast(r); error_g = (error_g & 0xff) + lut->toColorSpaceUint8xxFromLinearFloatFast(g); error_b = (error_b & 0xff) + lut->toColorSpaceUint8xxFromLinearFloatFast(b); assert(error_r < 0x10000 && error_g < 0x10000 && error_b < 0x10000); *dstPixels = qRgba( U8(error_r >> 8), U8(error_g >> 8), U8(error_b >> 8), U8(a * 255) ); } } U64 hashKey = image->getHashKey(); QString hashKeyStr = QString::number(hashKey); QString realFileName = filename.isEmpty() ? QString( hashKeyStr + QString::fromUtf8(".png") ) : filename; #ifdef DEBUG qDebug() << "Writing image: " << realFileName; renderWindow.debug(); #endif output.save(realFileName); } // Gui::debugImage
RectI ImageTilesState::getMinimalBboxToRenderFromTilesState(const RectI& roi, const TileStateHeader& stateMap) { if (stateMap.state->tiles.empty()) { return RectI(); } const RectI& imageBoundsRoundedToTileSize = stateMap.state->boundsRoundedToTileSize; const RectI& imageBoundsNotRounded = stateMap.state->bounds; assert(imageBoundsRoundedToTileSize.contains(roi)); RectI roiRoundedToTileSize = roi; roiRoundedToTileSize.roundToTileSize(stateMap.tileSizeX, stateMap.tileSizeY); // Search for rendered lines from bottom to top for (int y = roiRoundedToTileSize.y1; y < roiRoundedToTileSize.y2; y += stateMap.tileSizeY) { bool hasTileUnrenderedOnLine = false; for (int x = roiRoundedToTileSize.x1; x < roiRoundedToTileSize.x2; x += stateMap.tileSizeX) { const TileState* tile = stateMap.getTileAt(x, y); if (tile->status == eTileStatusNotRendered) { hasTileUnrenderedOnLine = true; break; } } if (!hasTileUnrenderedOnLine) { roiRoundedToTileSize.y1 += stateMap.tileSizeY; } else { break; } } // Search for rendered lines from top to bottom for (int y = roiRoundedToTileSize.y2 - stateMap.tileSizeY; y >= roiRoundedToTileSize.y1; y -= stateMap.tileSizeY) { bool hasTileUnrenderedOnLine = false; for (int x = roiRoundedToTileSize.x1; x < roiRoundedToTileSize.x2; x += stateMap.tileSizeX) { const TileState* tile = stateMap.getTileAt(x, y); if (tile->status == eTileStatusNotRendered) { hasTileUnrenderedOnLine = true; break; } } if (!hasTileUnrenderedOnLine) { roiRoundedToTileSize.y2 -= stateMap.tileSizeY; } else { break; } } // Avoid making roiRoundedToTileSize.width() iterations for nothing if (roiRoundedToTileSize.isNull()) { return roiRoundedToTileSize; } // Search for rendered columns from left to right for (int x = roiRoundedToTileSize.x1; x < roiRoundedToTileSize.x2; x += stateMap.tileSizeX) { bool hasTileUnrenderedOnCol = false; for (int y = roiRoundedToTileSize.y1; y < roiRoundedToTileSize.y2; y += stateMap.tileSizeY) { const TileState* tile = stateMap.getTileAt(x, y); if (tile->status == eTileStatusNotRendered) { hasTileUnrenderedOnCol = true; break; } } if (!hasTileUnrenderedOnCol) { roiRoundedToTileSize.x1 += stateMap.tileSizeX; } else { break; } } // Avoid making roiRoundedToTileSize.width() iterations for nothing if (roiRoundedToTileSize.isNull()) { return roiRoundedToTileSize; } // Search for rendered columns from right to left for (int x = roiRoundedToTileSize.x2 - stateMap.tileSizeX; x >= roiRoundedToTileSize.x1; x -= stateMap.tileSizeX) { bool hasTileUnrenderedOnCol = false; for (int y = roiRoundedToTileSize.y1; y < roiRoundedToTileSize.y2; y += stateMap.tileSizeY) { const TileState* tile = stateMap.getTileAt(x, y); if (tile->status == eTileStatusNotRendered) { hasTileUnrenderedOnCol = true; break; } } if (!hasTileUnrenderedOnCol) { roiRoundedToTileSize.x2 -= stateMap.tileSizeX; } else { break; } } // Intersect the result to the actual image bounds (because the tiles are rounded to tile size) RectI ret; roiRoundedToTileSize.intersect(imageBoundsNotRounded, &ret); return ret; } // getMinimalBboxToRenderFromTilesState
void ImageTilesState::getMinimalRectsToRenderFromTilesState(const RectI& roi, const TileStateHeader& stateMap, std::list<RectI>* rectsToRender) { if (stateMap.state->tiles.empty()) { return; } RectI roiRoundedToTileSize = roi; roiRoundedToTileSize.roundToTileSize(stateMap.tileSizeX, stateMap.tileSizeY); RectI bboxM = getMinimalBboxToRenderFromTilesState(roi, stateMap); if (bboxM.isNull()) { return; } bboxM.roundToTileSize(stateMap.tileSizeX, stateMap.tileSizeY); // optimization by Fred, Jan 31, 2014 // // Now that we have the smallest enclosing bounding box, // let's try to find rectangles for the bottom, the top, // the left and the right part. // This happens quite often, for example when zooming out // (in this case the area to compute is formed of A, B, C and D, // and X is already rendered), or when panning (in this case the area // is just two rectangles, e.g. A and C, and the rectangles B, D and // X are already rendered). // The rectangles A, B, C and D from the following drawing are just // zeroes, and X contains zeroes and ones. // // BBBBBBBBBBBBBB // BBBBBBBBBBBBBB // CXXXXXXXXXXDDD // CXXXXXXXXXXDDD // CXXXXXXXXXXDDD // CXXXXXXXXXXDDD // AAAAAAAAAAAAAA // First, find if there's an "A" rectangle, and push it to the result //find bottom RectI bboxX = bboxM; RectI bboxA = bboxX; bboxA.y2 = bboxA.y1; for (int y = bboxX.y1; y < bboxX.y2; y += stateMap.tileSizeY) { bool hasRenderedTileOnLine = false; for (int x = bboxX.x1; x < bboxX.x2; x += stateMap.tileSizeX) { const TileState* tile = stateMap.getTileAt(x, y); if (tile->status != eTileStatusNotRendered) { hasRenderedTileOnLine = true; break; } } if (hasRenderedTileOnLine) { break; } else { bboxX.y1 += stateMap.tileSizeY; bboxA.y2 = bboxX.y1; } } if ( !bboxA.isNull() ) { // empty boxes should not be pushed // Ensure the bbox lies in the RoI since we rounded to tile size earlier RectI bboxAIntersected; bboxA.intersect(roi, &bboxAIntersected); rectsToRender->push_back(bboxAIntersected); } // Now, find the "B" rectangle //find top RectI bboxB = bboxX; bboxB.y1 = bboxB.y2; for (int y = bboxX.y2 - stateMap.tileSizeY; y >= bboxX.y1; y -= stateMap.tileSizeY) { bool hasRenderedTileOnLine = false; for (int x = bboxX.x1; x < bboxX.x2; x += stateMap.tileSizeX) { const TileState* tile = stateMap.getTileAt(x, y); if (tile->status != eTileStatusNotRendered) { hasRenderedTileOnLine = true; break; } } if (hasRenderedTileOnLine) { break; } else { bboxX.y2 -= stateMap.tileSizeY; bboxB.y1 = bboxX.y2; } } if ( !bboxB.isNull() ) { // empty boxes should not be pushed // Ensure the bbox lies in the RoI since we rounded to tile size earlier RectI bboxBIntersected; bboxB.intersect(roi, &bboxBIntersected); rectsToRender->push_back(bboxBIntersected); } //find left RectI bboxC = bboxX; bboxC.x2 = bboxC.x1; if ( bboxX.y1 < bboxX.y2 ) { for (int x = bboxX.x1; x < bboxX.x2; x += stateMap.tileSizeX) { bool hasRenderedTileOnCol = false; for (int y = bboxX.y1; y < bboxX.y2; y += stateMap.tileSizeY) { const TileState* tile = stateMap.getTileAt(x, y); if (tile->status != eTileStatusNotRendered) { hasRenderedTileOnCol = true; break; } } if (hasRenderedTileOnCol) { break; } else { bboxX.x1 += stateMap.tileSizeX; bboxC.x2 = bboxX.x1; } } } if ( !bboxC.isNull() ) { // empty boxes should not be pushed // Ensure the bbox lies in the RoI since we rounded to tile size earlier RectI bboxCIntersected; bboxC.intersect(roi, &bboxCIntersected); rectsToRender->push_back(bboxCIntersected); } //find right RectI bboxD = bboxX; bboxD.x1 = bboxD.x2; if ( bboxX.y1 < bboxX.y2 ) { for (int x = bboxX.x2 - stateMap.tileSizeX; x >= bboxX.x1; x -= stateMap.tileSizeX) { bool hasRenderedTileOnCol = false; for (int y = bboxX.y1; y < bboxX.y2; y += stateMap.tileSizeY) { const TileState* tile = stateMap.getTileAt(x, y); if (tile->status != eTileStatusNotRendered) { hasRenderedTileOnCol = true; break; } } if (hasRenderedTileOnCol) { break; } else { bboxX.x2 -= stateMap.tileSizeX; bboxD.x1 = bboxX.x2; } } } if ( !bboxD.isNull() ) { // empty boxes should not be pushed // Ensure the bbox lies in the RoI since we rounded to tile size earlier RectI bboxDIntersected; bboxD.intersect(roi, &bboxDIntersected); rectsToRender->push_back(bboxDIntersected); } assert( bboxA.bottom() == bboxM.bottom() ); assert( bboxA.left() == bboxM.left() ); assert( bboxA.right() == bboxM.right() ); assert( bboxA.top() == bboxX.bottom() ); assert( bboxB.top() == bboxM.top() ); assert( bboxB.left() == bboxM.left() ); assert( bboxB.right() == bboxM.right() ); assert( bboxB.bottom() == bboxX.top() ); assert( bboxC.top() == bboxX.top() ); assert( bboxC.left() == bboxM.left() ); assert( bboxC.right() == bboxX.left() ); assert( bboxC.bottom() == bboxX.bottom() ); assert( bboxD.top() == bboxX.top() ); assert( bboxD.left() == bboxX.right() ); assert( bboxD.right() == bboxM.right() ); assert( bboxD.bottom() == bboxX.bottom() ); // get the bounding box of what's left (the X rectangle in the drawing above) bboxX = getMinimalBboxToRenderFromTilesState(bboxX, stateMap); if ( !bboxX.isNull() ) { // empty boxes should not be pushed // Ensure the bbox lies in the RoI since we rounded to tile size earlier RectI bboxXIntersected; bboxX.intersect(roi, &bboxXIntersected); rectsToRender->push_back(bboxXIntersected); } } // getMinimalRectsToRenderFromTilesState
std::pair<ImagePtr, RectI> TrackMarker::getMarkerImage(int time, const RectI& roi) const { std::list<ImageComponents> components; components.push_back( ImageComponents::getRGBComponents() ); const unsigned int mipmapLevel = 0; assert( !roi.isNull() ); NodePtr node = getContext()->getNode(); NodePtr input = node->getInput(0); if (!input) { return std::make_pair(ImagePtr(), roi); } AbortableRenderInfoPtr abortInfo = AbortableRenderInfo::create(false, 0); const bool isRenderUserInteraction = true; const bool isSequentialRender = false; AbortableThread* isAbortable = dynamic_cast<AbortableThread*>( QThread::currentThread() ); if (isAbortable) { isAbortable->setAbortInfo( isRenderUserInteraction, abortInfo, node->getEffectInstance() ); } ParallelRenderArgsSetter::CtorArgsPtr tlsArgs(new ParallelRenderArgsSetter::CtorArgs); tlsArgs->time = time; tlsArgs->view = ViewIdx(0); tlsArgs->isRenderUserInteraction = isRenderUserInteraction; tlsArgs->isSequential = isSequentialRender; tlsArgs->abortInfo = abortInfo; tlsArgs->treeRoot = getContext()->getNode(); tlsArgs->textureIndex = 0; tlsArgs->timeline = node->getApp()->getTimeLine(); tlsArgs->activeRotoPaintNode = NodePtr(); tlsArgs->activeRotoDrawableItem = RotoDrawableItemPtr(); tlsArgs->isDoingRotoNeatRender = false; tlsArgs->isAnalysis = true; tlsArgs->draftMode = true; tlsArgs->stats = RenderStatsPtr(); ParallelRenderArgsSetter frameRenderArgs(tlsArgs); RenderScale scale; scale.x = scale.y = 1.; EffectInstance::RenderRoIArgs args( time, scale, mipmapLevel, //mipmaplevel ViewIdx(0), false, roi, RectD(), components, eImageBitDepthFloat, false, node->getEffectInstance(), eStorageModeRAM /*returnOpenGlTex*/, time); std::map<ImageComponents, ImagePtr> planes; EffectInstance::RenderRoIRetCode stat = input->getEffectInstance()->renderRoI(args, &planes); appPTR->getAppTLS()->cleanupTLSForThread(); if ( (stat != EffectInstance::eRenderRoIRetCodeOk) || planes.empty() ) { return std::make_pair(ImagePtr(), roi); } return std::make_pair(planes.begin()->second, roi); } // TrackMarker::getMarkerImage