示例#1
0
			ReturnType operator()( T *dataContainer )
	{
		assert( dataContainer );

		const typename T::ValueType &data = dataContainer->readable();

		ScaledDataConversion<typename T::ValueType::value_type, float> converter;

		typedef boost::multi_array_ref< const typename T::ValueType::value_type, 2 > SourceArray2D;
		typedef boost::multi_array_ref< unsigned int, 2 > TargetArray2D;
		
		// Grab the display and data windows to avoid dereferencing pointers during the tight loop later...
		const Box2i srcDisplayWindow = m_image->getDisplayWindow();
		const Box2i srcDataWindow = m_image->getDataWindow();
		
		const SourceArray2D sourceData( &data[0], extents[ srcDataWindow.size().y + 1 ][ srcDataWindow.size().x + 1 ] );
		TargetArray2D targetData( &m_imageBuffer[0], extents[ srcDisplayWindow.size().y + 1 ][ srcDisplayWindow.size().x + 1 ] );

		const Box2i copyRegion = boxIntersection( m_dataWindow, boxIntersection( srcDisplayWindow, srcDataWindow ) );
		
		const unsigned int boxOffsetX = copyRegion.min.x - srcDisplayWindow.min.x;
		const unsigned int boxOffsetY = copyRegion.min.y - srcDisplayWindow.min.y;

		for ( int y = copyRegion.min.y; y <= copyRegion.max.y ; y++ )
		{
			for ( int x = copyRegion.min.x; x <= copyRegion.max.x ; x++ )
			{
				targetData[ (y - copyRegion.min.y) + boxOffsetY ][ (x - copyRegion.min.x) + boxOffsetX ]
						|= std::min((unsigned int)1023, (unsigned int)(converter( sourceData[ y - srcDataWindow.min.y ][ x - srcDataWindow.min.x ] ) * 1023 )) << m_bitShift;
			}
		}
	};
示例#2
0
IECore::ImagePrimitivePtr ImagePlug::image() const
{
	Format format = formatPlug()->getValue();
	Box2i dataWindow = dataWindowPlug()->getValue();
	Box2i newDataWindow( Imath::V2i(0) );
	
	if( dataWindow.isEmpty() )
	{
		dataWindow = Box2i( Imath::V2i(0) );
	}
	else
	{
		newDataWindow = format.yDownToFormatSpace( dataWindow );
	}
	
	ImagePrimitivePtr result = new ImagePrimitive( newDataWindow, format.getDisplayWindow() );

	ConstStringVectorDataPtr channelNamesData = channelNamesPlug()->getValue();
	const vector<string> &channelNames = channelNamesData->readable();
	
	vector<float *> imageChannelData;
	for( vector<string>::const_iterator it = channelNames.begin(), eIt = channelNames.end(); it!=eIt; it++ )
	{
		FloatVectorDataPtr cd = new FloatVectorData;
		vector<float> &c = cd->writable();
		c.resize( result->variableSize( PrimitiveVariable::Vertex ), 0.0f );
		result->variables[*it] = PrimitiveVariable( PrimitiveVariable::Vertex, cd );
		imageChannelData.push_back( &(c[0]) );
	}
	
	parallel_for( blocked_range2d<size_t>( 0, dataWindow.size().x+1, tileSize(), 0, dataWindow.size().y+1, tileSize() ),
		      GafferImage::Detail::CopyTiles( imageChannelData, channelNames, channelDataPlug(), dataWindow, Context::current(), tileSize()) );
	
	return result;
}
示例#3
0
void DisplayIop::engine( int y, int x, int r, const DD::Image::ChannelSet &channels, DD::Image::Row &row )
{
	Channel outputChannels[4] = { Chan_Red, Chan_Green, Chan_Blue, Chan_Alpha };
	const char *inputChannels[] = { "R", "G", "B", "A", nullptr };

	const ImagePrimitive *image = nullptr;
	Box2i inputDataWindow;
	Box2i inputDisplayWindow;
	if( firstDisplayIop()->m_driver )
	{
		image = firstDisplayIop()->m_driver->image().get();
		inputDataWindow = image->getDataWindow();
		inputDisplayWindow = image->getDisplayWindow();
	}

	int i = 0;
	while( inputChannels[i] )
	{
		const FloatVectorData *inputData = image ? image->getChannel<float>( inputChannels[i] ) : nullptr;
		if( inputData )
		{
			float *output = row.writable( outputChannels[i] ) + x;
			// remap coordinate relative to our data window. nuke images have pixel origin at bottom,
			// cortex images have pixel origin at top.
			V2i pDataWindow = V2i( x, inputDisplayWindow.max.y - y ) - inputDataWindow.min;
			const float *input = &((inputData->readable())[pDataWindow.y*(inputDataWindow.size().x+1) + pDataWindow.x]);
			memcpy( output, input, (r-x) * sizeof( float ) );
		}
		else
		{
			row.erase( outputChannels[i] );
		}
		i++;
	}
}
示例#4
0
DataPtr TIFFImageReader::readTypedChannel( const std::string &name, const Box2i &dataWindow )
{
    typedef TypedData< std::vector< V > > TargetVector;

    typename TargetVector::Ptr dataContainer = new TargetVector();

    typename TargetVector::ValueType &data = dataContainer->writable();

    std::vector<std::string> names;
    channelNames( names );

    std::vector<std::string>::iterator it = find( names.begin(), names.end(), name );
    if ( it == names.end() )
    {
        throw IOException( (boost::format( "TIFFImageReader: Could not find channel \"%s\" while reading %s") % name % fileName() ).str() );
    }

    int channelOffset = std::distance( names.begin(), it );
    assert( channelOffset >= 0 );
    assert( channelOffset < (int)names.size() );

    if ( channelOffset >= m_samplesPerPixel )
    {
        throw IOException( (boost::format( "TIFFImageReader: Insufficient samples-per-pixel (%d) for reading channel \"%s\"") % m_samplesPerPixel % name).str() );
    }

    int area = ( dataWindow.size().x + 1 ) * ( dataWindow.size().y + 1 );
    assert( area >= 0 );
    data.resize( area );

    int dataWidth = 1 + dataWindow.size().x;
    int bufferDataWidth = 1 + m_dataWindow.size().x;

    ScaledDataConversion<T, V> converter;

    int dataY = 0;
    for ( int y = dataWindow.min.y - m_dataWindow.min.y ; y <= dataWindow.max.y - m_dataWindow.min.y ; ++y, ++dataY )
    {
        int dataX = 0;

        for ( int x = dataWindow.min.x - m_dataWindow.min.x;  x <= dataWindow.max.x - m_dataWindow.min.x ; ++x, ++dataX  )
        {
            const T* buf = reinterpret_cast< T* >( & m_buffer[0] );
            assert( buf );

            // \todo Currently, we only support PLANARCONFIG_CONTIG for TIFFTAG_PLANARCONFIG.
            assert( m_planarConfig ==  PLANARCONFIG_CONTIG );
            typename TargetVector::ValueType::size_type dataOffset = dataY * dataWidth + dataX;
            assert( dataOffset < data.size() );

            data[dataOffset] = converter( buf[ m_samplesPerPixel * ( y * bufferDataWidth + x ) + channelOffset ] );
        }
    }

    return dataContainer;
}
示例#5
0
void OSLImage::hashChannelNames( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const
{
	ImageProcessor::hashChannelNames( output, context, h );
	inPlug()->channelNamesPlug()->hash( h );

	const Box2i dataWindow = inPlug()->dataWindowPlug()->getValue();
	if( !dataWindow.isEmpty() )
	{
		ContextPtr c = new Context( *context, Context::Borrowed );
		c->set( ImagePlug::tileOriginContextName, ImagePlug::tileOrigin( dataWindow.min ) );
		Context::Scope s( c.get() );
		shadingPlug()->hash( h );
	}
}
示例#6
0
文件: Display.cpp 项目: daevid/gaffer
		virtual void imageData( const Imath::Box2i &box, const float *data, size_t dataSize )
		{
			Box2i yUpBox = m_gafferFormat.yDownToFormatSpace( box );
			const V2i boxMinTileOrigin = ImagePlug::tileOrigin( yUpBox.min );
			const V2i boxMaxTileOrigin = ImagePlug::tileOrigin( yUpBox.max );
			for( int tileOriginY = boxMinTileOrigin.y; tileOriginY <= boxMaxTileOrigin.y; tileOriginY += ImagePlug::tileSize() )
			{
				for( int tileOriginX = boxMinTileOrigin.x; tileOriginX <= boxMaxTileOrigin.x; tileOriginX += ImagePlug::tileSize() )
				{
					for( int channelIndex = 0, numChannels = channelNames().size(); channelIndex < numChannels; ++channelIndex )
					{
						const V2i tileOrigin( tileOriginX, tileOriginY );
						ConstFloatVectorDataPtr tileData = getTile( tileOrigin, channelIndex );
						if( !tileData )
						{
							// we've been sent data outside of the data window
							continue;
						}

						// we must create a new object to hold the updated tile data,
						// because the old one might well have been returned from
						// computeChannelData and be being held in the cache.
						FloatVectorDataPtr updatedTileData = tileData->copy();
						vector<float> &updatedTile = updatedTileData->writable();

						const Box2i tileBound( tileOrigin, tileOrigin + Imath::V2i( GafferImage::ImagePlug::tileSize() - 1 ) );
						const Box2i transferBound = IECore::boxIntersection( tileBound, yUpBox );
						for( int y = transferBound.min.y; y<=transferBound.max.y; ++y )
						{
							int srcY = m_gafferFormat.formatToYDownSpace( y );
							size_t srcIndex = ( ( srcY - box.min.y ) * ( box.size().x + 1 ) * numChannels ) + ( transferBound.min.x - box.min.x ) + channelIndex;
							size_t dstIndex = ( y - tileBound.min.y ) * ImagePlug::tileSize() + transferBound.min.x - tileBound.min.x;
							const size_t srcEndIndex = srcIndex + transferBound.size().x * numChannels;
							while( srcIndex <= srcEndIndex )
							{
								updatedTile[dstIndex] = data[srcIndex];
								srcIndex += numChannels;
								dstIndex++;
							}
						}

						setTile( tileOrigin, channelIndex, updatedTileData );
					}
				}
			}

			dataReceivedSignal()( this, box );
		}
示例#7
0
IECore::ImagePrimitivePtr ImagePlug::image() const
{
	Format format = formatPlug()->getValue();
	Box2i dataWindow = dataWindowPlug()->getValue();
	Box2i newDataWindow( Imath::V2i(0) );

	if( dataWindow.isEmpty() )
	{
		dataWindow = Box2i( Imath::V2i(0) );
	}
	else
	{
		newDataWindow = format.yDownToFormatSpace( dataWindow );
	}

	// use the default format if we don't have an explicit one.
	/// \todo: remove this once FormatPlug is handling it for
	/// us during ExecutableNode::execute (see issue #887).
	if( format.getDisplayWindow().isEmpty() )
	{
		format = Context::current()->get<Format>( Format::defaultFormatContextName, Format() );
	}
	
	ImagePrimitivePtr result = new ImagePrimitive( newDataWindow, format.getDisplayWindow() );
	
	ConstCompoundObjectPtr metadata = metadataPlug()->getValue();
	compoundObjectToCompoundData( metadata.get(), result->blindData() );
	
	ConstStringVectorDataPtr channelNamesData = channelNamesPlug()->getValue();
	const vector<string> &channelNames = channelNamesData->readable();

	vector<float *> imageChannelData;
	for( vector<string>::const_iterator it = channelNames.begin(), eIt = channelNames.end(); it!=eIt; it++ )
	{
		FloatVectorDataPtr cd = new FloatVectorData;
		vector<float> &c = cd->writable();
		c.resize( result->variableSize( PrimitiveVariable::Vertex ), 0.0f );
		result->variables[*it] = PrimitiveVariable( PrimitiveVariable::Vertex, cd );
		imageChannelData.push_back( &(c[0]) );
	}

	parallel_for( blocked_range3d<size_t>( 0, imageChannelData.size(), 1, 0, dataWindow.size().x+1, tileSize(), 0, dataWindow.size().y+1, tileSize() ),
		      GafferImage::Detail::CopyTiles( imageChannelData, channelNames, channelDataPlug(), dataWindow, Context::current(), tileSize()) );

	return result;
}
void ImagePrimitive::setDisplayWindow( const Box2i &displayWindow )
{
	if ( displayWindow.isEmpty() )
	{
		throw InvalidArgumentException( "ImagePrimitive: Cannot set displayWindow to the empty window" );
	}

	m_displayWindow = displayWindow;
}
void ImageDisplayDriver::imageData( const Box2i &box, const float *data, size_t dataSize )
{
	Box2i tmpBox = box;
	Box2i dataWindow = m_image->getDataWindow();
	tmpBox.extendBy( dataWindow );
	if ( tmpBox != dataWindow )
	{
		throw Exception("The box is outside image data window.");
	}

	int pixelSize = channelNames().size();
	if ( dataSize != (box.max.x - box.min.x + 1) * (box.max.y - box.min.y + 1) * channelNames().size() )
	{
		throw Exception("Invalid dataSize value.");
	}

	int channel, targetX, targetY, sourceWidth, sourceHeight, targetWidth;
	sourceWidth = box.max.x - box.min.x + 1;
	sourceHeight = box.max.y - box.min.y + 1;
	targetWidth = dataWindow.max.x - dataWindow.min.x + 1;
	channel = 0;
	targetX = box.min.x - dataWindow.min.x;
	targetY = box.min.y - dataWindow.min.y;

	for ( vector<string>::const_iterator it = channelNames().begin(); it != channelNames().end(); it++, channel++ )
	{
		vector< float > &target = boost::static_pointer_cast< FloatVectorData >(m_image->variables[ *it ].data)->writable();
		vector< float >::iterator targetIt;
		const float *sourceIt = data+channel;
		targetIt = target.begin()+targetWidth*targetY+targetX;

		for ( int y = 0; y < sourceHeight; y++ )
		{
			for ( int x = 0; x < sourceWidth; x++ )
			{
				*targetIt = *sourceIt;
				sourceIt += pixelSize;
				targetIt++;
			}
			targetIt += targetWidth - sourceWidth;
		}
	}
}
示例#10
0
    void iterateTiles(std::array<QuadSetup, N> quads, Intersector intersector) const
    {
        Box2i bounds;
        for (const auto &q : quads)
            bounds.grow(q.bounds);

        if (bounds.empty())
            return;

        uint32 minX = uint32(bounds.min().x()) & ~TileMask, maxX = bounds.max().x();
        uint32 minY = uint32(bounds.min().y()) & ~TileMask, maxY = bounds.max().y();

        for (auto &q : quads) {
            q.start(minX + TileSize*0.5f, minY + TileSize*0.5f);
            for (int k = 0; k < 3; ++k) {
                q.stepX[k] *= float(TileSize);
                q.stepY[k] *= float(TileSize);
            }
        }

        for (uint32 y = minY; y < maxY; y += TileSize) {
            for (auto &q : quads)
                q.beginRow();

            for (uint32 x = minX; x < maxX; x += TileSize) {
                float wMin = -1.0f;
                for (auto &q : quads)
                    wMin = max(wMin, q.reduce());
                if (wMin >= 0.0f) {
                    uint32 xjMax = min(x + TileSize, _res.x());
                    uint32 yjMax = min(y + TileSize, _res.y());
                    for (uint32 yj = y; yj < yjMax; ++yj)
                        for (uint32 xj = x; xj < xjMax; ++xj)
                            intersector(xj, yj, xj + yj*_res.x());
                }
                for (auto &q : quads)
                    q.stepCol();
            }
            for (auto &q : quads)
                q.endRow();
        }
    }
示例#11
0
void ImageSampler::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const
{
	ComputeNode::hash( output, context, h );

	if( output->parent<Plug>() == colorPlug() )
	{
		std::string channel = channelName( output );
		if( channel.size() )
		{
			V2f pixel = pixelPlug()->getValue();
			Box2i sampleWindow;
			sampleWindow.extendBy( V2i( pixel ) - V2i( 1 ) );
			sampleWindow.extendBy( V2i( pixel ) + V2i( 1 ) );
			Sampler sampler( imagePlug(), channel, sampleWindow );

			sampler.hash( h );
			h.append( pixel );
		}
	}
}
示例#12
0
IECore::ConstStringVectorDataPtr OSLImage::computeChannelNames( const Gaffer::Context *context, const GafferImage::ImagePlug *parent ) const
{
	ConstStringVectorDataPtr channelNamesData = inPlug()->channelNamesPlug()->getValue();

	set<string> result( channelNamesData->readable().begin(), channelNamesData->readable().end() );

	const Box2i dataWindow = inPlug()->dataWindowPlug()->getValue();
	if( !dataWindow.isEmpty() )
	{
		ContextPtr c = new Context( *context, Context::Borrowed );
		c->set( ImagePlug::tileOriginContextName, ImagePlug::tileOrigin( dataWindow.min ) );
		Context::Scope s( c.get() );

		ConstCompoundDataPtr shading = runTimeCast<const CompoundData>( shadingPlug()->getValue() );
		for( CompoundDataMap::const_iterator it = shading->readable().begin(), eIt = shading->readable().end(); it != eIt; ++it )
		{
			result.insert( it->first );
		}
	}

	return new StringVectorData( vector<string>( result.begin(), result.end() ) );
}
示例#13
0
		void operator()( const blocked_range3d<size_t>& r ) const
		{
			ContextPtr context = new Context( *m_parentContext, Context::Borrowed );
			Context::Scope scope( context.get() );
			
			const Box2i operationWindow( V2i( r.rows().begin()+m_dataWindow.min.x, r.cols().begin()+m_dataWindow.min.y ), V2i( r.rows().end()+m_dataWindow.min.x-1, r.cols().end()+m_dataWindow.min.y-1 ) );
			V2i minTileOrigin = ImagePlug::tileOrigin( operationWindow.min );
			V2i maxTileOrigin = ImagePlug::tileOrigin( operationWindow.max );
			size_t imageStride = m_dataWindow.size().x + 1;
			
			for( size_t channelIndex = r.pages().begin(); channelIndex < r.pages().end(); ++channelIndex )
			{
				context->set( ImagePlug::channelNameContextName, m_channelNames[channelIndex] );
				float *channelBegin = m_imageChannelData[channelIndex];
				
				for( int tileOriginY = minTileOrigin.y; tileOriginY <= maxTileOrigin.y; tileOriginY += m_tileSize )
				{
					for( int tileOriginX = minTileOrigin.x; tileOriginX <= maxTileOrigin.x; tileOriginX += m_tileSize )
					{
						context->set( ImagePlug::tileOriginContextName, V2i( tileOriginX, tileOriginY ) );
						
						Box2i tileBound( V2i( tileOriginX, tileOriginY ), V2i( tileOriginX + m_tileSize - 1, tileOriginY + m_tileSize - 1 ) );
						Box2i b = boxIntersection( tileBound, operationWindow );
						size_t tileStrideSize = sizeof(float) * ( b.size().x + 1 );
						
						ConstFloatVectorDataPtr tileData = m_channelDataPlug->getValue();
						const float *tileDataBegin = &(tileData->readable()[0]);

						for( int y = b.min.y; y<=b.max.y; y++ )
						{
							const float *tilePtr = tileDataBegin + (y - tileOriginY) * m_tileSize + (b.min.x - tileOriginX);
							float *channelPtr = channelBegin + ( m_dataWindow.size().y - ( y - m_dataWindow.min.y ) ) * imageStride + (b.min.x - m_dataWindow.min.x);
							std::memcpy( channelPtr, tilePtr, tileStrideSize );
						}
					}
				}
			}
		}
示例#14
0
void ImageSampler::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const
{
	if( output->parent<Plug>() == colorPlug() )
	{
		float sample = 0;

		std::string channel = channelName( output );
		if( channel.size() )
		{
			V2f pixel = pixelPlug()->getValue();
			Box2i sampleWindow;
			sampleWindow.extendBy( V2i( pixel ) - V2i( 1 ) );
			sampleWindow.extendBy( V2i( pixel ) + V2i( 1 ) );
			Sampler sampler( imagePlug(), channel, sampleWindow, Filter::create( filterPlug()->getValue() ) );
			sample = sampler.sample( pixel.x, pixel.y );
		}

		static_cast<FloatPlug *>( output )->setValue( sample );
		return;
	}

	ComputeNode::compute( output, context );
}
示例#15
0
static void medianCut( const Array2D &luminance, const Array2D &summedLuminance, MedianCutSampler::Projection projection, const Box2i &area, vector<Box2i> &areas, vector<V2f> &centroids, int depth, int maxDepth )
{
	float radiansPerPixel = M_PI / (luminance.shape()[1]);

	if( depth==maxDepth )
	{
		float totalEnergy = 0.0f;
		V2f position( 0.0f );
		for( int y=area.min.y; y<=area.max.y; y++ )
		{
			for( int x=area.min.x; x<=area.max.x; x++ )
			{
				float e = luminance[x][y];
				position += V2f( x, y ) * e;
				totalEnergy += e;
			}
		}

		position /= totalEnergy;
		centroids.push_back( position );
		areas.push_back( area );
	}
	else
	{
		// find cut dimension
		V2f size = area.size();
		if( projection==MedianCutSampler::LatLong )
		{
			float centreY = (area.max.y + area.min.y) / 2.0f;
			float centreAngle = (M_PI - radiansPerPixel) / 2.0f - centreY * radiansPerPixel;
			size.x *= cosf( centreAngle );
		}
		int cutAxis = size.x > size.y ? 0 : 1;
		float e = energy( summedLuminance, area );
		float halfE = e / 2.0f;
		Box2i lowArea = area;
		while( e > halfE )
		{
			lowArea.max[cutAxis] -= 1;
			e = energy( summedLuminance, lowArea );
		}
		Box2i highArea = area;
		highArea.min[cutAxis] = lowArea.max[cutAxis] + 1;
		medianCut( luminance, summedLuminance, projection, lowArea, areas, centroids, depth + 1, maxDepth );
		medianCut( luminance, summedLuminance, projection, highArea, areas, centroids, depth + 1, maxDepth );
	}
}
示例#16
0
void DPXImageWriter::writeImage( const vector<string> &names, const ImagePrimitive *image, const Box2i &dataWindow ) const
{
	// write the dpx in the standard 10bit log format
	ofstream out;
	out.open(fileName().c_str());
	if ( !out.is_open() )
	{
		throw IOException( "DPXImageWriter: Error writing to " + fileName() );
	}

	/// We'd like RGB to be at the front, in that order, because it seems that not all readers support the channel identifiers!
	vector<string> desiredChannelOrder;
	desiredChannelOrder.push_back( "R" );
	desiredChannelOrder.push_back( "G" );
	desiredChannelOrder.push_back( "B" );

	vector<string> namesCopy = names;
	vector<string> filteredNames;

	for ( vector<string>::const_iterator it = desiredChannelOrder.begin(); it != desiredChannelOrder.end(); ++it )
	{
		vector<string>::iterator res = find( namesCopy.begin(), namesCopy.end(), *it );
		if ( res != namesCopy.end() )
		{
			namesCopy.erase( res );
			filteredNames.push_back( *it );
		}
	}

	for ( vector<string>::const_iterator it = namesCopy.begin(); it != namesCopy.end(); ++it )
	{
		filteredNames.push_back( *it );
	}

	assert( names.size() == filteredNames.size() );

	Box2i displayWindow = image->getDisplayWindow();

	int displayWidth  = 1 + displayWindow.size().x;
	int displayHeight = 1 + displayWindow.size().y;

	// build the header
	DPXFileInformation fi;
	memset(&fi, 0, sizeof(fi));

	DPXImageInformation ii;
	memset(&ii, 0, sizeof(ii));

	DPXImageOrientation ioi;
	memset(&ioi, 0, sizeof(ioi));

	DPXMotionPictureFilm mpf;
	memset(&mpf, 0, sizeof(mpf));

	DPXTelevisionHeader th;
	memset(&th, 0, sizeof(th));

	fi.magic = asBigEndian<>( 0x53445058 );

	// compute data offsets
	fi.gen_hdr_size = sizeof(fi) + sizeof(ii) + sizeof(ioi);
	fi.gen_hdr_size = asBigEndian<>(fi.gen_hdr_size);

	fi.ind_hdr_size = sizeof(mpf) + sizeof(th);
	fi.ind_hdr_size = asBigEndian<>(fi.ind_hdr_size);

	int header_size = sizeof(fi) + sizeof(ii) + sizeof(ioi) + sizeof(mpf) + sizeof(th);
	fi.image_data_offset = header_size;
	fi.image_data_offset = asBigEndian<>(fi.image_data_offset);

	strcpy((char *) fi.vers, "V2.0");

	strncpy( (char *) fi.file_name, fileName().c_str(), sizeof( fi.file_name ) );

	// compute the current date and time
	boost::posix_time::ptime utc = boost::posix_time::second_clock::universal_time();
	boost::gregorian::date date = utc.date();
	boost::posix_time::time_duration time = utc.time_of_day();

	snprintf((char *) fi.create_time,  sizeof( fi.create_time ), "%04d:%02d:%02d:%02d:%02d:%02d:%s",
	        static_cast<int>( date.year() ),
		static_cast<int>( date.month() ),
		static_cast<int>( date.day() ),
	        time.hours(),
		time.minutes(),
		time.seconds(),
		"UTC"
	);

	snprintf((char *) fi.creator, sizeof( fi.creator ), "cortex");
	snprintf((char *) fi.project, sizeof( fi.project ), "cortex");
	snprintf((char *) fi.copyright, sizeof( fi.copyright ), "Unknown");

	ii.orientation = 0;    // left-to-right, top-to-bottom
	ii.element_number = 1;
	ii.pixels_per_line = displayWidth;
	ii.lines_per_image_ele = displayHeight;

	ii.element_number      = asBigEndian<>(ii.element_number);
	ii.pixels_per_line     = asBigEndian<>(ii.pixels_per_line);
	ii.lines_per_image_ele = asBigEndian<>(ii.lines_per_image_ele);

	for (int c = 0; c < 8; ++c)
	{
		DPXImageInformation::_image_element &ie = ii.image_element[c];
		ie.data_sign = 0;

		/// \todo Dcoument these constants
		ie.ref_low_data = 0;
		ie.ref_low_quantity = 0.0;
		ie.ref_high_data = 1023;
		ie.ref_high_quantity = 2.046;

		ie.ref_low_data = asBigEndian<>(ie.ref_low_data);
		ie.ref_low_quantity = asBigEndian<>(ie.ref_low_quantity);
		ie.ref_high_data = asBigEndian<>(ie.ref_high_data);
		ie.ref_high_quantity = asBigEndian<>(ie.ref_high_quantity);

		/// \todo Dcoument these constants
		ie.transfer = 1;
		ie.packing =  asBigEndian<short>( 1 );
		ie.bit_size = 10;
		ie.descriptor = 50;

		ie.data_offset = fi.image_data_offset;
	}

	ioi.x_offset = 0;
	ioi.y_offset = 0;

	// Write the header
	int image_data_size = sizeof( unsigned int ) * displayWidth * displayHeight;
	fi.file_size = header_size + image_data_size;

	fi.file_size = asBigEndian<>(fi.file_size);

	out.write(reinterpret_cast<char *>(&fi),  sizeof(fi));
	if ( out.fail() )
	{
		throw IOException( "DPXImageWriter: Error writing to " + fileName() );
	}

	out.write(reinterpret_cast<char *>(&ii),  sizeof(ii));
	if ( out.fail() )
	{
		throw IOException( "DPXImageWriter: Error writing to " + fileName() );
	}

	out.write(reinterpret_cast<char *>(&ioi), sizeof(ioi));
	if ( out.fail() )
	{
		throw IOException( "DPXImageWriter: Error writing to " + fileName() );
	}

	out.write(reinterpret_cast<char *>(&mpf), sizeof(mpf));
	if ( out.fail() )
	{
		throw IOException( "DPXImageWriter: Error writing to " + fileName() );
	}

	out.write(reinterpret_cast<char *>(&th),  sizeof(th));
	if ( out.fail() )
	{
		throw IOException( "DPXImageWriter: Error writing to " + fileName() );
	}

	// write the data
	vector<unsigned int> imageBuffer( displayWidth*displayHeight, 0 );

	int offset = 0;
	vector<string>::const_iterator i = filteredNames.begin();
	while (i != filteredNames.end())
	{
		if (!(*i == "R" || *i == "G" || *i == "B"))
		{
			msg( Msg::Warning, "DPXImageWriter::write", format( "Channel \"%s\" was not encoded." ) % *i );
			++i;
			continue;
		}

		int bpp = 10;
		unsigned int shift = (32 - bpp) - (offset*bpp);

		assert( image->variables.find( *i ) != image->variables.end() );
		DataPtr dataContainer = image->variables.find( *i )->second.data;
		assert( dataContainer );

		ChannelConverter converter( *i, image, dataWindow, shift, imageBuffer );

		despatchTypedData<
			ChannelConverter,
			TypeTraits::IsNumericVectorTypedData,
			ChannelConverter::ErrorHandler
		>( dataContainer.get(), converter );

		++i;
		++offset;
	}

	// write the buffer
	for (int i = 0; i < displayWidth * displayHeight; ++i)
	{
		imageBuffer[i] = asBigEndian<>(imageBuffer[i]);
		out.write( (const char *) (&imageBuffer[i]), sizeof(unsigned int) );
		if ( out.fail() )
		{
			throw IOException( "DPXImageWriter: Error writing to " + fileName() );
		}
	}
}
示例#17
0
ObjectPtr EnvMapSampler::doOperation( const CompoundObject * operands )
{
	ImagePrimitivePtr image = static_cast<ImagePrimitive *>( imageParameter()->getValue() )->copy();
	Box2i dataWindow = image->getDataWindow();

	// find the rgb channels
	ConstFloatVectorDataPtr redData = image->getChannel<float>( "R" );
	ConstFloatVectorDataPtr greenData = image->getChannel<float>( "G" );
	ConstFloatVectorDataPtr blueData = image->getChannel<float>( "B" );
	if( !(redData && greenData && blueData) )
	{
		throw Exception( "Image does not contain valid RGB float channels." );
	}
	const vector<float> &red = redData->readable();
	const vector<float> &green = greenData->readable();
	const vector<float> &blue = blueData->readable();

	// get a luminance channel
	LuminanceOpPtr luminanceOp = new LuminanceOp();
	luminanceOp->inputParameter()->setValue( image );
	luminanceOp->copyParameter()->getTypedValue() = false;
	luminanceOp->removeColorPrimVarsParameter()->getTypedValue() = false;
	luminanceOp->operate();

	// do the median cut thing to get some samples
	MedianCutSamplerPtr sampler = new MedianCutSampler;
	sampler->imageParameter()->setValue( image );
	sampler->subdivisionDepthParameter()->setNumericValue( subdivisionDepthParameter()->getNumericValue() );
	ConstCompoundObjectPtr samples = boost::static_pointer_cast<CompoundObject>( sampler->operate() );
	const vector<V2f> &centroids = boost::static_pointer_cast<V2fVectorData>( samples->members().find( "centroids" )->second )->readable();
	const vector<Box2i> &areas = boost::static_pointer_cast<Box2iVectorData>( samples->members().find( "areas" )->second )->readable();

	// get light directions and colors from the samples
	V3fVectorDataPtr directionsData = new V3fVectorData;
	Color3fVectorDataPtr colorsData = new Color3fVectorData;
	vector<V3f> &directions = directionsData->writable();
	vector<Color3f> &colors = colorsData->writable();

	float radiansPerPixel = M_PI / (dataWindow.size().y + 1);
	float angleAtTop = ( M_PI - radiansPerPixel ) / 2.0f;

	for( unsigned i=0; i<centroids.size(); i++ )
	{
		const Box2i &area = areas[i];
		Color3f color( 0 );
		for( int y=area.min.y; y<=area.max.y; y++ )
		{
			int yRel = y - dataWindow.min.y;

			float angle = angleAtTop - yRel * radiansPerPixel;
			float weight = cosf( angle );
			int index = (area.min.x - dataWindow.min.x) + (dataWindow.size().x + 1 ) * yRel;
			for( int x=area.min.x; x<=area.max.x; x++ )
			{
				color[0] += weight * red[index];
				color[1] += weight * green[index];
				color[2] += weight * blue[index];
				index++;
			}
		}
		color /= red.size();
		colors.push_back( color );

		float phi = angleAtTop - (centroids[i].y - dataWindow.min.y) * radiansPerPixel;

		V3f direction;
		direction.y = sinf( phi );
		float r = cosf( phi );
		float theta = 2 * M_PI * lerpfactor( (float)centroids[i].x, (float)dataWindow.min.x, (float)dataWindow.max.x );
		direction.x = r * cosf( theta );
		direction.z = r * sinf( theta );

		directions.push_back( -direction ); // negated so we output the direction the light shines in
	}

	// return the result
	CompoundObjectPtr result = new CompoundObject;
	result->members()["directions"] = directionsData;
	result->members()["colors"] = colorsData;
	return result;
}
示例#18
0
ObjectPtr MedianCutSampler::doOperation( const CompoundObject * operands )
{
	ImagePrimitivePtr image = static_cast<ImagePrimitive *>( imageParameter()->getValue() )->copy();
	Box2i dataWindow = image->getDataWindow();

	// find the right channel
	const std::string &channelName = m_channelNameParameter->getTypedValue();
	FloatVectorDataPtr luminance = image->getChannel<float>( channelName );
	if( !luminance )
	{
		throw Exception( str( format( "No FloatVectorData channel named \"%s\"." ) % channelName ) );
	}

	// if the projection requires it, weight the luminances so they're less
	// important towards the poles of the sphere
	Projection projection = (Projection)m_projectionParameter->getNumericValue();
	if( projection==LatLong )
	{
		float radiansPerPixel = M_PI / (dataWindow.size().y + 1);
		float angle = ( M_PI - radiansPerPixel ) / 2.0f;

		float *p = &(luminance->writable()[0]);

		for( int y=dataWindow.min.y; y<=dataWindow.max.y; y++ )
		{
			float *pEnd = p + dataWindow.size().x + 1;
			float w = cosf( angle );
			while( p < pEnd )
			{
				*p *= w;
				p++;
			}
			angle -= radiansPerPixel;
		}

	}

	// make a summed area table for speed
	FloatVectorDataPtr summedLuminance = luminance;
	luminance = luminance->copy(); // we need this for the centroid computation

	SummedAreaOpPtr summedAreaOp = new SummedAreaOp();
	summedAreaOp->inputParameter()->setValue( image );
	summedAreaOp->copyParameter()->setTypedValue( false );
	summedAreaOp->channelNamesParameter()->getTypedValue().clear();
	summedAreaOp->channelNamesParameter()->getTypedValue().push_back( "Y" );
	summedAreaOp->operate();

	// do the median cut thing
	CompoundObjectPtr result = new CompoundObject;
	V2fVectorDataPtr centroids = new V2fVectorData;
	Box2iVectorDataPtr areas = new Box2iVectorData;
	result->members()["centroids"] = centroids;
	result->members()["areas"] = areas;

	dataWindow.max -= dataWindow.min;
	dataWindow.min -= dataWindow.min; // let's start indexing from 0 shall we?
	Array2D array( &(luminance->writable()[0]), extents[dataWindow.size().x+1][dataWindow.size().y+1], fortran_storage_order() );
	Array2D summedArray( &(summedLuminance->writable()[0]), extents[dataWindow.size().x+1][dataWindow.size().y+1], fortran_storage_order() );
	medianCut( array, summedArray, projection, dataWindow, areas->writable(), centroids->writable(), 0, subdivisionDepthParameter()->getNumericValue() );

	return result;
}
示例#19
0
static bool triangleRender(const Triangle2i& triangle,SmartPointer<Texture> _rgb,SmartPointer<Texture> _alpha,Color4f color,const Box2i& box, bool bWrite)
{
	XgeDebugAssert(_rgb->bpp==24 && _alpha->bpp==8 && _rgb->width==_alpha->width && _rgb->height==_alpha->height);

	int x1=triangle.p0.x,y1=triangle.p0.y;
	int x2=triangle.p1.x,y2=triangle.p1.y;
	int x3=triangle.p2.x,y3=triangle.p2.y;

	unsigned char* rgb     =_rgb  ->buffer;
	unsigned char* alpha   =_alpha->buffer;
	int            W       =_rgb  ->width;

	int minx = min3(x1, x2, x3) , maxx = max3(x1, x2, x3);
	int miny = min3(y1, y2, y3) , maxy = max3(y1, y2, y3);

	//must be completely be contained in the current area
	if (!(minx>=box.left() && maxx<=box.right() && miny>=box.bottom() && maxy<=box.top()))
	{
		XgeDebugAssert(!bWrite);
		return false;
	}

	int Sign=(((x2-x1)*(y3-y1)-(x3-x1)*(y2-y1))>=0)?(+1):(-1);

	for(int y = miny; y <= maxy; y++)
	for(int x = minx; x <= maxx; x++)
	{
		// if the pixel is inside....
		if
		(
			(Sign*((x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)) >= 0) &&
			(Sign*((x3 - x2) * (y - y2) - (y3 - y2) * (x - x2)) >= 0) &&
			(Sign*((x1 - x3) * (y - y3) - (y1 - y3) * (x - x3)) >= 0)
		)
		{
			int idx=y*W+x;

			//test if it has already been written
			//note: for each pixel consider also the pixel (-1,0) (+1,0) (0,-1) (0,+1)
			if (!bWrite && (alpha[idx] || alpha[idx-1] || alpha[idx+1] ||alpha[idx-W] || alpha[idx+W]))
				return false;
			
			//if write....
			if (bWrite)
			{
				unsigned char R=(unsigned char)(255.0f*color.r);
				unsigned char G=(unsigned char)(255.0f*color.g);
				unsigned char B=(unsigned char)(255.0f*color.b);

				rgb[idx*3+0] = R;
				rgb[idx*3+1] = G;
				rgb[idx*3+2] = B;

				rgb[(idx-1)*3+0] = rgb[(idx+1)*3+0] = rgb[(idx-W)*3+0] = rgb[(idx+W)*3+0] =R ;
				rgb[(idx-1)*3+1] = rgb[(idx+1)*3+1] = rgb[(idx-W)*3+1] = rgb[(idx+W)*3+1] =G ;
				rgb[(idx-1)*3+2] = rgb[(idx+1)*3+2] = rgb[(idx-W)*3+2]  =rgb[(idx+W)*3+2] =B ;

				//sign as drawn
				alpha [idx  ] = 0x01;
				alpha [idx-1] = 0x01;
				alpha [idx+1] = 0x01;
				alpha [idx-W] = 0x01;
				alpha [idx+W] = 0x01;
			}
		}
	}

	return true;
}
示例#20
0
DataPtr SGIImageReader::readTypedChannel( const std::string &name, const Box2i &dataWindow )
{
	typedef TypedData< std::vector< V > > TargetVector;

	assert( open() );

	assert( m_header );

	typename TypedData< std::vector<T > >::ConstPtr dataBuffer = assertedStaticCast< TypedData< std::vector<T > > >( m_buffer );
	assert( dataBuffer );

	const typename TypedData< std::vector<T > >::ValueType &buffer = dataBuffer->readable();

	assert( m_header->m_channelOffsets.find( name ) != m_header->m_channelOffsets.end() );
	int channelOffset = m_header->m_channelOffsets[name];

	typename TargetVector::Ptr dataContainer = new TargetVector();

	typename TargetVector::ValueType &data = dataContainer->writable();
	int area = ( dataWindow.size().x + 1 ) * ( dataWindow.size().y + 1 );
	assert( area >= 0 );
	data.resize( area );

	int dataWidth = 1 + dataWindow.size().x;

	Box2i wholeDataWindow = this->dataWindow();

	int wholeDataHeight = 1 + wholeDataWindow.size().y;
	int wholeDataWidth = 1 + wholeDataWindow.size().x;

	const int yMin = dataWindow.min.y - wholeDataWindow.min.y;
	const int yMax = dataWindow.max.y - wholeDataWindow.min.y;

	const int xMin = dataWindow.min.x - wholeDataWindow.min.x;
	const int xMax = dataWindow.max.x - wholeDataWindow.min.x;

	ScaledDataConversion<T, V> converter;

	if ( m_header->m_fileHeader.m_storageFormat >= 1 ) /// RLE
	{
		int dataY = 0;
		for ( int y = yMin ; y <= yMax ; ++y, ++dataY )
		{
			assert( yMin >= 0 );
			assert( yMin < wholeDataHeight );

			/// Images are unencoded "upside down" (flipped in Y), for our purposes
			uint32_t rowOffset = m_header->m_offsetTable[ channelOffset * wholeDataHeight + ( wholeDataHeight - 1 - y ) ];

			if ( rowOffset >= buffer.size() )
			{
				throw IOException( "SGIImageReader: Invalid RLE row offset found while reading " + fileName() );
			}

			std::vector<T> scanline( wholeDataWidth );

			uint32_t scanlineOffset = 0;
			bool done = false;

			while ( !done )
			{
				T pixel = buffer[ rowOffset ++ ];

				T count = pixel & 0x7f;

				if ( count == 0 )
				{
					done = true;
				}
				else
				{
					if ( pixel & 0x80 )
					{
						if ( scanlineOffset + count - 1 >= scanline.size() || rowOffset + count - 1 >= buffer.size() )
						{
							throw IOException( "SGIImageReader: Invalid RLE data found while reading " + fileName() );
						}

						while ( count -- )
						{
							assert( scanlineOffset < scanline.size() );
							assert( rowOffset < buffer.size() );
							scanline[ scanlineOffset++ ] = buffer[ rowOffset ++ ];
						}
					}
					else
					{
						if ( scanlineOffset + count - 1 >= scanline.size() || rowOffset - 1 >= buffer.size() )
						{
							throw IOException( "SGIImageReader: Invalid RLE data found while reading " + fileName() );
						}

						assert( rowOffset < buffer.size() );
						pixel = buffer[ rowOffset ++ ];

						while ( count -- )
						{
							assert( scanlineOffset < scanline.size() );
							scanline[ scanlineOffset++ ] = pixel;
						}
					}
				}
			}

			if ( scanlineOffset != scanline.size() )
			{
				throw IOException( "SGIImageReader: Error occurred during RLE decode while reading " + fileName() );
			}

			int dataOffset = dataY * dataWidth;

			for ( int x = xMin; x <= xMax ; ++x, ++dataOffset  )
			{
				data[dataOffset] = converter( scanline[x] );
			}
		}
	}
	else /// Not RLE
	{
		int dataY = 0;

		for ( int y = dataWindow.min.y; y <= dataWindow.max.y; ++y, ++dataY )
		{
			typename TargetVector::ValueType::size_type dataOffset = dataY * dataWidth;

			for ( int x = dataWindow.min.x; x <= dataWindow.max.x; ++x, ++dataOffset )
			{
				assert( dataOffset < data.size() );

				/// Images are unencoded "upside down" (flipped in Y), for our purposes
				data[dataOffset] = converter( buffer[ ( channelOffset * wholeDataHeight * wholeDataWidth ) + ( wholeDataHeight - 1 - y  ) * wholeDataWidth + x  ] );
			}
		}
	}

	return dataContainer;
}
示例#21
0
void TGAImageWriter::writeImage( const vector<string> &names, const ImagePrimitive * image, const Box2i &dataWindow ) const
{
	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" );
	vector<string>::const_iterator aIt = std::find( names.begin(), names.end(), "A" );

	int numChannels = 0;
	if ( rIt != names.end() && gIt != names.end() && bIt != names.end() )
	{
		numChannels = 3;
	}
	if ( aIt != names.end() )
	{
		numChannels ++;
	}

	if ( numChannels != 3 && numChannels != 4 )
	{
		throw IOException( "TGAImageWriter: Unsupported channel names specified." );
	}

	std::ofstream out;
	out.open( fileName().c_str() );
	if ( !out.is_open() )
	{
		throw IOException( "TGAImageWriter: Could not open " + fileName() );
	}

	vector<string> desiredChannelOrder;
	desiredChannelOrder.push_back( "B" );
	desiredChannelOrder.push_back( "G" );
	desiredChannelOrder.push_back( "R" );
	desiredChannelOrder.push_back( "A" );

	vector<string> namesCopy = names;
	vector<string> filteredNames;

	for ( vector<string>::const_iterator it = desiredChannelOrder.begin(); it != desiredChannelOrder.end(); ++it )
	{
		vector<string>::iterator res = find( namesCopy.begin(), namesCopy.end(), *it );
		if ( res != namesCopy.end() )
		{
			namesCopy.erase( res );
			filteredNames.push_back( *it );
		}
	}

	for ( vector<string>::const_iterator it = namesCopy.begin(); it != namesCopy.end(); ++it )
	{
		filteredNames.push_back( *it );
	}

	assert( names.size() == filteredNames.size() );

	Box2i displayWindow = image->getDisplayWindow();

	int displayWidth  = 1 + displayWindow.size().x;
	int displayHeight = 1 + displayWindow.size().y;

	// Write the header

	/// ID Length
	writeLittleEndian<uint8_t>( out, 0 );

	/// Color Map Type
	writeLittleEndian<uint8_t>( out, 0 );

	/// Image Type
	writeLittleEndian<uint8_t>( out, 2 );

	/// Color Map Specification
	writeLittleEndian<uint16_t>( out, 0 );
	writeLittleEndian<uint16_t>( out, 0 );
	writeLittleEndian<uint8_t>( out, 0 );

	/// Image Specification
	writeLittleEndian<uint16_t>( out, 0 );
	writeLittleEndian<uint16_t>( out, 0 );
	writeLittleEndian<uint16_t>( out, displayWidth );
	writeLittleEndian<uint16_t>( out, displayHeight );
	writeLittleEndian<uint8_t>( out, numChannels * 8 );
	writeLittleEndian<uint8_t>( out, ( numChannels == 4 ? 8 : 0 ) + 32 );

	// Encode the image buffer
	std::vector<uint8_t> imageBuffer( displayWidth*displayHeight*numChannels, 0 );

	int offset = 0;
	for ( vector<string>::const_iterator it = filteredNames.begin(); it != filteredNames.end(); ++it )
	{
		const std::string &name = *it;
		if ( !( name == "R" || name == "G" || name == "B" || name == "A" ) )
		{
			msg( Msg::Warning, "TGAImageWriter::write", format( "Channel \"%s\" was not encoded." ) % name );
		}
		else
		{
			assert( image->variables.find( name ) != image->variables.end() );
			DataPtr dataContainer = image->variables.find( name )->second.data;
			assert( dataContainer );
			ChannelConverter converter( name, image, dataWindow, numChannels, offset, imageBuffer );
			despatchTypedData<
			ChannelConverter,
			TypeTraits::IsNumericVectorTypedData,
			ChannelConverter::ErrorHandler
			>( dataContainer, converter );

			++offset;
		}
	}

	/// Write the Buffer
	for ( int i = 0; i < displayWidth*displayHeight*numChannels; ++i )
	{
		assert( i < ( int )imageBuffer.size() );
		writeLittleEndian( out, imageBuffer[i] );
		if ( out.fail() )
		{
			throw IOException( "TGAImageWriter: Error writing to " + fileName() );
		}
	}
}
示例#22
0
void
makeMultiView (const vector <string> &viewNames,
	       const vector <const char *> &inFileNames,
	       const char *outFileName,
	       Compression compression,
	       bool verbose)
{
    Header header;
    Image image;
    FrameBuffer outFb;

    //
    // Find the size of the dataWindow, check files
    //
    
    Box2i d;
    
    
    for (int i = 0; i < viewNames.size(); ++i)
    {
	InputFile in (inFileNames[i]);

	if (verbose)
	{
	    cout << "reading file " << inFileNames[i] << " "
		    "for " << viewNames[i] << " view" << endl;
	}

	if (hasMultiView (in.header()))
	{
	    THROW (IEX_NAMESPACE::NoImplExc,
		   "The image in file " << inFileNames[i] << " is already a "
		   "multi-view image.  Cannot combine multiple multi-view "
		   "images.");
	}

        header = in.header();
	if (i == 0)
        {
             d=header.dataWindow();
	}else{
             d.extendBy(header.dataWindow());
        }
    }
    
    
    image.resize (d);
    
    header.dataWindow()=d;
    
    // blow away channels; we'll rebuild them
    header.channels()=ChannelList();
    
    
    //
    // Read the input image files
    //

    for (int i = 0; i < viewNames.size(); ++i)
    {
	InputFile in (inFileNames[i]);

	if (verbose)
	{
	    cout << "reading file " << inFileNames[i] << " "
		    "for " << viewNames[i] << " view" << endl;
	}

	FrameBuffer inFb;

	for (ChannelList::ConstIterator j = in.header().channels().begin();
	     j != in.header().channels().end();
	     ++j)
	{
	    const Channel &inChannel = j.channel();
	    string inChanName = j.name();
	    string outChanName = insertViewName (inChanName, viewNames, i);

	    image.addChannel (outChanName, inChannel);
            image.channel(outChanName).black();
            
	    header.channels().insert (outChanName, inChannel);

	    inFb.insert  (inChanName,  image.channel(outChanName).slice());
	    outFb.insert (outChanName, image.channel(outChanName).slice());
	}

	in.setFrameBuffer (inFb);
	in.readPixels (in.header().dataWindow().min.y, in.header().dataWindow().max.y);
    }

    //
    // Write the output image file
    //

    {
	header.compression() = compression;
	addMultiView (header, viewNames);

	OutputFile out (outFileName, header);

	if (verbose)
	    cout << "writing file " << outFileName << endl;

	out.setFrameBuffer (outFb);

	out.writePixels
	    (header.dataWindow().max.y - header.dataWindow().min.y + 1);
    }
}