Esempio n. 1
0
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" );
	}
}