DataPtr JPEGImageReader::readTypedChannel( const std::string &name, const Imath::Box2i &dataWindow, int channelOffset ) { int area = ( dataWindow.size().x + 1 ) * ( dataWindow.size().y + 1 ); assert( area >= 0 ); int dataWidth = 1 + dataWindow.size().x; int dataY = 0; typedef TypedData< std::vector< V > > TargetVector; typename TargetVector::Ptr dataContainer = new TargetVector(); typename TargetVector::ValueType &data = dataContainer->writable(); data.resize( area ); ScaledDataConversion< unsigned char, V> converter; 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() ); data[dataOffset] = converter( m_buffer[ m_numChannels * ( y * m_bufferWidth + x ) + channelOffset ] ); } } return dataContainer; }
void ImageStats::compute( ValuePlug *output, const Context *context ) const { const int colorIndex = ::colorIndex( output ); if( colorIndex == -1 ) { // Not a plug we know about ComputeNode::compute( output, context ); return; } const std::string channelName = this->channelName( colorIndex ); const Imath::Box2i area = areaPlug()->getValue(); if( channelName.empty() || BufferAlgo::empty( area ) ) { output->setToDefault(); return; } // Loop over the ROI and compute the min, max and average channel values and then set our outputs. Sampler s( inPlug(), channelName, area ); float min = Imath::limits<float>::max(); float max = Imath::limits<float>::min(); double sum = 0.; for( int y = area.min.y; y < area.max.y; ++y ) { for( int x = area.min.x; x < area.max.x; ++x ) { float v = s.sample( x, y ); min = std::min( v, min ); max = std::max( v, max ); sum += v; } } if( output->parent<Plug>() == minPlug() ) { static_cast<FloatPlug *>( output )->setValue( min ); } else if( output->parent<Plug>() == maxPlug() ) { static_cast<FloatPlug *>( output )->setValue( max ); } else if( output->parent<Plug>() == averagePlug() ) { static_cast<FloatPlug *>( output )->setValue( sum / double( (area.size().x) * (area.size().y) ) ); } }
void make_color_bars( const image_view_t& view, const Imath::Box2i& domain, const Imath::Box2i& defined) { typedef detail::color_bars_fn deref_t; typedef deref_t::point_t point_t; typedef boost::gil::virtual_2d_locator<deref_t,false> locator_t; typedef boost::gil::image_view<locator_t> my_virt_view_t; point_t dims( domain.size().x+1, domain.size().y+1); my_virt_view_t bars( dims, locator_t( point_t(0,0), point_t(1,1), deref_t( dims))); boost::gil::copy_pixels( boost::gil::subimage_view( bars, defined.min.x - domain.min.x, defined.min.y - domain.min.y, defined.size().x+1, defined.size().y+1), view); }
Imath::M33f Transform2DPlug::matrix( const Imath::Box2i &displayWindow, double pixelAspect ) const { // We need to transform from image space (with 0x0 being the bottom left) // to Gadget space (where 0x0 is the top left). To do this, we need to know the // size of the Format. ///\todo: We don't handle the pixel aspect of the format here but we should! float formatHeight = displayWindow.size().y + 1; M33f p; V2f pivotVec = pivotPlug()->getValue(); pivotVec.y = formatHeight - pivotVec.y; p.translate( pivotVec ); M33f t; V2f translateVec = translatePlug()->getValue(); translateVec.y *= -1.; t.translate( translateVec ); M33f r; r.rotate( IECore::degreesToRadians( rotatePlug()->getValue() ) ); M33f s; s.scale( scalePlug()->getValue() ); M33f pi; pi.translate( pivotVec*Imath::V2f(-1.f) ); M33f result = pi * s * r * t * p; return result; }
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 ); }
DataPtr CINImageReader::readTypedChannel( const std::string &name, const Imath::Box2i &dataWindow ) { typedef TypedData< std::vector< V > > TargetVector; // figure out the offset into the bitstream for the given channel assert( m_header->m_channelOffsets.find( name ) != m_header->m_channelOffsets.end() ); int channelOffset = m_header->m_channelOffsets[name]; assert( (int)m_header->m_imageInformation.channel_information[channelOffset].bpp == 10 ); int bpp = m_header->m_imageInformation.channel_information[channelOffset].bpp; // todo: make sure ushort is enough for the current bpp int ushortShift = sizeof(unsigned short)*8 - bpp; // form the mask for this channel unsigned int mask = 0; for (int pi = 0; pi < bpp; ++pi) { mask = 1 + (mask << 1); } mask <<= ((32 - bpp) - channelOffset * bpp); ScaledDataConversion< unsigned short, V> converter; 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(); 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; int dataY = 0; for ( int y = yMin ; y <= yMax ; ++y, ++dataY ) { typename TargetVector::ValueType::size_type dataOffset = dataY * dataWidth; std::vector<unsigned int>::size_type bufferOffset = y * m_bufferWidth + xMin; for ( int x = xMin; x <= xMax ; ++x, ++dataOffset, ++bufferOffset ) { assert( dataOffset < data.size() ); unsigned int cell = m_buffer[ bufferOffset ]; if ( m_reverseBytes ) { cell = reverseBytes( cell ); } // assume we have 10bit log, two wasted bits aligning to 32 longword unsigned short cv = (unsigned short) ( ( mask & cell ) >> ( 2 + ( 2 - channelOffset ) * bpp ) ); assert( cv < 1024 ); data[dataOffset] = converter( (cv << ushortShift) + ((1 << ushortShift) - 1) ); } } return dataContainer; }
gray_image_view_t buffer_t::gray_subimage_view( const Imath::Box2i& area) const { check_area_inside_image( area); return boost::gil::subimage_view( gray_view(), area.min.x - bounds_.min.x, area.min.y - bounds_.min.y, area.size().x+1, area.size().y+1); }
///\todo: It seems that if a JPG is written with RGBA channels the output is wrong but it should be supported. Find out why and fix it. /// There is a test case in ImageWriterTest which checks the output of the jpg writer against an incorrect image and it will fail if it is equal to the writer output. void ImageWriter::execute( const Contexts &contexts ) const { if( !inPlug()->getInput<ImagePlug>() ) { throw IECore::Exception( "No input image." ); } // Loop over the execution contexts... for( Contexts::const_iterator it = contexts.begin(), eIt = contexts.end(); it != eIt; it++ ) { Context::Scope scopedContext( it->get() ); std::string fileName = fileNamePlug()->getValue(); fileName = (*it)->substitute( fileName ); boost::shared_ptr< ImageOutput > out( ImageOutput::create( fileName.c_str() ) ); if ( !out ) { throw IECore::Exception( boost::str( boost::format( "Invalid filename: %s" ) % fileName ) ); } // Grab the intersection of the channels from the "channels" plug and the image input to see which channels we are to write out. IECore::ConstStringVectorDataPtr channelNamesData = inPlug()->channelNamesPlug()->getValue(); std::vector<std::string> maskChannels = channelNamesData->readable(); channelsPlug()->maskChannels( maskChannels ); const int nChannels = maskChannels.size(); // Get the image channel data. IECore::ImagePrimitivePtr imagePtr( inPlug()->image() ); // Get the image's display window. const Imath::Box2i displayWindow( imagePtr->getDisplayWindow() ); const int displayWindowWidth = displayWindow.size().x+1; const int displayWindowHeight = displayWindow.size().y+1; // Get the image's data window and if it then set a flag. bool imageIsBlack = false; Imath::Box2i dataWindow( imagePtr->getDataWindow() ); if ( inPlug()->dataWindowPlug()->getValue().isEmpty() ) { dataWindow = displayWindow; imageIsBlack = true; } int dataWindowWidth = dataWindow.size().x+1; int dataWindowHeight = dataWindow.size().y+1; // Create the image header. ImageSpec spec( dataWindowWidth, dataWindowHeight, nChannels, TypeDesc::FLOAT ); // Add the channel names to the header whilst getting pointers to the channel data. std::vector<const float*> channelPtrs; spec.channelnames.clear(); for ( std::vector<std::string>::iterator channelIt( maskChannels.begin() ); channelIt != maskChannels.end(); channelIt++ ) { spec.channelnames.push_back( *channelIt ); IECore::FloatVectorDataPtr dataPtr = imagePtr->getChannel<float>( *channelIt ); channelPtrs.push_back( &(dataPtr->readable()[0]) ); // OIIO has a special attribute for the Alpha and Z channels. If we find some, we should tag them... if ( *channelIt == "A" ) { spec.alpha_channel = channelIt-maskChannels.begin(); } else if ( *channelIt == "Z" ) { spec.z_channel = channelIt-maskChannels.begin(); } } // Specify the display window. spec.full_x = displayWindow.min.x; spec.full_y = displayWindow.min.y; spec.full_width = displayWindowWidth; spec.full_height = displayWindowHeight; spec.x = dataWindow.min.x; spec.y = dataWindow.min.y; if ( !out->open( fileName, spec ) ) { throw IECore::Exception( boost::str( boost::format( "Could not open \"%s\", error = %s" ) % fileName % out->geterror() ) ); } // Only allow tiled output if our file format supports it. int writeMode = writeModePlug()->getValue() & out->supports( "tile" ); if ( writeMode == Scanline ) { // Create a buffer for the scanline. float scanline[ nChannels*dataWindowWidth ]; if ( imageIsBlack ) { memset( scanline, 0, sizeof(float) * nChannels*dataWindowWidth ); for ( int y = spec.y; y < spec.y + dataWindowHeight; ++y ) { if ( !out->write_scanline( y, 0, TypeDesc::FLOAT, &scanline[0] ) ) { throw IECore::Exception( boost::str( boost::format( "Could not write scanline to \"%s\", error = %s" ) % fileName % out->geterror() ) ); } } } else { // Interleave the channel data and write it by scanline to the file. for ( int y = spec.y; y < spec.y + dataWindowHeight; ++y ) { for ( std::vector<const float *>::iterator channelDataIt( channelPtrs.begin() ); channelDataIt != channelPtrs.end(); channelDataIt++ ) { float *outPtr = &scanline[0] + (channelDataIt - channelPtrs.begin()); // The pointer that we are writing to. // The row that we are reading from is flipped (in the Y) as we use a different image space internally to OpenEXR and OpenImageIO. const float *inRowPtr = (*channelDataIt) + ( y - spec.y ) * dataWindowWidth; const int inc = channelPtrs.size(); for ( int x = 0; x < dataWindowWidth; ++x, outPtr += inc ) { *outPtr = *inRowPtr++; } } if ( !out->write_scanline( y, 0, TypeDesc::FLOAT, &scanline[0] ) ) { throw IECore::Exception( boost::str( boost::format( "Could not write scanline to \"%s\", error = %s" ) % fileName % out->geterror() ) ); } } } } // Tiled output else { // Create a buffer for the tile. const int tileSize = ImagePlug::tileSize(); float tile[ nChannels*tileSize*tileSize ]; if ( imageIsBlack ) { memset( tile, 0, sizeof(float) * nChannels*tileSize*tileSize ); for ( int tileY = 0; tileY < dataWindowHeight; tileY += tileSize ) { for ( int tileX = 0; tileX < dataWindowWidth; tileX += tileSize ) { if ( !out->write_tile( tileX+dataWindow.min.x, tileY+spec.y, 0, TypeDesc::FLOAT, &tile[0] ) ) { throw IECore::Exception( boost::str( boost::format( "Could not write tile to \"%s\", error = %s" ) % fileName % out->geterror() ) ); } } } } else { // Interleave the channel data and write it to the file tile-by-tile. for ( int tileY = 0; tileY < dataWindowHeight; tileY += tileSize ) { for ( int tileX = 0; tileX < dataWindowWidth; tileX += tileSize ) { float *outPtr = &tile[0]; const int r = std::min( tileSize+tileX, dataWindowWidth ); const int t = std::min( tileSize+tileY, dataWindowHeight ); for ( int y = 0; y < t; ++y ) { for ( std::vector<const float *>::iterator channelDataIt( channelPtrs.begin() ); channelDataIt != channelPtrs.end(); channelDataIt++ ) { const int inc = channelPtrs.size(); const float *inRowPtr = (*channelDataIt) + ( tileY + t - y - 1 ) * dataWindowWidth; for ( int x = 0; x < r; ++x, outPtr += inc ) { *outPtr = *inRowPtr+(tileX+x); } } } if ( !out->write_tile( tileX+dataWindow.min.x, tileY+spec.y, 0, TypeDesc::FLOAT, &tile[0] ) ) { throw IECore::Exception( boost::str( boost::format( "Could not write tile to \"%s\", error = %s" ) % fileName % out->geterror() ) ); } } } } } out->close(); } }
DataPtr EXRImageReader::readTypedChannel( const std::string &name, const Imath::Box2i &dataWindow, const Imf::Channel *channel ) { assert( channel ); Imath::V2i pixelDimensions = dataWindow.size() + Imath::V2i( 1 ); unsigned numPixels = pixelDimensions.x * pixelDimensions.y; typedef TypedData<vector<T> > DataType; typename DataType::Ptr data = new DataType; data->writable().resize( numPixels ); Imath::Box2i fullDataWindow = this->dataWindow(); if( fullDataWindow.min.x==dataWindow.min.x && fullDataWindow.max.x==dataWindow.max.x ) { // the width we want to read matches the width in the file, so we can read straight // into the result buffer FrameBuffer frameBuffer; T *buffer00 = data->baseWritable() - dataWindow.min.y * pixelDimensions.x - fullDataWindow.min.x; Slice slice( channel->type, (char *)buffer00, sizeof(T), sizeof(T) * pixelDimensions.x ); frameBuffer.insert( name.c_str(), slice ); m_inputFile->setFrameBuffer( frameBuffer ); // exr library will choose the best order to read scanlines automatically (increasing or decreasing) try { m_inputFile->readPixels( dataWindow.min.y, dataWindow.max.y ); } catch( Iex::InputExc &e ) { // so we can read incomplete files msg( Msg::Warning, "EXRImageReader::readChannel", e.what() ); return data; } } else { // widths don't match, we need to read into a temporary buffer and then transfer just // the bits we need into the result buffer. int numTmpPixels = fullDataWindow.size().x + 1; vector<T> tmpBuffer( numTmpPixels ); T *tmpBufferTransferStart = &(tmpBuffer[0]) + dataWindow.min.x - fullDataWindow.min.x; size_t tmpBufferTransferLength = pixelDimensions.x * sizeof( T ); T *transferDestination = &(data->writable()[0]); // slice has yStride of 0 so each successive scanline just overwrites the previous one Slice slice( channel->type, (char *)(&(tmpBuffer[0]) - fullDataWindow.min.x), sizeof(T), 0 ); FrameBuffer frameBuffer; frameBuffer.insert( name.c_str(), slice ); m_inputFile->setFrameBuffer( frameBuffer ); int yStart = dataWindow.min.y; int yEnd = dataWindow.max.y; int yStep = 1; try { for( int y=yStart; y!=(yEnd+yStep); y+=yStep ) { m_inputFile->readPixels( y ); memcpy( (char *)transferDestination, (const char *)tmpBufferTransferStart, tmpBufferTransferLength ); transferDestination += pixelDimensions.x; } } catch( Iex::InputExc &e ) { // so we can read incomplete files msg( Msg::Warning, "EXRImageReader::readChannel", e.what() ); return data; } } #ifndef NDEBUG for ( typename DataType::ValueType::const_iterator it = data->readable().begin(); it != data->readable().end(); ++it ) { assert( *it == *it ); // Will fail iff NaN } #endif return data; }
void viewport_t::resize( const Imath::Box2i& device) { world_.max.x = world_.min.x + ( device.size().x / zoom_x()); world_.max.y = world_.min.y + ( device.size().y / zoom_y()); device_ = device; }
void ImageCompositeOp::composite( CompositeFn fn, DataWindowResult dwr, ImagePrimitive * imageB, const CompoundObject * operands ) { assert( fn ); assert( imageB ); assert( operands ); const StringVectorParameter::ValueType &channelNames = channelNamesParameter()->getTypedValue(); if ( !channelNames.size() ) { throw InvalidArgumentException( "ImageCompositeOp: No channels specified" ); } ImagePrimitivePtr imageA = runTimeCast< ImagePrimitive > ( imageAParameter()->getValue() ); assert( imageA ); const std::string &alphaChannel = alphaChannelNameParameter()->getTypedValue(); if ( !imageA->arePrimitiveVariablesValid() ) { throw InvalidArgumentException( "ImageCompositeOp: Input image has invalid channels" ); } const int inputMode = m_inputModeParameter->getNumericValue(); if ( inputMode == Unpremultiplied ) { ImagePremultiplyOpPtr premultOp = new ImagePremultiplyOp(); premultOp->alphaChannelNameParameter()->setTypedValue( alphaChannel ); premultOp->channelNamesParameter()->setTypedValue( channelNames ); if ( imageA->variables.find( alphaChannel ) != imageA->variables.end() ) { /// Make a new imageA, premultiplied premultOp->copyParameter()->setTypedValue( true ); premultOp->inputParameter()->setValue( imageA ); imageA = assertedStaticCast< ImagePrimitive >( premultOp->operate() ); assert( imageA->arePrimitiveVariablesValid() ); } if ( imageB->variables.find( alphaChannel ) != imageB->variables.end() ) { /// Premultiply imageB in-place premultOp->copyParameter()->setTypedValue( false ); premultOp->inputParameter()->setValue( imageB ); premultOp->operate(); } } else { assert( inputMode == Premultiplied ); } const Imath::Box2i displayWindow = imageB->getDisplayWindow(); Imath::Box2i newDataWindow = imageB->getDataWindow(); if ( dwr == Union ) { newDataWindow.extendBy( imageA->getDataWindow() ); } else { assert( dwr == Intersection ); newDataWindow = boxIntersection( newDataWindow, imageA->getDataWindow() ); } newDataWindow = boxIntersection( newDataWindow, displayWindow ); ImageCropOpPtr cropOp = new ImageCropOp(); /// Need to make sure that we don't create a new image here - we want to modify the current one in-place. So, /// we turn off the "copy" parameter of ModifyOp. cropOp->copyParameter()->setTypedValue( false ); cropOp->inputParameter()->setValue( imageB ); cropOp->cropBoxParameter()->setTypedValue( newDataWindow ); cropOp->matchDataWindowParameter()->setTypedValue( true ); cropOp->resetOriginParameter()->setTypedValue( false ); cropOp->operate(); assert( imageB->arePrimitiveVariablesValid() ); assert( imageB->getDataWindow() == newDataWindow ); /// \todo Use the "reformat" parameter of the ImageCropOp to do this, when it's implemented imageB->setDisplayWindow( displayWindow ); FloatVectorDataPtr aAlphaData = getChannelData( imageA.get(), alphaChannel, false ); FloatVectorDataPtr bAlphaData = getChannelData( imageB, alphaChannel, false ); const int newWidth = newDataWindow.size().x + 1; const int newHeight = newDataWindow.size().y + 1; const int newArea = newWidth * newHeight; assert( newArea == (int)imageB->variableSize( PrimitiveVariable::Vertex ) ); for( unsigned i=0; i<channelNames.size(); i++ ) { const StringVectorParameter::ValueType::value_type &channelName = channelNames[i]; FloatVectorDataPtr aData = getChannelData( imageA.get(), channelName ); assert( aData->readable().size() == imageA->variableSize( PrimitiveVariable::Vertex ) ); FloatVectorDataPtr bData = getChannelData( imageB, channelName ); assert( bData->readable().size() == imageB->variableSize( PrimitiveVariable::Vertex ) ); FloatVectorDataPtr newBData = new FloatVectorData(); newBData->writable().resize( newArea ); imageB->variables[ channelName ].data = newBData; for ( int y = newDataWindow.min.y; y <= newDataWindow.max.y; y++ ) { int offset = (y - newDataWindow.min.y ) * newWidth; for ( int x = newDataWindow.min.x; x <= newDataWindow.max.x; x++, offset++ ) { float aVal = readChannelData( imageA.get(), aData.get(), V2i( x, y ) ); float bVal = readChannelData( imageB, bData.get(), V2i( x, y ) ); float aAlpha = aAlphaData ? readChannelData( imageA.get(), aAlphaData.get(), V2i( x, y ) ) : 1.0f; float bAlpha = bAlphaData ? readChannelData( imageB, bAlphaData.get(), V2i( x, y ) ) : 1.0f; assert( offset >= 0 ); assert( offset < (int)newBData->readable().size() ); newBData->writable()[ offset ] = fn( aVal, aAlpha, bVal, bAlpha ); } } } /// displayWindow should be unchanged assert( imageB->getDisplayWindow() == displayWindow ); assert( imageB->arePrimitiveVariablesValid() ); /// If input images were unpremultiplied, then ensure that the output is also if ( inputMode == Unpremultiplied && imageB->variables.find( alphaChannel ) != imageB->variables.end() ) { ImageUnpremultiplyOpPtr unpremultOp = new ImageUnpremultiplyOp(); /// Unpremultiply imageB in-place unpremultOp->copyParameter()->setTypedValue( false ); unpremultOp->channelNamesParameter()->setTypedValue( channelNames ); unpremultOp->alphaChannelNameParameter()->setTypedValue( alphaChannel ); unpremultOp->inputParameter()->setValue( imageB ); unpremultOp->operate(); assert( imageB->arePrimitiveVariablesValid() ); } else { assert( inputMode == Premultiplied ); } }
void HitMissTransform::modifyChannels( const Imath::Box2i &displayWindow, const Imath::Box2i &dataWindow, ChannelVector &channels ) { // process the structuring elements, including making rotated versions if requested. bool rotateElements = rotateStructuringElementsParameter()->getTypedValue(); vector<int> masks; vector<int> elements; const std::vector<M33f> &matrices = structuringElementsParameter()->getTypedValue(); for( unsigned i=0; i<matrices.size(); i++ ) { int mask = 0; int element = 0; processMatrix( matrices[i], mask, element ); masks.push_back( mask ); elements.push_back( element ); if( rotateElements ) { M33f m = matrices[i]; for( unsigned r=0; r<3; r++ ) { M33f m2( m[0][2], m[1][2], m[2][2], m[0][1], m[1][1], m[2][1], m[0][0], m[1][0], m[2][0] ); processMatrix( m2, mask, element ); masks.push_back( mask ); elements.push_back( element ); m = m2; } } } // apply the operation to each channel char value = valueParameter()->getNumericValue() > thresholdParameter()->getNumericValue() ? 1 : 0; char borderValue = borderValueParameter()->getNumericValue() > thresholdParameter()->getNumericValue() ? 1 : 0; bool applyAlternately = applyElementsAlternatelyParameter()->getTypedValue(); int numIterations = iterationsParameter()->getNumericValue(); if( applyAlternately ) { numIterations *= elements.size(); } V2i size = dataWindow.size() + V2i( 1 ); V2i paddedSize = size + V2i( 2 ); std::vector<char> pixels; std::vector<char> pixels2; for( unsigned i=0; i<channels.size(); i++ ) { // threshold the image into a temporary pixels structure Thresholder thresholder( thresholdParameter()->getNumericValue(), borderValue, size, pixels ); despatchTypedData<Thresholder, TypeTraits::IsNumericVectorTypedData>( channels[i], thresholder ); pixels2.clear(); pixels2.resize( pixels.size(), borderValue ); // do the work unsigned iterationsSinceChange = 0; for( int n=0; n<numIterations || numIterations==0; n++ ) { iterationsSinceChange++; for( int y=0; y<size.y; y++ ) { const char *r0 = &(pixels[y * paddedSize.x]) + 1; const char *r1 = r0 + paddedSize.x; const char *r2 = r1 + paddedSize.x; char *ro = &(pixels2[(y+1) * paddedSize.x]) + 1; for( int x=0; x<size.x; x++ ) { if( *r1==value ) { // no point doing the work if the existing value is the one we'd change it to anyway *ro = value; } else { int v = r0[-1] | r0[0] << 2 | r0[1] << 4 | r1[-1] << 6 | r1[0] << 8 | r1[1] << 10 | r2[-1] << 12 | r2[0] << 14 | r2[1] << 16; bool matches = false; if( applyAlternately ) { size_t e = n % elements.size(); if( (v & masks[e])==elements[e] ) { matches = true; } } else { for( size_t e=0; e<elements.size(); e++ ) { if( (v & masks[e])==elements[e] ) { matches = true; break; } } } if( matches ) { *ro = value; iterationsSinceChange = 0; } else { *ro = *r1; } } ro++; r0++; r1++; r2++; } } pixels.swap( pixels2 ); if( (applyAlternately && iterationsSinceChange==elements.size()) || ( !applyAlternately && iterationsSinceChange ) ) { break; } } // and copy back into the original structure Copyer copyer( size, pixels ); despatchTypedData<Copyer, TypeTraits::IsNumericVectorTypedData>( channels[i], copyer ); } }
void ImageThinner::modifyChannels( const Imath::Box2i &displayWindow, const Imath::Box2i &dataWindow, ChannelVector &channels ) { float threshold = thresholdParameter()->getNumericValue(); const V2i size = dataWindow.size() + V2i( 1 ); for( unsigned i=0; i<channels.size(); i++ ) { FloatVectorDataPtr floatData = runTimeCast<FloatVectorData>( channels[i] ); if( !floatData ) { throw Exception( "ImageThinner::modifyChannels : only float channels supported." ); } std::vector<float> &channel = floatData->writable(); // threshold the image first for( std::vector<float>::iterator it=channel.begin(); it!=channel.end(); it++ ) { *it = *it < threshold ? 0.0f : 1.0f; } // then apply the graphics gems magic that i don't understand in the slightest ////////////////////////////////////////////////////////////////////////////// boost::multi_array_ref<float, 2> image( &channel[0], boost::extents[size.y][size.x] ); int p, q; // Neighborhood maps of adjacent cells std::vector<unsigned char> qb; // Neighborhood maps of previous scanline qb.resize( size.x ); qb[qb.size()-1] = 0; // Used for lower-right pixel int count = 1; // Deleted pixel count while( count ) { count = 0; for( int i = 0; i < 4 ; i++ ) { int m = g_masks[i]; // Build initial previous scan buffer p = image[0][0] > 0.5f; for( int x = 0; x < size.x-1; x++ ) { qb[x] = p = ((p<<1)&0006) | (image[0][x+1] > 0.5f); } // Scan image for pixel deletion candidates for( int y = 0; y < size.y-1; y++ ) { q = qb[0]; p = ((q<<3)&0110) | (image[y+1][0] > 0.5f); for( int x = 0; x < size.x-1; x++ ) { q = qb[x]; p = ((p<<1)&0666) | ((q<<3)&0110) | (image[y+1][x+1] > 0.5f); qb[x] = p; if( ((p&m) == 0) && g_delete[p] ) { count++; image[y][x] = 0.f; } } // Process right edge pixel p = (p<<1)&0666; if( (p&m) == 0 && g_delete[p] ) { count++; image[y][size.x-1] = 0.f; } } // Process bottom scan line for( int x = 0 ; x < size.x ; x++ ) { q = qb[x]; p = ((p<<1)&0666) | ((q<<3)&0110); if( (p&m) == 0 && g_delete[p] ) { count++; image[size.y-1][x] = 0.f; } } } } } }