/************************************************************************* * * OpenOffice.org - a multi-platform office productivity suite * * $RCSfile: fumorph.cxx,v $ * * $Revision: 1.17 $ * * last change: $Author: kz $ $Date: 2006-12-12 17:20:07 $ * * The Contents of this file are made available subject to * the terms of GNU Lesser General Public License Version 2.1. * * * GNU Lesser General Public License Version 2.1 * ============================================= * Copyright 2005 by Sun Microsystems, Inc. * 901 San Antonio Road, Palo Alto, CA 94303, USA * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * ************************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_sd.hxx" //#define _FUMORPH_PRIVATE #include "fumorph.hxx" #ifndef _SVX_FILLITEM_HXX //autogen #include #endif #ifndef _SVX_XLINIIT_HXX //autogen #include #endif #ifndef _SV_MSGBOX_HXX //autogen #include #endif #ifndef _SVDPOOL_HXX //autogen #include #endif #ifndef _TL_POLY_HXX #include #endif #ifndef _SVDOPATH_HXX //autogen #include #endif #ifndef _SVDOGRP_HXX //autogen #include #endif #include #ifndef SD_VIEW_HXX #include "View.hxx" #endif #ifndef SD_VIEW_SHELL_HXX #include "ViewShell.hxx" #endif #ifndef SD_WINDOW_HXX #include "Window.hxx" #endif #ifndef _BGFX_POLYGON_B2DPOLYGONTOOLS_HXX #include #endif #ifndef _BGFX_POLYPOLYGON_B2DPOLYGONTOOLS_HXX #include #endif #ifndef _BGFX_MATRIX_B2DHOMMATRIX_HXX #include #endif #ifndef _B3D_B3DCOLOR_HXX #include #endif #include "strings.hrc" #include "sdresid.hxx" #include "sdabstdlg.hxx" // #i48168# #ifndef _SVDITER_HXX #include #endif namespace sd { #define ITEMVALUE( ItemSet, Id, Cast ) ( ( (const Cast&) (ItemSet).Get( (Id) ) ).GetValue() ) TYPEINIT1( FuMorph, FuPoor ); ////////////////////////////////////////////////////////////////////////////// // constructor // FuMorph::FuMorph ( ViewShell* pViewSh, ::sd::Window* pWin, ::sd::View* pView, SdDrawDocument* pDoc, SfxRequest& rReq ) : FuPoor(pViewSh, pWin, pView, pDoc, rReq) { } FunctionReference FuMorph::Create( ViewShell* pViewSh, ::sd::Window* pWin, ::sd::View* pView, SdDrawDocument* pDoc, SfxRequest& rReq ) { FunctionReference xFunc( new FuMorph( pViewSh, pWin, pView, pDoc, rReq ) ); xFunc->DoExecute(rReq); return xFunc; } void FuMorph::DoExecute( SfxRequest& ) { const SdrMarkList& rMarkList = mpView->GetMarkedObjectList(); if(rMarkList.GetMarkCount() == 2) { // Clones erzeugen SdrObject* pObj1 = rMarkList.GetMark(0)->GetMarkedSdrObj(); SdrObject* pObj2 = rMarkList.GetMark(1)->GetMarkedSdrObj(); SdrObject* pCloneObj1 = pObj1->Clone(); SdrObject* pCloneObj2 = pObj2->Clone(); // Text am Clone loeschen, da wir sonst kein richtiges PathObj bekommen pCloneObj1->SetOutlinerParaObject(NULL); pCloneObj2->SetOutlinerParaObject(NULL); // Path-Objekte erzeugen SdrObject* pPolyObj1 = pCloneObj1->ConvertToPolyObj(FALSE, FALSE); SdrObject* pPolyObj2 = pCloneObj2->ConvertToPolyObj(FALSE, FALSE); SdAbstractDialogFactory* pFact = SdAbstractDialogFactory::Create(); AbstractMorphDlg* pDlg = pFact ? pFact->CreateMorphDlg( static_cast< ::Window*>(mpWindow), pObj1, pObj2 ) : 0; if(pPolyObj1 && pPolyObj2 && pDlg && (pDlg->Execute() == RET_OK)) { List aPolyPolyList; ::basegfx::B2DPolyPolygon aPolyPoly1; ::basegfx::B2DPolyPolygon aPolyPoly2; ::basegfx::B2DPolyPolygon* pPolyPoly; pDlg->SaveSettings(); // #i48168# Not always is the pPolyObj1/pPolyObj2 a SdrPathObj, it may also be a group object // containing SdrPathObjs. To get the polygons, i add two iters here SdrObjListIter aIter1(*pPolyObj1); SdrObjListIter aIter2(*pPolyObj2); while(aIter1.IsMore()) { SdrObject* pObj = aIter1.Next(); if(pObj && pObj->ISA(SdrPathObj)) aPolyPoly1.append(((SdrPathObj*)pObj)->GetPathPoly()); } while(aIter2.IsMore()) { SdrObject* pObj = aIter2.Next(); if(pObj && pObj->ISA(SdrPathObj)) aPolyPoly2.append(((SdrPathObj*)pObj)->GetPathPoly()); } // Morphing durchfuehren if(aPolyPoly1.count() && aPolyPoly2.count()) { aPolyPoly1 = ::basegfx::tools::correctOrientations(aPolyPoly1); aPolyPoly1.removeDoublePoints(); ::basegfx::B2VectorOrientation eIsClockwise1(::basegfx::tools::getOrientation(aPolyPoly1.getB2DPolygon(0L))); aPolyPoly2 = ::basegfx::tools::correctOrientations(aPolyPoly2); aPolyPoly2.removeDoublePoints(); ::basegfx::B2VectorOrientation eIsClockwise2(::basegfx::tools::getOrientation(aPolyPoly2.getB2DPolygon(0L))); // set same orientation if(eIsClockwise1 != eIsClockwise2) aPolyPoly2.flip(); // force same poly count if(aPolyPoly1.count() < aPolyPoly2.count()) ImpAddPolys(aPolyPoly1, aPolyPoly2); else if(aPolyPoly2.count() < aPolyPoly1.count()) ImpAddPolys(aPolyPoly2, aPolyPoly1); // use orientation flag from dialog if(!pDlg->IsOrientationFade()) aPolyPoly2.flip(); // force same point counts for( sal_uInt32 a(0L); a < aPolyPoly1.count(); a++ ) { ::basegfx::B2DPolygon aSub1(aPolyPoly1.getB2DPolygon(a)); ::basegfx::B2DPolygon aSub2(aPolyPoly2.getB2DPolygon(a)); if(aSub1.count() < aSub2.count()) ImpEqualizePolyPointCount(aSub1, aSub2); else if(aSub2.count() < aSub1.count()) ImpEqualizePolyPointCount(aSub2, aSub1); aPolyPoly1.setB2DPolygon(a, aSub1); aPolyPoly2.setB2DPolygon(a, aSub2); } if(ImpMorphPolygons(aPolyPoly1, aPolyPoly2, pDlg->GetFadeSteps(), aPolyPolyList)) { String aString(mpView->GetDescriptionOfMarkedObjects()); aString.Append(sal_Unicode(' ')); aString.Append(String(SdResId(STR_UNDO_MORPHING))); mpView->BegUndo(aString); ImpInsertPolygons(aPolyPolyList, pDlg->IsAttributeFade(), pObj1, pObj2); mpView->EndUndo(); } // erzeugte Polygone wieder loeschen for(pPolyPoly = (::basegfx::B2DPolyPolygon*)aPolyPolyList.First(); pPolyPoly; pPolyPoly = (::basegfx::B2DPolyPolygon *)aPolyPolyList.Next()) { delete pPolyPoly; } } } delete pDlg; delete pCloneObj1; delete pCloneObj2; delete pPolyObj1; delete pPolyObj2; } } ::basegfx::B2DPolygon ImpGetExpandedPolygon(const ::basegfx::B2DPolygon& rCandidate, sal_uInt32 nNum) { if(rCandidate.count() && nNum && rCandidate.count() != nNum) { // length of step in dest poly ::basegfx::B2DPolygon aRetval; const double fStep(::basegfx::tools::getLength(rCandidate) / (double)(rCandidate.isClosed() ? nNum : nNum - 1L)); double fDestPos(0.0); double fSrcPos(0.0); sal_uInt32 nSrcPos(0L); sal_uInt32 nSrcPosNext((nSrcPos + 1L == rCandidate.count()) ? 0L : nSrcPos + 1L); double fNextSrcLen(::basegfx::B2DVector(rCandidate.getB2DPoint(nSrcPos) - rCandidate.getB2DPoint(nSrcPosNext)).getLength()); for(sal_uInt32 b(0L); b < nNum; b++) { // calc fDestPos in source while(fSrcPos + fNextSrcLen < fDestPos) { fSrcPos += fNextSrcLen; nSrcPos++; nSrcPosNext = (nSrcPos + 1L == rCandidate.count()) ? 0L : nSrcPos + 1L; fNextSrcLen = ::basegfx::B2DVector(rCandidate.getB2DPoint(nSrcPos) - rCandidate.getB2DPoint(nSrcPosNext)).getLength(); } // fDestPos is between fSrcPos and (fSrcPos + fNextSrcLen) const double fLenA((fDestPos - fSrcPos) / fNextSrcLen); const ::basegfx::B2DPoint aOld1(rCandidate.getB2DPoint(nSrcPos)); const ::basegfx::B2DPoint aOld2(rCandidate.getB2DPoint(nSrcPosNext)); ::basegfx::B2DPoint aNewPoint(::basegfx::interpolate(aOld1, aOld2, fLenA)); aRetval.append(aNewPoint); // next step fDestPos += fStep; } if(aRetval.count() >= 3L) { aRetval.setClosed(rCandidate.isClosed()); } return aRetval; } else { return rCandidate; } } ////////////////////////////////////////////////////////////////////////////// // make the point count of the polygons equal in adding points // void FuMorph::ImpEqualizePolyPointCount(::basegfx::B2DPolygon& rSmall, const ::basegfx::B2DPolygon& rBig) { // create poly with equal point count const sal_uInt32 nCnt(rBig.count()); ::basegfx::B2DPolygon aPoly1(ImpGetExpandedPolygon(rSmall, nCnt)); // create transformation for rBig to do the compare const ::basegfx::B2DRange aSrcSize(::basegfx::tools::getRange(rBig)); const ::basegfx::B2DPoint aSrcPos(aSrcSize.getCenter()); const ::basegfx::B2DRange aDstSize(::basegfx::tools::getRange(rSmall)); const ::basegfx::B2DPoint aDstPos(aDstSize.getCenter()); ::basegfx::B2DHomMatrix aTrans; aTrans.translate(-aSrcPos.getX(), -aSrcPos.getY()); aTrans.scale(aDstSize.getWidth() / aSrcSize.getWidth(), aDstSize.getHeight() / aSrcSize.getHeight()); aTrans.translate(aDstPos.getX(), aDstPos.getY()); // transpose points to have smooth linear blending ::basegfx::B2DPolygon aPoly2; aPoly2.append(::basegfx::B2DPoint(), nCnt); sal_uInt32 nInd(ImpGetNearestIndex(aPoly1, aTrans * rBig.getB2DPoint(0L))); for(sal_uInt32 a(0L); a < nCnt; a++) { aPoly2.setB2DPoint((a + nCnt - nInd) % nCnt, aPoly1.getB2DPoint(a)); } aPoly2.setClosed(rBig.isClosed()); rSmall = aPoly2; } ////////////////////////////////////////////////////////////////////////////// // sal_uInt32 FuMorph::ImpGetNearestIndex(const ::basegfx::B2DPolygon& rPoly, const ::basegfx::B2DPoint& rPos) { double fMinDist = 0.0; sal_uInt32 nActInd = 0; for(sal_uInt32 a(0L); a < rPoly.count(); a++) { double fNewDist(::basegfx::B2DVector(rPoly.getB2DPoint(a) - rPos).getLength()); if(!a || fNewDist < fMinDist) { fMinDist = fNewDist; nActInd = a; } } return nActInd; } ////////////////////////////////////////////////////////////////////////////// // add to a point reduced polys until count is same // void FuMorph::ImpAddPolys(::basegfx::B2DPolyPolygon& rSmaller, const ::basegfx::B2DPolyPolygon& rBigger) { while(rSmaller.count() < rBigger.count()) { const ::basegfx::B2DPolygon aToBeCopied(rBigger.getB2DPolygon(rSmaller.count())); const ::basegfx::B2DRange aToBeCopiedPolySize(::basegfx::tools::getRange(aToBeCopied)); ::basegfx::B2DPoint aNewPoint(aToBeCopiedPolySize.getCenter()); ::basegfx::B2DPolygon aNewPoly; const ::basegfx::B2DRange aSrcSize(::basegfx::tools::getRange(rBigger.getB2DPolygon(0L))); const ::basegfx::B2DPoint aSrcPos(aSrcSize.getCenter()); const ::basegfx::B2DRange aDstSize(::basegfx::tools::getRange(rSmaller.getB2DPolygon(0L))); const ::basegfx::B2DPoint aDstPos(aDstSize.getCenter()); aNewPoint = aNewPoint - aSrcPos + aDstPos; for(sal_uInt32 a(0L); a < aToBeCopied.count(); a++) { aNewPoly.append(aNewPoint); } rSmaller.append(aNewPoly); } } ////////////////////////////////////////////////////////////////////////////// // create group object with morphed polygons // void FuMorph::ImpInsertPolygons(List& rPolyPolyList3D, BOOL bAttributeFade, const SdrObject* pObj1, const SdrObject* pObj2) { Color aStartFillCol; Color aEndFillCol; Color aStartLineCol; Color aEndLineCol; long nStartLineWidth = 0; long nEndLineWidth = 0; SdrPageView* pPageView = mpView->GetSdrPageView(); SfxItemPool* pPool = pObj1->GetObjectItemPool(); SfxItemSet aSet1( *pPool,SDRATTR_START,SDRATTR_NOTPERSIST_FIRST-1,EE_ITEMS_START,EE_ITEMS_END,0 ); SfxItemSet aSet2( aSet1 ); BOOL bLineColor = FALSE; BOOL bFillColor = FALSE; BOOL bLineWidth = FALSE; BOOL bIgnoreLine = FALSE; BOOL bIgnoreFill = FALSE; aSet1.Put(pObj1->GetMergedItemSet()); aSet2.Put(pObj2->GetMergedItemSet()); const XLineStyle eLineStyle1 = ITEMVALUE( aSet1, XATTR_LINESTYLE, XLineStyleItem ); const XLineStyle eLineStyle2 = ITEMVALUE( aSet2, XATTR_LINESTYLE, XLineStyleItem ); const XFillStyle eFillStyle1 = ITEMVALUE( aSet1, XATTR_FILLSTYLE, XFillStyleItem ); const XFillStyle eFillStyle2 = ITEMVALUE( aSet2, XATTR_FILLSTYLE, XFillStyleItem ); if ( bAttributeFade ) { if ( ( eLineStyle1 != XLINE_NONE ) && ( eLineStyle2 != XLINE_NONE ) ) { bLineWidth = bLineColor = TRUE; aStartLineCol = static_cast< XLineColorItem const & >( aSet1.Get(XATTR_LINECOLOR)).GetColorValue(); aEndLineCol = static_cast< XLineColorItem const & >( aSet2.Get(XATTR_LINECOLOR)).GetColorValue(); nStartLineWidth = ITEMVALUE( aSet1, XATTR_LINEWIDTH, XLineWidthItem ); nEndLineWidth = ITEMVALUE( aSet2, XATTR_LINEWIDTH, XLineWidthItem ); } else if ( ( eLineStyle1 == XLINE_NONE ) && ( eLineStyle2 == XLINE_NONE ) ) bIgnoreLine = TRUE; if ( ( eFillStyle1 == XFILL_SOLID ) && ( eFillStyle2 == XFILL_SOLID ) ) { bFillColor = TRUE; aStartFillCol = static_cast< XFillColorItem const & >( aSet1.Get(XATTR_FILLCOLOR)).GetColorValue(); aEndFillCol = static_cast< XFillColorItem const & >( aSet2.Get(XATTR_FILLCOLOR)).GetColorValue(); } else if ( ( eFillStyle1 == XFILL_NONE ) && ( eFillStyle2 == XFILL_NONE ) ) bIgnoreFill = TRUE; } if ( pPageView ) { SfxItemSet aSet( aSet1 ); SdrObjGroup* pObjGroup = new SdrObjGroup; SdrObjList* pObjList = pObjGroup->GetSubList(); const ULONG nCount = rPolyPolyList3D.Count(); const double fStep = 1. / ( nCount + 1 ); const double fDelta = nEndLineWidth - nStartLineWidth; double fFactor = fStep; aSet.Put( XLineStyleItem( XLINE_SOLID ) ); aSet.Put( XFillStyleItem( XFILL_SOLID ) ); for ( ULONG i = 0; i < nCount; i++, fFactor += fStep ) { const ::basegfx::B2DPolyPolygon& rPolyPoly3D = *(::basegfx::B2DPolyPolygon*)rPolyPolyList3D.GetObject(i); SdrPathObj* pNewObj = new SdrPathObj(OBJ_POLY, rPolyPoly3D); B3dColor aLineCol, aFillCol; aLineCol.CalcInBetween(aStartLineCol, aEndLineCol, fFactor); aFillCol.CalcInBetween(aStartFillCol, aEndFillCol, fFactor); // Linienfarbe if ( bLineColor ) aSet.Put( XLineColorItem( aEmptyStr, aLineCol)); else if ( bIgnoreLine ) aSet.Put( XLineStyleItem( XLINE_NONE ) ); // Fuellfarbe if ( bFillColor ) aSet.Put( XFillColorItem( aEmptyStr, aFillCol)); else if ( bIgnoreFill ) aSet.Put( XFillStyleItem( XFILL_NONE ) ); // Linienstaerke if ( bLineWidth ) aSet.Put( XLineWidthItem( nStartLineWidth + (long) ( fFactor * fDelta + 0.5 ) ) ); pNewObj->SetMergedItemSetAndBroadcast(aSet); pObjList->InsertObject( pNewObj, LIST_APPEND ); } if ( nCount ) { pObjList->InsertObject( pObj1->Clone(), 0 ); pObjList->InsertObject( pObj2->Clone(), LIST_APPEND ); mpView->DeleteMarked(); mpView->InsertObjectAtView( pObjGroup, *pPageView, SDRINSERT_SETDEFLAYER ); } } } ////////////////////////////////////////////////////////////////////////////// // create single morphed PolyPolygon // ::basegfx::B2DPolyPolygon* FuMorph::ImpCreateMorphedPolygon( const ::basegfx::B2DPolyPolygon& rPolyPolyStart, const ::basegfx::B2DPolyPolygon& rPolyPolyEnd, double fMorphingFactor) { ::basegfx::B2DPolyPolygon* pNewPolyPolygon = new ::basegfx::B2DPolyPolygon(); const double fFactor = 1.0 - fMorphingFactor; for(sal_uInt32 a(0L); a < rPolyPolyStart.count(); a++) { const ::basegfx::B2DPolygon aPolyStart(rPolyPolyStart.getB2DPolygon(a)); const ::basegfx::B2DPolygon aPolyEnd(rPolyPolyEnd.getB2DPolygon(a)); const sal_uInt32 nCount(aPolyStart.count()); ::basegfx::B2DPolygon aNewPolygon; for(sal_uInt32 b(0L); b < nCount; b++) { const ::basegfx::B2DPoint& aPtStart(aPolyStart.getB2DPoint(b)); const ::basegfx::B2DPoint& aPtEnd(aPolyEnd.getB2DPoint(b)); aNewPolygon.append(aPtEnd + ((aPtStart - aPtEnd) * fFactor)); } aNewPolygon.setClosed(aPolyStart.isClosed() && aPolyEnd.isClosed()); pNewPolyPolygon->append(aNewPolygon); } return pNewPolyPolygon; } ////////////////////////////////////////////////////////////////////////////// // create morphed PolyPolygons // sal_Bool FuMorph::ImpMorphPolygons( const ::basegfx::B2DPolyPolygon& rPolyPoly1, const ::basegfx::B2DPolyPolygon& rPolyPoly2, const sal_uInt16 nSteps, List& rPolyPolyList3D) { if(nSteps) { const ::basegfx::B2DRange aStartPolySize(::basegfx::tools::getRange(rPolyPoly1)); const ::basegfx::B2DPoint aStartCenter(aStartPolySize.getCenter()); const ::basegfx::B2DRange aEndPolySize(::basegfx::tools::getRange(rPolyPoly2)); const ::basegfx::B2DPoint aEndCenter(aEndPolySize.getCenter()); const ::basegfx::B2DPoint aDelta(aEndCenter - aStartCenter); const double fFactor(1.0 / (nSteps + 1)); double fValue(0.0); for(sal_uInt16 i(0); i < nSteps; i++) { fValue += fFactor; ::basegfx::B2DPolyPolygon* pNewPolyPoly3D = ImpCreateMorphedPolygon(rPolyPoly1, rPolyPoly2, fValue); const ::basegfx::B2DRange aNewPolySize(::basegfx::tools::getRange(*pNewPolyPoly3D)); const ::basegfx::B2DPoint aNewS(aNewPolySize.getCenter()); const ::basegfx::B2DPoint aRealS(aStartCenter + (aDelta * fValue)); ::basegfx::B2DHomMatrix aTrans; const ::basegfx::B2DPoint aDiff(aRealS - aNewS); aTrans.translate(aDiff.getX(), aDiff.getY()); pNewPolyPoly3D->transform(aTrans); rPolyPolyList3D.Insert(pNewPolyPoly3D, LIST_APPEND); } } return TRUE; } } // end of namespace sd