| /************************************************************** |
| * |
| * 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 |