void YUVImageWriter::writeImage( const vector<string> &names, const ImagePrimitive * image, const Box2i &dataWindow ) const { const V2f &kBkR = m_kBkRParameter->getTypedValue(); const Box3f &range = m_rangeParameter->getTypedValue(); if ( range.isEmpty() ) { throw InvalidArgumentException("YUVImageWriter: Empty YUV range specified "); } const float kB = kBkR.x; const float kR = kBkR.y; int displayWidth = 1 + image->getDisplayWindow().size().x; int displayHeight = 1 + image->getDisplayWindow().size().y; if ( displayWidth % 2 != 0 || displayHeight % 2 != 0 ) { throw IOException("YUVImageWriter: Unsupported resolution"); } vector<string>::const_iterator rIt = std::find( names.begin(), names.end(), "R" ); vector<string>::const_iterator gIt = std::find( names.begin(), names.end(), "G" ); vector<string>::const_iterator bIt = std::find( names.begin(), names.end(), "B" ); if ( rIt == names.end() || gIt == names.end() || bIt == names.end() ) { throw IOException("YUVImageWriter: Unsupported channel names specified"); } std::ofstream outFile( fileName().c_str(), std::ios::trunc | std::ios::binary | std::ios::out ); if (! outFile.is_open() ) { throw IOException("Could not open '" + fileName() + "' for writing."); } Color3fVectorDataPtr rgbData = new Color3fVectorData(); rgbData->writable().resize( displayWidth * displayHeight, Color3f(0,0,0) ); try { for ( vector<string>::const_iterator it = names.begin(); it != names.end(); it++ ) { const string &name = *it; if (!( name == "R" || name == "G" || name == "B" ) ) { msg( Msg::Warning, "YUVImageWriter", format( "Channel \"%s\" was not encoded." ) % name ); continue; } int channelOffset = 0; if ( name == "R" ) { channelOffset = 0; } else if ( name == "G" ) { channelOffset = 1; } else { assert( name == "B" ); channelOffset = 2; } // get the image channel assert( image->variables.find( name ) != image->variables.end() ); DataPtr dataContainer = image->variables.find( name )->second.data; assert( dataContainer ); ChannelConverter converter( *it, image, dataWindow, channelOffset, rgbData ); despatchTypedData< ChannelConverter, TypeTraits::IsNumericVectorTypedData, ChannelConverter::ErrorHandler >( dataContainer, converter ); } V3fVectorDataPtr yuvData = new V3fVectorData(); yuvData->writable().resize( displayWidth * displayHeight, V3f(0,0,0) ); assert( yuvData->readable().size() == rgbData->readable().size() ); for ( int i = 0; i < displayWidth * displayHeight; i ++ ) { Color3f rgb = rgbData->readable()[i]; if ( rgb.x < 0.0 ) rgb.x = 0; if ( rgb.x > 1.0 ) rgb.x = 1.0; if ( rgb.y < 0.0 ) rgb.y = 0; if ( rgb.y > 1.0 ) rgb.y = 1.0; if ( rgb.z < 0.0 ) rgb.z = 0; if ( rgb.z > 1.0 ) rgb.z = 1.0; V3f yPbPr; float &Y = yPbPr.x; float &Pb = yPbPr.y; float &Pr = yPbPr.z; Y = kR * rgb.x + ( 1.0 - kR - kB ) * rgb.y + kB * rgb.z; Pb = 0.5 * ( rgb.z - Y ) / ( 1.0 - kB ); Pr = 0.5 * ( rgb.x - Y ) / ( 1.0 - kR ); V3f yCbCr = yPbPr; /// Map from 0-1 yCbCr.y += 0.5; yCbCr.z += 0.5; /// Apply any scaling for "head-room" and "toe-room" yCbCr.x = ( yCbCr.x * ( range.max.x - range.min.x ) ) + range.min.x; yCbCr.y = ( yCbCr.y * ( range.max.y - range.min.y ) ) + range.min.y; yCbCr.z = ( yCbCr.z * ( range.max.z - range.min.z ) ) + range.min.z; yuvData->writable()[i] = yCbCr; } /// \todo Chroma-filtering. Ideally we should do a proper sampled downsize of the chroma, rather than skipping data /// elements. This would avoid any aliasing. int lumaStepX = 1; int lumaStepY = 1; int chromaUStepX = 1; int chromaUStepY = 1; int chromaVStepX = 1; int chromaVStepY = 1; switch ( m_formatParameter->getNumericValue() ) { case YUV420P : /// Half res in U and V chromaUStepX = 2; chromaUStepY = 2; chromaVStepX = 2; chromaVStepY = 2; break; case YUV422P : /// Half horizonal res in U and V chromaUStepX = 2; chromaUStepY = 1; chromaVStepX = 2; chromaVStepY = 1; break; case YUV444P : /// Full res in U and V break; default : assert( false ); } ScaledDataConversion<float, unsigned char> converter; /// Y-plane for ( int y = 0; y < displayHeight; y += lumaStepX ) { for ( int x = 0; x < displayWidth; x += lumaStepY ) { const V3f yCbCr = yuvData->readable()[y*displayWidth + x]; const unsigned char val = converter( yCbCr.x ); outFile.write( (const char*)&val, 1 ); } } /// U-plane for ( int y = 0; y < displayHeight; y+=chromaUStepX ) { for ( int x = 0; x < displayWidth; x+=chromaUStepY ) { const V3f yCbCr = yuvData->readable()[y*displayWidth + x]; const unsigned char val = converter( yCbCr.y ); outFile.write( (const char*)&val, 1 ); } } /// V-plane for ( int y = 0; y < displayHeight; y+=chromaVStepX ) { for ( int x = 0; x < displayWidth; x+=chromaVStepY ) { const V3f yCbCr = yuvData->readable()[y*displayWidth + x]; const unsigned char val = converter( yCbCr.z ); outFile.write( (const char*)&val, 1 ); } } } catch ( std::exception &e ) { throw IOException( ( boost::format( "YUVImageWriter : %s" ) % e.what() ).str() ); } catch ( ... ) { throw IOException( "YUVImageWriter: Unexpected error" ); } }