/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_basegfx.hxx" #include <basegfx/polygon/b2dpolypolygonrasterconverter.hxx> #include <basegfx/numeric/ftools.hxx> #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <boost/mem_fn.hpp> #include <algorithm> namespace basegfx { class radixSort { //! public interface public: //! default constructor radixSort( void ); //! destructor ~radixSort( void ); bool sort( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ); inline sal_uInt32 *indices( void ) const { return m_indices1; } //! private attributes private: // current size of index list sal_uInt32 m_current_size; // last known size of index list sal_uInt32 m_previous_size; // index lists sal_uInt32 *m_indices1; sal_uInt32 *m_indices2; sal_uInt32 m_counter[256*4]; sal_uInt32 m_offset[256]; //! private methods private: bool resize( sal_uInt32 nNumElements ); inline void reset_indices( void ); bool prepareCounters( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ); }; inline radixSort::radixSort( void ) { m_indices1 = NULL; m_indices2 = NULL; m_current_size = 0; m_previous_size = 0; reset_indices(); } inline radixSort::~radixSort( void ) { delete [] m_indices2; delete [] m_indices1; } bool radixSort::resize( sal_uInt32 nNumElements ) { if(nNumElements==m_previous_size) return true; if(nNumElements > m_current_size) { // release index lists if(m_indices2) delete [] m_indices2; if(m_indices1) delete [] m_indices1; // allocate new index lists m_indices1 = new sal_uInt32[nNumElements]; m_indices2 = new sal_uInt32[nNumElements]; // check for out of memory situation if(!m_indices1 || !m_indices2) { delete [] m_indices1; delete [] m_indices2; m_indices1 = NULL; m_indices2 = NULL; m_current_size = 0; return false; } m_current_size = nNumElements; } m_previous_size = nNumElements; // initialize indices reset_indices(); return true; } inline void radixSort::reset_indices( void ) { for(sal_uInt32 i=0;i<m_current_size;i++) m_indices1[i] = i; } bool radixSort::prepareCounters( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ) { // clear counters sal_uInt32 *ptr = m_counter; for(int i=0; i<64; ++i) { *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; *ptr++ = 0; } // prepare pointers to relevant memory addresses sal_uInt8 *p = (sal_uInt8*)pInput; sal_uInt8 *pe = p+(nNumElements*dwStride); sal_uInt32 *h0= &m_counter[0]; sal_uInt32 *h1= &m_counter[256]; sal_uInt32 *h2= &m_counter[512]; sal_uInt32 *h3= &m_counter[768]; sal_uInt32 *Indices = m_indices1; float previous_value = *(float *)(((sal_uInt8 *)pInput)+(m_indices1[0]*dwStride)); bool bSorted = true; while(p!=pe) { float value = *(float *)(((sal_uInt8 *)pInput)+((*Indices++)*dwStride)); if(value<previous_value) { bSorted = false; break; } previous_value = value; h0[*p++]++; h1[*p++]++; h2[*p++]++; h3[*p++]++; p += dwStride-4; } if(bSorted) return true; while(p!=pe) { h0[*p++]++; h1[*p++]++; h2[*p++]++; h3[*p++]++; p += dwStride-4; } return false; } bool radixSort::sort( const float *pInput, sal_uInt32 nNumElements, sal_uInt32 dwStride ) { if(!(pInput)) return false; if(!(nNumElements)) return false; if(!(resize(nNumElements))) return false; // prepare radix counters, return if already sorted if(prepareCounters(pInput,nNumElements,dwStride)) return true; // count number of negative values sal_uInt32 num_negatives = 0; sal_uInt32 *h3= &m_counter[768]; for(sal_uInt32 i=128;i<256;i++) num_negatives += h3[i]; // perform passes, one for each byte for(sal_uInt32 j=0;j<4;j++) { // ignore this pass if all values have the same byte bool bRun = true; sal_uInt32 *current_counter = &m_counter[j<<8]; sal_uInt8 unique_value = *(((sal_uInt8*)pInput)+j); if(current_counter[unique_value]==nNumElements) bRun=false; // does the incoming byte contain the sign bit? sal_uInt32 i; if(j!=3) { if(bRun) { m_offset[0] = 0; for(i=1;i<256;i++) m_offset[i] = m_offset[i-1] + current_counter[i-1]; sal_uInt8 *InputBytes = (sal_uInt8 *)pInput; sal_uInt32 *Indices = m_indices1; sal_uInt32 *IndicesEnd = &m_indices1[nNumElements]; InputBytes += j; while(Indices!=IndicesEnd) { sal_uInt32 id = *Indices++; m_indices2[m_offset[InputBytes[id*dwStride]]++] = id; } sal_uInt32 *Tmp = m_indices1; m_indices1 = m_indices2; m_indices2 = Tmp; } } else { if(bRun) { m_offset[0] = num_negatives; for(i=1;i<128;i++) m_offset[i] = m_offset[i-1] + current_counter[i-1]; m_offset[255] = 0; for(i=0;i<127;i++) m_offset[254-i] = m_offset[255-i] + current_counter[255-i]; for(i=128;i<256;i++) m_offset[i] += current_counter[i]; for(i=0;i<nNumElements;i++) { sal_uInt32 Radix = (*(sal_uInt32 *)(((sal_uInt8 *)pInput)+(m_indices1[i]*dwStride)))>>24; if(Radix<128) m_indices2[m_offset[Radix]++] = m_indices1[i]; else m_indices2[--m_offset[Radix]] = m_indices1[i]; } sal_uInt32 *Tmp = m_indices1; m_indices1 = m_indices2; m_indices2 = Tmp; } else { if(unique_value>=128) { for(i=0;i<nNumElements;i++) m_indices2[i] = m_indices1[nNumElements-i-1]; sal_uInt32 *Tmp = m_indices1; m_indices1 = m_indices2; m_indices2 = Tmp; } } } } return true; } //************************************************************ // Internal vertex storage of B2DPolyPolygonRasterConverter //************************************************************ inline B2DPolyPolygonRasterConverter::Vertex::Vertex() : aP1(), aP2(), bDownwards( true ) { } inline B2DPolyPolygonRasterConverter::Vertex::Vertex( const B2DPoint& rP1, const B2DPoint& rP2, bool bDown ) : aP1( rP1 ), aP2( rP2 ), bDownwards( bDown ) { } //************************************************************ // Helper class for holding horizontal line segments during raster // conversion //************************************************************ namespace { class ImplLineNode { public: sal_Int32 mnYCounter; float mfXPos; float mfXDelta; bool mbDownwards; public: /**rP1 and rP2 must not have equal y values, when rounded to integer! */ ImplLineNode(const B2DPoint& rP1, const B2DPoint& rP2, bool bDown) : mnYCounter( fround(rP2.getY()) - fround(rP1.getY()) ), mfXPos( (float)(rP1.getX()) ), mfXDelta((float) ((rP2.getX() - rP1.getX()) / mnYCounter) ), mbDownwards( bDown ) { } /// get current x position const float& getXPos() const { return mfXPos; } /// returns true, if line ends on this Y value float nextLine() { if(mnYCounter>=0) { // go one step in Y mfXPos += mfXDelta; --mnYCounter; return mfXDelta; } return 0.0f; } bool isEnded() { return mnYCounter<=0; } bool isDownwards() { return mbDownwards; } }; } typedef ::std::vector<ImplLineNode> VectorOfLineNodes; //************************************************************ // Base2D PolyPolygon Raster Converter (Rasterizer) //************************************************************ namespace { struct VertexComparator { bool operator()( const B2DPolyPolygonRasterConverter::Vertex& rLHS, const B2DPolyPolygonRasterConverter::Vertex& rRHS ) { return rLHS.aP1.getX() < rRHS.aP1.getX(); } }; } void B2DPolyPolygonRasterConverter::init() { if(!maPolyPolyRectangle.isEmpty()) { const sal_Int32 nMinY( fround(maPolyPolyRectangle.getMinY()) ); const sal_Int32 nScanlines(fround(maPolyPolyRectangle.getMaxY()) - nMinY); maScanlines.resize( nScanlines+1 ); // add all polygons for( sal_uInt32 i(0), nCount(maPolyPolygon.count()); i < nCount; ++i ) { // add all vertices const B2DPolygon& rPoly( maPolyPolygon.getB2DPolygon(i) ); for( sal_uInt32 k(0), nVertices(rPoly.count()); k<nVertices; ++k ) { const B2DPoint& rP1( rPoly.getB2DPoint(k) ); const B2DPoint& rP2( rPoly.getB2DPoint( (k + 1) % nVertices ) ); const sal_Int32 nVertexYP1( fround(rP1.getY()) ); const sal_Int32 nVertexYP2( fround(rP2.getY()) ); // insert only vertices which are not strictly // horizontal. Note that the ImplLineNode relies on // this. if(nVertexYP1 != nVertexYP2) { if( nVertexYP2 < nVertexYP1 ) { const sal_Int32 nStartScanline(nVertexYP2 - nMinY); // swap edges maScanlines[ nStartScanline ].push_back( Vertex(rP2, rP1, false) ); } else { const sal_Int32 nStartScanline(nVertexYP1 - nMinY); maScanlines[ nStartScanline ].push_back( Vertex(rP1, rP2, true) ); } } } } // now sort all scanlines, with increasing x coordinates VectorOfVertexVectors::iterator aIter( maScanlines.begin() ); VectorOfVertexVectors::iterator aEnd( maScanlines.end() ); while( aIter != aEnd ) { ::std::sort( aIter->begin(), aIter->end(), VertexComparator() ); ++aIter; } } } B2DPolyPolygonRasterConverter::B2DPolyPolygonRasterConverter( const B2DPolyPolygon& rPolyPoly ) : maPolyPolygon( rPolyPoly ), maPolyPolyRectangle( tools::getRange( rPolyPoly ) ), maScanlines() { init(); } namespace { B2DRectangle getCombinedBounds( const B2DPolyPolygon& rPolyPolyRaster, const B2DRectangle& rRasterArea ) { B2DRectangle aRect( tools::getRange( rPolyPolyRaster ) ); aRect.expand( rRasterArea ); return aRect; } } B2DPolyPolygonRasterConverter::B2DPolyPolygonRasterConverter( const B2DPolyPolygon& rPolyPolyRaster, const B2DRectangle& rRasterArea ) : maPolyPolygon( rPolyPolyRaster ), maPolyPolyRectangle( getCombinedBounds( rPolyPolyRaster, rRasterArea ) ), maScanlines() { init(); } B2DPolyPolygonRasterConverter::~B2DPolyPolygonRasterConverter() { } namespace { class LineNodeGenerator { public: LineNodeGenerator( VectorOfLineNodes& rActiveVertices ) : mrActiveVertices( rActiveVertices ) { } void operator()( const B2DPolyPolygonRasterConverter::Vertex& rVertex ) { mrActiveVertices.push_back( ImplLineNode(rVertex.aP1, rVertex.aP2, rVertex.bDownwards) ); } private: VectorOfLineNodes& mrActiveVertices; }; struct LineNodeComparator { bool operator()( const ImplLineNode& rLHS, const ImplLineNode& rRHS ) { return rLHS.getXPos() < rRHS.getXPos(); } }; } void B2DPolyPolygonRasterConverter::rasterConvert( FillRule eFillRule ) { if( maScanlines.empty() ) return; // no scanlines at all -> bail out const sal_Int32 nMinY( fround(maPolyPolyRectangle.getMinY()) ); const sal_Int32 nScanlines(fround(maPolyPolyRectangle.getMaxY()) - nMinY); // Vector of currently active vertices. A vertex is active, if // it crosses or touches the current scanline. VectorOfLineNodes aActiveVertices; // mickey's optimized version... radixSort rs; std::size_t nb(0); std::size_t nb_previous(0); bool bSort(false); // process each scanline for( sal_Int32 y(0); y <= nScanlines; ++y ) { // add vertices which start at current scanline into // active vertex vector ::std::for_each( maScanlines[y].begin(), maScanlines[y].end(), LineNodeGenerator( aActiveVertices ) ); nb = aActiveVertices.size(); if(nb != nb_previous) { nb_previous = nb; bSort = true; } // sort with increasing X if(bSort) { bSort = false; if( nb ) { rs.sort(&aActiveVertices[0].mfXPos, nb, sizeof(ImplLineNode)); } } const std::size_t nLen( nb ); if( !nLen ) { // empty scanline - call derived with an 'off' span // for the full width span( maPolyPolyRectangle.getMinX(), maPolyPolyRectangle.getMaxX(), nMinY + y, false ); } else { const sal_Int32 nCurrY( nMinY + y ); // scanline not empty - forward all scans to derived, // according to selected fill rule // TODO(P1): Maybe allow these 'off' span calls to be // switched off (or all 'on' span calls, depending on // use case scenario) // sorting didn't change the order of the elements // in memory but prepared a list of indices in sorted order. // thus we now process the nodes with an additional indirection. sal_uInt32 *sorted = rs.indices(); // call derived with 'off' span for everything left of first active span if( aActiveVertices[sorted[0]].getXPos() > maPolyPolyRectangle.getMinX() ) { span( maPolyPolyRectangle.getMinX(), aActiveVertices[sorted[0]].getXPos(), nCurrY, false ); } switch( eFillRule ) { default: OSL_ENSURE(false, "B2DPolyPolygonRasterConverter::rasterConvert(): Unexpected fill rule"); return; case FillRule_EVEN_ODD: // process each span in current scanline, with // even-odd fill rule for( ::std::size_t i(0), nLength(aActiveVertices.size()); i+1 < nLength; ++i ) { sal_uInt32 nIndex = sorted[i]; sal_uInt32 nNextIndex = sorted[i+1]; span( aActiveVertices[nIndex].getXPos(), aActiveVertices[nNextIndex].getXPos(), nCurrY, i % 2 == 0 ); float delta = aActiveVertices[nIndex].nextLine(); if(delta > 0.0f) { if(aActiveVertices[nIndex].getXPos() > aActiveVertices[nNextIndex].getXPos()) bSort = true; } else if(delta < 0.0f) { if(i) { sal_uInt32 nPrevIndex = sorted[i-1]; if(aActiveVertices[nIndex].getXPos() < aActiveVertices[nPrevIndex].getXPos()) bSort = true; } } } break; case FillRule_NONZERO_WINDING_NUMBER: // process each span in current scanline, with // non-zero winding numbe fill rule sal_Int32 nWindingNumber(0); for( ::std::size_t i(0), nLength(aActiveVertices.size()); i+1 < nLength; ++i ) { sal_uInt32 nIndex = sorted[i]; sal_uInt32 nNextIndex = sorted[i+1]; nWindingNumber += -1 + 2*aActiveVertices[nIndex].isDownwards(); span( aActiveVertices[nIndex].getXPos(), aActiveVertices[nNextIndex].getXPos(), nCurrY, nWindingNumber != 0 ); float delta = aActiveVertices[nIndex].nextLine(); if(delta > 0.0f) { if(aActiveVertices[nIndex].getXPos() > aActiveVertices[nNextIndex].getXPos()) bSort = true; } else if(delta < 0.0f) { if(i) { sal_uInt32 nPrevIndex = sorted[i-1]; if(aActiveVertices[nIndex].getXPos() < aActiveVertices[nPrevIndex].getXPos()) bSort = true; } } } break; } // call derived with 'off' span for everything right of last active span if( aActiveVertices[sorted[nb-1]].getXPos()+1.0 < maPolyPolyRectangle.getMaxX() ) { span( aActiveVertices[sorted[nb-1]].getXPos()+1.0, maPolyPolyRectangle.getMaxX(), nCurrY, false ); } // also call nextLine on very last line node sal_uInt32 nIndex = sorted[nb-1]; float delta = aActiveVertices[nIndex].nextLine(); if(delta < 0.0f) { if(nb) { sal_uInt32 nPrevIndex = sorted[nb-2]; if(aActiveVertices[nIndex].getXPos() < aActiveVertices[nPrevIndex].getXPos()) bSort = true; } } } // remove line nodes which have ended on the current scanline aActiveVertices.erase( ::std::remove_if( aActiveVertices.begin(), aActiveVertices.end(), ::boost::mem_fn( &ImplLineNode::isEnded ) ), aActiveVertices.end() ); nb = aActiveVertices.size(); if(nb != nb_previous) { nb_previous = nb; bSort = true; } } } } // eof