// $Id: brushref.cpp 1282 2006-06-09 09:46:49Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ // BrushRef implementation #include "camtypes.h" #include "brushref.h" #include "nodebldr.h" //#include "docrect.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "doccoord.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "fillval.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "fillattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "nodebldr.h" //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "nodepath.h" //#include "nodecomp.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "gblend.h" #include "nodeblnd.h" #include "nodeclip.h" #include "lineattr.h" #include "attrmap.h" CC_IMPLEMENT_MEMDUMP(BrushRef, BlendRef) CC_IMPLEMENT_MEMDUMP(BrushRefBlender, CC_CLASS_MEMDUMP); #define DELPTR(p) if (p != NULL) { delete p; p = NULL; } #define FLATNESS 1024 // Declare smart memory handling in Debug builds #define new CAM_DEBUG_NEW /*********************************************************************************************** > void BlendPathOffset::CalculateAngleAndMagnitude() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: - Outputs: - Returns: - Purpose: takes the x and y offset members and calculates the angle and magnitude of that vector ***********************************************************************************************/ void BlendPathOffset::CalculateAngleAndMagnitude() { MILLIPOINT Xsq = m_XOffset * m_XOffset; MILLIPOINT Ysq = m_YOffset * m_YOffset; double Hypotenuse = sqrt((double)Xsq + (double)Ysq); double Calc = ((double)m_XOffset / Hypotenuse); double RadAngle = acos(Calc); // double DegAngle = RadAngle * (180 / PI); m_Angle = RadAngle; m_Magnitude = (MILLIPOINT)Hypotenuse; } /*********************************************************************************************** > void BlendPathOffset::GetAngleAndMagnitude(double* pAngle, MILLIPOINT* pMagnitude) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: - Outputs: pAngle pMagnitude Returns: - Purpose: to get the angle and magnitude of this vector ***********************************************************************************************/ void BlendPathOffset::GetAngleAndMagnitude(double* pAngle, MILLIPOINT* pMagnitude) { *pAngle = m_Angle; *pMagnitude = m_Magnitude; } /*********************************************************************************************** > void BlendPathOffset::RotateByAngle(double Angle, MILLIPOINT* pXOffset, MILLIPOINT* pYOffset) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: Angle - the angle to rotate by Outputs: pXOffset - the resulting scalar value of the vector pYOffset Returns: - Purpose: to find out the x and y offset values if this blendpathoffset were to be rotated by the given angle ***********************************************************************************************/ void BlendPathOffset::RotateByAngle(double Angle, MILLIPOINT* pXOffset, MILLIPOINT* pYOffset) { double NewAngle = m_Angle + Angle; *pXOffset = (MILLIPOINT)(m_Magnitude * cos(NewAngle)); *pYOffset = (MILLIPOINT)(m_Magnitude * sin(NewAngle)); } /*********************************************************************************************** > BrushRef::BrushRef() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: - Outputs: - Returns: - Purpose: Default constructor. ***********************************************************************************************/ BrushRef::BrushRef() { m_pNode = NULL; m_NumBlendPaths = 0; m_pBlendPathAttrMap = NULL; m_CachedRect = DocRect(0,0,0,0); m_pCurrentBlendPath = NULL; } /*********************************************************************************************** > BrushRef::~BrushRef() Author: Mark_Neves (Xara Group Ltd) Created: 12/10/94 Inputs: - Outputs: - Returns: - Purpose: Default destructor. ***********************************************************************************************/ BrushRef::~BrushRef() { FreeAttributes(); m_BlendPathList.DeleteAll(); m_OffsetList.clear(); } /*********************************************************************************************** > BOOL BrushRef::Initialise(NodeRenderableInk* pInitNode) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: pInitNode = ptr to the node this BrushRef refers to. Outputs: - Returns: TRUE if successfully initialises, FALSE otherwise Purpose: This inits the BrushRef object with the given node. Essentially a wrapper for the blendref initialise function. However it also works out the blendpath offsets, if there are any ***********************************************************************************************/ BOOL BrushRef::Initialise(NodeRenderableInk* pInitNode) { ERROR2IF(pInitNode == NULL, FALSE, "Ink node is NULL in BrushRef::Initialise"); // Note that BrushDefinition::GenerateBrush now prevents objects with brush attributes being // used to generate brushes, instead it converts them to groups of nodepaths as per Convert Line to Shapes pInitNode->MakeAttributeComplete(); // just in case we're missing any BOOL ok = BlendRef::InitialiseForBrush(pInitNode); if (ok) { // if we have made a brushref from a group then the different // blendpaths will be offset from the centre of. work out these // offsets and store them in a list m_OffsetList.clear(); DocRect BRect = GetBoundingRect(); DocCoord Centre = BRect.Centre(); BlendPath* pBlendPath = GetFirstBlendPath(); while (pBlendPath != NULL) { Path* pPath = pBlendPath->GetPath(); if (pPath) { DocRect BlendPathRect = pPath->GetBoundingRect(); DocCoord BlendPathCentre = BlendPathRect.Centre(); BlendPathOffset ThisOffset; ThisOffset.m_XOffset = BlendPathCentre.x - Centre.x ; ThisOffset.m_YOffset = BlendPathCentre.y - Centre.y; ThisOffset.CalculateAngleAndMagnitude(); m_OffsetList.push_back(ThisOffset); pBlendPath->FindAppliedAttributes(); pBlendPath = GetNextBlendPath(pBlendPath); } else { ERROR3("BlendPath has no path"); return FALSE; } } // if we have a bitmap fill we need to do some special things.. SetBitmapFillFlags(); PreBlend(); if (pInitNode->IsABlend()) ((NodeBlend*)pInitNode)->Deinit(); return TRUE; } return FALSE; } /*********************************************************************************************** > BOOL BrushRef::MakeCopiesForRendering() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 12/9/2000 Inputs: - Outputs: - Returns: TRUE if successful, FALSE otherwise (e.g. out of memory) Purpose: Asks the blendpaths to make copies of their paths and attributes which we will use to render with ***********************************************************************************************/ BOOL BrushRef::MakeCopiesForRendering() { // BlendPath* pLastPath = NULL; BlendPath* pBlendPath = GetFirstBlendPath(); BOOL ok = TRUE; while (pBlendPath != NULL) { if (ok) ok = pBlendPath->MakeCopyPathAndAttributes(); pBlendPath = GetNextBlendPath(pBlendPath); } return ok; } /*********************************************************************************************** > BOOL BrushRef::UpdateCopies() Author: Phil_Martin (Xara Group Ltd) Created: 14/01/2004 Inputs: - Outputs: - Returns: TRUE if successful, FALSE otherwise (e.g. out of memory) Purpose: Asks the blendpaths to make copies of their paths and attributes which we will use to render with ***********************************************************************************************/ BOOL BrushRef::UpdateCopies() { // BlendPath* pLastPath = NULL; BlendPath* pBlendPath = GetFirstBlendPath(); BOOL ok = TRUE; while (pBlendPath != NULL && ok) { pBlendPath->UpdateCopyPathAndAttributes(); pBlendPath = GetNextBlendPath(pBlendPath); } return ok; } /*********************************************************************************************** > BOOL BrushRef::MakeCopiesForRendering() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 12/9/2000 Inputs: - Outputs: - Returns: - Purpose: Asks the blendpaths to delete the copies of paths and attributes ***********************************************************************************************/ void BrushRef::DeleteRenderCopies() { // BlendPath* pLastPath = NULL; BlendPath* pBlendPath = GetFirstBlendPath(); while (pBlendPath != NULL) { pBlendPath->DeleteCopyPathAndAttributes(); pBlendPath = GetNextBlendPath(pBlendPath); } } /*********************************************************************************************** > Path* BrushRef::GetFirstCopyPath() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 12/9/2000 Inputs: - Outputs: - Returns: the copy of the path made by the first blendpath that we should use for rendering Purpose: Makes the first blendpath our current blendpath member and returns the copy of its path. Basically you should use the system like this: Path* pPath = pBrushRef->GetFirstCopyPath(); CCAttrMap* pMap = NULL; while (pPath) { pMap = pBrushRef->GetCurrentAttributeCopy(); pMap->Render(or whatever) pPath->Render (or whatever) pPath = pBrushRef->GetNextCopyPath(); } ***********************************************************************************************/ Path* BrushRef::GetFirstCopyPath() { m_pCurrentBlendPath = GetFirstBlendPath(); if (m_pCurrentBlendPath == NULL) { ERROR3("First blendpath is NULL in BrushRef::GetFirstCopyPath"); return NULL; } return m_pCurrentBlendPath->GetCopyPath(); } /*********************************************************************************************** > Path* BrushRef::GetFirstCopyPath() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 12/9/2000 Inputs: - Outputs: - Returns: the next path to be rendered Purpose: Increments our current blendpath member and returns the copy path of the next blendpath ***********************************************************************************************/ Path* BrushRef::GetNextCopyPath() { // If our current blendpath isn't set yet then its an error if (m_pCurrentBlendPath == NULL) { ERROR3("Current blendpath is NULL in BrushRef::GetNextCopyPath"); return NULL; } m_pCurrentBlendPath = GetNextBlendPath(m_pCurrentBlendPath); if (m_pCurrentBlendPath != NULL) return m_pCurrentBlendPath->GetCopyPath(); // we've hit the end of the line return NULL; } /*********************************************************************************************** > CCAttrMap* BrushRef::GetCurrentAttributeCopy() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 12/9/2000 Inputs: - Outputs: - Returns: the attribute map that we should render with our current copy path Purpose: ***********************************************************************************************/ CCAttrMap* BrushRef::GetCurrentAttributeCopy() { if (m_pCurrentBlendPath == NULL) { ERROR3("Current blendpath is NULL in BrushRef::GetCurrentAttributeCopy"); return NULL; } return m_pCurrentBlendPath->GetCopyAttributes(); } /*********************************************************************************************** > MILLIPOINT BrushRef::GetFirstOffset(POSITION* pHeadPosition) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: pListPosition - uninitialised POSITION variable Outputs: pListPosition - the position of the head of the list - use it to pass into GetNextOffset Returns: the value of the first offset in the m_offset list. If the list is empty it returns -1. Purpose: as above ***********************************************************************************************/ BlendPathOffset *BrushRef::GetFirstOffset( iterator *pHeadPosition ) { if (m_OffsetList.empty()) return NULL; else { *pHeadPosition = m_OffsetList.begin(); return &( *(*pHeadPosition)++ ); } } /*********************************************************************************************** > MILLIPOINT BrushRef::GetNextOffset(POSITION* pPosition) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: pPosition - the position to get in the list Outputs: pPosition - the next position in the list (or NULL) Returns: the value of the next offset in the m_offset list. If the list is empty it returns NULL. Purpose: as above ***********************************************************************************************/ BlendPathOffset* BrushRef::GetNextOffset( iterator *pHeadPosition ) { if( m_OffsetList.empty() ) return NULL; else return &( *(*pHeadPosition)++ ); } /*********************************************************************************************** > DocRect BrushRef::GetBoundingRect() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: Outputs: Returns: the bounding rect of this brushref Purpose: as above, unions the bounding boxes ***********************************************************************************************/ DocRect BrushRef::GetBoundingRect() { DocRect Rect; BlendPath *pBlendPath = GetFirstBlendPath(); BlendPathOffset Offset; iterator ListPos = m_OffsetList.begin(); CCAttrMap *pAttrMap = NULL; while (pBlendPath != NULL) { DocRect PathRect; pAttrMap = pBlendPath->FindAppliedAttributes(); pBlendPath->m_pPath->GetTrueBoundingRect(&PathRect, 0, pAttrMap); if (ListPos != m_OffsetList.end() ) { Offset = *ListPos++; /* PathRect.hix += Offset.m_XOffset; PathRect.lox += Offset.m_XOffset; PathRect.hiy += Offset.m_YOffset; PathRect.loy += Offset.m_YOffset; */ } Rect = Rect.Union(PathRect); pAttrMap = NULL; pBlendPath = GetNextBlendPath(pBlendPath); } return Rect; } /*********************************************************************************************** > BOOL BrushRef::SetBitmapFillFlags() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: Outputs: Returns: TRUE if we don't have a bitmap fill, or if we do and successfully made a copy Purpose: Because brush definitions exist independently of documents, but bitmap fills do not. Therefore if we make a brush with a bitmap fill and then delete them document we created it in we lose the fill. However the brush still exists, therefore this function searches for a bitmap fill attribute, and sets flags that tell the bitmap not to delete itself. NOTE: This means that we are responsible for deleting the bitmap fills that belong to us ***********************************************************************************************/ BOOL BrushRef::SetBitmapFillFlags() { // for each blendpath we wish to find out if they have an applied bitmap fill attribute. // if so then we want to copy it, and replace it in the attribute map BlendPath* pBlendPath = GetFirstBlendPath(); while (pBlendPath != NULL) { // there may be both a colour fill and transparency attribute, so get both AttrBitmapFill* pBMPFill = pBlendPath->GetAppliedBitmapColourFill(); if (pBMPFill != NULL) { // Get the attribute value BitmapFillAttribute* pVal = (BitmapFillAttribute*)pBMPFill->GetAttributeValue(); if (pVal == NULL) { ERROR3("Couldn't retrieve attribute value"); return FALSE; } KernelBitmap* pKBitmap = pVal->GetBitmap(); if (pKBitmap != NULL) { OILBitmap* pOBitmap = pKBitmap->GetActualBitmap(); // tell the bitmaps that they are being used by us so that if the // document closes they don't get deleted if (pOBitmap != NULL) { pOBitmap->SetUsedByBrush(TRUE); pKBitmap->SetUsedByBrush(TRUE); } } } AttrBitmapFill* pTranspFill = pBlendPath->GetAppliedBitmapTranspFill(); if (pTranspFill != NULL) { // Get the attribute value BitmapFillAttribute* pVal = (BitmapFillAttribute*)pTranspFill->GetAttributeValue(); if (pVal == NULL) { ERROR3("Couldn't retrieve attribute value"); return FALSE; } KernelBitmap* pKBitmap = pVal->GetBitmap(); if (pKBitmap != NULL) { OILBitmap* pOBitmap = pKBitmap->GetActualBitmap(); // tell the bitmaps that they are being used by us so that if the // document closes they don't get deleted if (pOBitmap != NULL) { pOBitmap->SetUsedByBrush(TRUE); pKBitmap->SetUsedByBrush(TRUE); } } } pBlendPath = GetNextBlendPath(pBlendPath); } return TRUE; } /*********************************************************************************************** > void BrushRef::DeleteBitmapFills() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: Outputs: Returns: - Purpose: Searches for bitmap fill attributes and deletes their bitmaps, see previous function for the rationale. Note that this function was designed to be called by the BrushDefinition destructor. I cannot think of a good reason for wanting to use it anywhere else. ***********************************************************************************************/ void BrushRef::DeleteBitmapFills() { // loop through each blendpath BlendPath* pBlendPath = GetFirstBlendPath(); while (pBlendPath != NULL) { // retrieve the attribute node AttrBitmapFill* pBMPFill = pBlendPath->GetAppliedBitmapColourFill(); if (pBMPFill != NULL) { // get the attribute value BitmapFillAttribute* pVal = (BitmapFillAttribute*)pBMPFill->GetAttributeValue(); if (pVal != NULL) { // get the bitmap KernelBitmap* pBitmap = pVal->GetBitmap(); // if it has been flagged by us then zap it if (pBitmap != NULL && pBitmap->IsUsedByBrush()) { pBitmap->SetUsedByBrush(FALSE); pBitmap->Detach(); delete pBitmap; } } } pBlendPath = GetNextBlendPath(pBlendPath); } } /*********************************************************************************************** > void BrushRef::DeleteAttributesAndPath() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: Outputs: Returns: - Purpose: Goes through all of the blendpaths and sets a flag which ensures that all the applied attributes of this blendpath are deleted upon destruction. This must not be used under normal circumstances because these attributes will still be in use by other objects. It was designed for when we are blending brush attributes and have to generate our attribute maps from scratch ***********************************************************************************************/ void BrushRef::DeleteAttributesAndPath() { // BlendPath* pLastPath = NULL; BlendPath* pBlendPath = GetFirstBlendPath(); while (pBlendPath != NULL) { // ensures that all the applied attributes will be deleted when the object is deleted pBlendPath->DeleteAttributesAndPath(); pBlendPath = GetNextBlendPath(pBlendPath); } } /*********************************************************************************************** > void BrushRef::FreeAttributes() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: Outputs: Returns: - Purpose: Goes through the blendpaths and asks them to delete their cached attribute map ***********************************************************************************************/ void BrushRef::FreeAttributes() { // BlendPath* pLastPath = NULL; BlendPath* pBlendPath = GetFirstBlendPath(); while (pBlendPath != NULL) { pBlendPath->SetFreeAttributeFlag(TRUE); pBlendPath = GetNextBlendPath(pBlendPath); } //DeleteBitmapFills(); // disabled until I get it working properly } /*********************************************************************************************** > void BrushRef::TranslateTo(DocCoord Destination) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: Destination - the coord to translateto Outputs: - Returns: - Purpose: Translates the blendpaths and their applied attributes to Destination ***********************************************************************************************/ void BrushRef::TranslateTo(DocCoord Destination) { BlendPath* pBlendPath = GetFirstBlendPath(); CCAttrMap* pAttrMap = NULL; DocRect BRect; DocCoord CurrentCentre; Trans2DMatrix Trans; while (pBlendPath != NULL) { // find out where we are BRect = pBlendPath->m_pPath->GetBoundingRect(); CurrentCentre = BRect.Centre(); // set the translation in the matrix Trans.SetTransform(Destination.x - CurrentCentre.x, Destination.y - CurrentCentre.y); // we want to transform attributes too pAttrMap = pBlendPath->FindAppliedAttributes(); if (pAttrMap == NULL) { ERROR3("No applied attributes"); } else { pAttrMap->Transform(Trans); pAttrMap = NULL; } pBlendPath->Transform(Trans); pBlendPath = GetNextBlendPath(pBlendPath); } } /*********************************************************************************************** > void BrushRef::TransformBitmapAttributes(TransformBase& Trans) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: Trans - the transformation to perform Outputs: - Returns: - Purpose: When a brush stroke is transformed in the document we do not really need to transform the blendpaths however we do need tot transform the attributes, otherwise bitmap fills become incorrect as do line widths etc. However if we transform all attributes then we also need to transform the blendpaths, because such things as radial fills are translated by the brush code from the position of the blendpaths. It is important however that we do not scale the blendpaths, as the scaling code assumes that they always stay the same size. ***********************************************************************************************/ void BrushRef::TransformAttributes(TransformBase& Trans) { BlendPath *pBlendPath = GetFirstBlendPath(); CCAttrMap *pAttrMap = NULL; CCRuntimeClass *pType; void *pVal; NodeAttribute *pNodeAttr; while (pBlendPath != NULL) { // we want to transform attributes too pAttrMap = pBlendPath->FindAppliedAttributes(); if (pAttrMap == NULL) { ERROR3("No applied attributes"); } else { // monster bodge - currently we only actually want to transform line widths and bitmap fills for( CCAttrMap::iterator Pos = pAttrMap->GetStartPosition(); Pos != pAttrMap->GetEndPosition(); ) { // Get attr at position Pos pAttrMap->GetNextAssoc( Pos, pType, pVal ); // pVal is actually an attribute, so get a ptr to it and render it pNodeAttr = (NodeAttribute *)pVal; if (pNodeAttr->IsABitmapFill()) pNodeAttr->Transform(Trans); if( pNodeAttr->IsALineWidthAttr() && FALSE != Trans.TransLines ) { INT32 Test = labs( INT32(Trans.GetScalar().MakeDouble() * ((AttrLineWidth*)pNodeAttr)->Value.LineWidth) ); if (Test >= 10) pNodeAttr->Transform(Trans); } } pAttrMap = NULL; } pBlendPath = GetNextBlendPath(pBlendPath); } return; } /*--------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------- ------------------The BrushRefBlender Class---------------------------------------------------- ----------------------------------------------------------------------------------------------*/ /*********************************************************************************************** > BrushRefBlender::BrushRefBlender() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 28/3/2000 Inputs: - Outputs: - Returns: - Purpose: Default constructor. ***********************************************************************************************/ BrushRefBlender::BrushRefBlender() { m_pStartBrushRef = NULL; m_pEndBrushRef = NULL; m_pTempCoords = NULL; m_pTempVerbs = NULL; m_pTempFlags = NULL; m_GBlendBuffSize = 0; m_pGBlendBuff = NULL; m_ArrayLength = 0; } /*********************************************************************************************** > BrushRefBlender::~BrushRefBlender() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 28/3/2000 Inputs: - Outputs: - Returns: - Purpose: Default destructor. ***********************************************************************************************/ BrushRefBlender::~BrushRefBlender() { DeallocTempBuffers(); } /*********************************************************************************************** > void BrushRefBlender::SetBrushref(BrushRef* pBrushRef, BOOL Start) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 28/3/2000 Inputs: the brushref to set, and whether it is the first or last brushref Outputs: - Returns: - Purpose: Default destructor. ***********************************************************************************************/ void BrushRefBlender::SetBrushRef(BrushRef* pBrushRef, BOOL Start) { if (pBrushRef == NULL) return; if (Start) m_pStartBrushRef = pBrushRef; else m_pEndBrushRef = pBrushRef; } /*********************************************************************************************** > BrushRef* BrushRefBlender::Blend(BrushRef* pStartBrush, BrushRef* pEndBrush, double Ratio) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/12/99 Inputs: pStartBrush - the first brushref to blend pEndBrush - the other brushref to blend to Ratio - the ratio of that to this Outputs: Returns: a newly allocated brushref created by blending this with pOther Purpose: as above. A lot of this code is similar to that of NodeBlender::CreateBlends, however it is actuall used by the BrushComponent ***********************************************************************************************/ BrushRef* BrushRefBlender::Blend(BrushRef* pStartBrush, BrushRef* pEndBrush, double Ratio) { if (pStartBrush == NULL || pEndBrush == NULL) { ERROR3("brush ref is NULL in BrushRef::Blend"); return NULL; } if (Ratio < 0 || Ratio > 1) { ERROR3("Invalid blend ratio"); return NULL; } // allocate the extra memory that we need if (!ReallocTempBuffers(1000)) return NULL; // right, lets go to work. The first thing to do is translate both sets of paths to 0,0 // the reason for this is that in the course of its normal existence the blendpaths can get // translated all over the place, so in order for our blend to make sense they should at least // be centered in the same place // We will also want to store their current location in order to translate them back when we're done // DocRect StartRect = pStartBrush->GetBoundingRect(); // DocRect EndRect = pEndBrush->GetBoundingRect(); // DocCoord StartCoord = StartRect.Centre(); // DocCoord EndCoord = EndRect.Centre(); // pStartBrush->TranslateTo(DocCoord(0,0)); // pEndBrush->TranslateTo(DocCoord(0,0)); // Find num blend paths in start and end, and keep hold of the MAX value UINT32 NumPathsInStart = pStartBrush->GetNumBlendPaths(); UINT32 NumPathsInEnd = pEndBrush->GetNumBlendPaths(); UINT32 MaxNumPaths = max(NumPathsInStart,NumPathsInEnd); if (NumPathsInStart == 0 || NumPathsInEnd == 0) return NULL; // This object is used to step along the the two lists of objects choosing a pair to go and blend. // It mainly comes into play when there are different numbers of objects in the two lists. ListStepper Stepper; Stepper.Init(NumPathsInStart, NumPathsInEnd, 0); INT32 NextPathStart, NextPathEnd; //allocate our new brushref BrushRef* pBlendedBrushRef = new BrushRef; if (pBlendedBrushRef == NULL) return NULL; // set up our variables, they will be allocated in the loop (slow I know) Path* pBlendedPath = NULL; BlendPath* pNewBlendPath = NULL; CCAttrMap* pBlendedAttrMap = NULL; // Get the first pair of objects to blend Stepper.GetNext(&NextPathStart,&NextPathEnd); // The first pair of blend paths become the current blend path pair // Get ptrs to these, plus the subpath IDs of these BlendPath* pCurrBlendPathStart = pStartBrush->GetBlendPath(NextPathStart); BlendPath* pCurrBlendPathEnd = pEndBrush->GetBlendPath(NextPathEnd ); UINT32 CurrSubPathIDStart = pCurrBlendPathStart->GetSubPathID(); UINT32 CurrSubPathIDEnd = pCurrBlendPathEnd ->GetSubPathID(); // We also need info on the next blend path pair, so get some vars ready BlendPath* pNextBlendPathStart = NULL; BlendPath* pNextBlendPathEnd = NULL; UINT32 NextSubPathIDStart = CurrSubPathIDStart; // This was uninitialized - AMB guessed a good value UINT32 NextSubPathIDEnd = CurrSubPathIDEnd; // This was uninitialized - AMB guessed a good value for (UINT32 NextPath = 0; NextPath < MaxNumPaths; NextPath++) { // allocate the variables pBlendedPath = new Path; pBlendedAttrMap = new CCAttrMap(30); if (pBlendedPath == NULL || pBlendedAttrMap == NULL ) goto ExitNull; if (!pBlendedPath->Initialise()) goto ExitNull; // Get the next pair of objects to blend Stepper.GetNext(&NextPathStart,&NextPathEnd); // if we haven't run out then assign the next path variables if (NextPathStart >= 0 && NextPathEnd >= 0) { pNextBlendPathStart = pStartBrush->GetBlendPath(NextPathStart); pNextBlendPathEnd = pEndBrush->GetBlendPath(NextPathEnd); NextSubPathIDStart = pNextBlendPathStart->GetSubPathID(); NextSubPathIDEnd = pNextBlendPathEnd->GetSubPathID(); } if (BlendAttributes(pCurrBlendPathStart, pCurrBlendPathEnd, pBlendedAttrMap, Ratio)) { // Blend the paths together, putting the blended path in BlendedPath if (BlendPaths(pCurrBlendPathStart,pCurrBlendPathEnd,Ratio)) { // The blended path will be filled if either of the paths are filled // The blended path will be stroked if either of the paths are stroked BOOL Filled = pCurrBlendPathStart->IsFilled() || pCurrBlendPathEnd->IsFilled(); BOOL Stroked = pCurrBlendPathStart->IsStroked() || pCurrBlendPathEnd->IsStroked(); if (!pBlendedPath->MakeSpaceInPath(m_ArrayLength)) goto ExitNull; pBlendedPath->MergeTwoPaths(m_pTempCoords,m_pTempVerbs,m_pTempFlags,m_ArrayLength,Filled); pBlendedPath->IsFilled = Filled; pBlendedPath->IsStroked = Stroked; // We haven't yet got a complete path to render or pass back if next path has the same ID and is // a different path // (this applies to the start OR the end path) BOOL NotACompletePath = ((CurrSubPathIDStart == NextSubPathIDStart && pCurrBlendPathStart != pNextBlendPathStart) || (CurrSubPathIDEnd == NextSubPathIDEnd && pCurrBlendPathEnd != pNextBlendPathEnd )); if (!NotACompletePath) { // it all worked, lets allocate a new blendpath pNewBlendPath = new BlendPath; if (pNewBlendPath == NULL) goto ExitNull; // tell it to use our blended path and attributes pNewBlendPath->SetPath(pBlendedPath); pNewBlendPath->SetAppliedAttributes(pBlendedAttrMap); // give it to the brushref pBlendedBrushRef->AddBlendPath(pNewBlendPath); } else goto ExitNull; pNewBlendPath = NULL; pBlendedPath = NULL; pBlendedAttrMap = NULL; } else // end if blendpaths goto ExitNull; } // end if blendattributes else goto ExitNull; } // end for // DeallocTempBuffers(); // translate the brushes back //pStartBrush->TranslateTo(StartCoord); //pEndBrush->TranslateTo(EndCoord); return pBlendedBrushRef; // just so I don't have to keep repeating this code, if an allocation fails then come here where everything is deleted ExitNull: if (pBlendedPath != NULL) delete pBlendedPath; if (pBlendedBrushRef != NULL) delete pBlendedBrushRef; if (pBlendedAttrMap != NULL) delete pBlendedAttrMap; if (pNewBlendPath != NULL) delete pNewBlendPath; ERROR3("Something went wrong in BrushRefBlender::Blend"); return NULL; } /******************************************************************************************** > DocCoord* BrushRefBlender::GetCoordArray(UINT32 MinSize); Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/11/94 Inputs: MinSize = min size the array should be Outputs: - Returns: Ptr to the array, or NULL if can't get memory Purpose: Used to get an array you can write to. SeeAlso: - ********************************************************************************************/ DocCoord* BrushRefBlender::GetCoordArray(UINT32 MinSize) { MinSize++; if (m_TempArraySize >= MinSize) return m_pTempCoords; if (ReallocTempBuffers(MinSize)) return m_pTempCoords; else return NULL; } /******************************************************************************************** > PathVerb* BrushRefBlender::GetVerbArray(UINT32 MinSize); Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/11/94 Inputs: MinSize = min size the array should be Outputs: - Returns: Ptr to the array, or NULL if can't get memory Purpose: Used to get an array you can write to. SeeAlso: - ********************************************************************************************/ PathVerb* BrushRefBlender::GetVerbArray(UINT32 MinSize) { MinSize++; if (m_TempArraySize >= MinSize) return m_pTempVerbs; if (ReallocTempBuffers(MinSize)) return m_pTempVerbs; else return NULL; } /******************************************************************************************** > PathFlags* BrushRefBlender::GetFlagArray(UINT32 MinSize); Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/11/94 Inputs: MinSize = min size the array should be Outputs: - Returns: Ptr to the array, or NULL if can't get memory Purpose: Used to get an array you can write to. SeeAlso: - ********************************************************************************************/ PathFlags* BrushRefBlender::GetFlagArray(UINT32 MinSize) { MinSize++; if (m_TempArraySize >= MinSize) return m_pTempFlags; if (ReallocTempBuffers(MinSize)) return m_pTempFlags; else return NULL; } /******************************************************************************************** > UINT32* BrushRefBlender::GetGBlendBuff(UINT32 MinSize); Author: Diccon_Yamanaka (Xara Group Ltd) Created: 7/11/94 Inputs: MinSize = min size the buffer should be Outputs: - Returns: Ptr to the buffer, or NULL if can't get memory Purpose: Used to get a buffer you can write to. SeeAlso: - ********************************************************************************************/ UINT32* BrushRefBlender::GetGBlendBuff(UINT32 MinSize) { MinSize++; if (m_GBlendBuffSize >= MinSize) return m_pGBlendBuff; if (m_pGBlendBuff != NULL) CCFree(m_pGBlendBuff); m_pGBlendBuff = (UINT32*) CCMalloc(MinSize*sizeof(UINT32)); if (m_pGBlendBuff != NULL) m_GBlendBuffSize = MinSize; else m_GBlendBuffSize = 0; return m_pGBlendBuff; } /******************************************************************************************** > BOOL BrushRefBlender::ReallocTempBuffers(UINT32 Size) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 28/3/2000 Inputs: Size = Size the temp arrays should be Outputs: - Returns: - Purpose: Allocates memory for the temp path arrays, and sets the size var to 0 You can use calls to GetCoordArray(), GetVerbArray() and GetFlagArray() to get the alloced arrays. SeeAlso: - ********************************************************************************************/ BOOL BrushRefBlender::ReallocTempBuffers(UINT32 Size) { DeallocTempBuffers(); // Allocate the arrays m_TempArraySize = Size; m_pTempCoords = (DocCoord*) CCMalloc(Size*sizeof(DocCoord)); m_pTempVerbs = (PathVerb*) CCMalloc(Size*sizeof(PathVerb)); m_pTempFlags = (PathFlags*) CCMalloc(Size*sizeof(PathFlags)); // If any of the arrays are not allocated, dealloc the alloced ones, and return FALSE if (m_pTempCoords == NULL || m_pTempVerbs == NULL || m_pTempFlags == NULL) { DeallocTempBuffers(); return FALSE; } // It's all OK, so return TRUE return TRUE; } /******************************************************************************************** > void BrushRefBlender::DeallocTempBuffers() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 28/3/2000 Inputs: - Outputs: - Returns: - Purpose: Releases memory allocated for the temp path arrays, and sets the size var to 0 SeeAlso: - ********************************************************************************************/ void BrushRefBlender::DeallocTempBuffers() { if (m_pTempCoords != NULL) { CCFree(m_pTempCoords); m_pTempCoords = NULL; } if (m_pTempVerbs != NULL) { CCFree(m_pTempVerbs); m_pTempVerbs = NULL; } if (m_pTempFlags != NULL) { CCFree(m_pTempFlags); m_pTempFlags = NULL; } m_TempArraySize = 0; if (m_pGBlendBuff != NULL) { CCFree(m_pGBlendBuff); m_pGBlendBuff = NULL; } m_GBlendBuffSize = 0; } /******************************************************************************************** > BOOL BrushRefBlender::BlendAttributes(BlendPath* pBlendPathStart, BlendPath* pBlendPathEnd, CCAttrMap* pBlendedAttrMap,double BlendRatio) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 27/3/2000 Inputs: pBlendPathStart = ptr to blend path to blend from pBlendPathEnd = ptr to blend path to blend to pBlendedAttrMap = ptr to map to store the blended attributes in BlendRatio = amount to blend by (0.0 <= BlendPath <= 1.0) Outputs: - Returns: TRUE if successful, FALSE otherwise Purpose: Blends the attributes of the two BlendPath objects by the amount specified in BlendRatio This is pretty much the same as the fn. by the same name in NodeBlender SeeAlso: - ********************************************************************************************/ BOOL BrushRefBlender::BlendAttributes( BlendPath* pBlendPathStart, BlendPath* pBlendPathEnd, CCAttrMap* pBlendedAttrMap,double BlendRatio) { // Check entry params BOOL ok = (pBlendPathStart != NULL && pBlendPathEnd != NULL && pBlendedAttrMap != NULL); ERROR3IF(!ok,"One or more NULL entry params"); if (!ok) return FALSE; // Find the attributes that are applied to the blend paths CCAttrMap* pAttrMapStart = pBlendPathStart->FindAppliedAttributes(); CCAttrMap* pAttrMapEnd = pBlendPathEnd->FindAppliedAttributes(); // If either are NULL, return FALSE if (pAttrMapStart == NULL || pAttrMapEnd == NULL) return FALSE; // These vars are used as params to the CCAttrMap funcs CCRuntimeClass *pTypeStart; void *pValStart; void *pValEnd; double OldBlendRatio = BlendRatio; // Process each attribute in turn CCAttrMap::iterator PosStart = pAttrMapStart->GetStartPosition(); for (;PosStart != pAttrMapStart->GetEndPosition();) { // Get a ptr to the attr at position PosStart in the start node's attr map pAttrMapStart->GetNextAssoc( PosStart, pTypeStart, pValStart ); NodeAttribute* pNodeAttrStart = (NodeAttribute *)pValStart; BlendRatio = OldBlendRatio; // Diccon 10/99 When using non-linear profiles for the objects those attributes // that make use of control points were not being profiled, making the objects look strange. // to avoid this those attributes now share the same profiles as the objects. /* if (pNodeAttrStart->IsAGradFill()) { if (!((AttrFillGeometry*)pNodeAttrStart)->IsAColourFill()) { BlendRatio = GetObjectRatio(); } else { BlendRatio = GetInvertedAttributeRatio(); } } if (pNodeAttrStart->IsAFlatFill() || (pNodeAttrStart->GetRuntimeClass() == CC_RUNTIME_CLASS(AttrLineWidth))) { BlendRatio = GetInvertedAttributeRatio(); } */ // Get a blended attribute NodeAttribute* pBlendedNodeAttr = NULL; // Find an attr of the same type in the end object's attr list, // and blend the two attrs together if( pAttrMapEnd->Lookup( pTypeStart, pValEnd ) ) { // We've found a matching end attr, so try to blend it with the start attr // Set up the param object to pass to the start attr's blend method BlendAttrParam BlendParam; // Initialise the BlendParam with the end attr and blend ratio if (BlendParam.Init(NULL, (NodeAttribute *)pValEnd,BlendRatio,COLOURBLEND_FADE, pAttrMapStart, pAttrMapEnd)) { // Successfully initialised, so now try blending the attributes if (pNodeAttrStart->Blend(&BlendParam)) { // Attrs successfully blended, now get a ptr to the new attr. // Once we get the blended attr ptr, it belongs to us, so we have // to delete it when it is not needed pBlendedNodeAttr = BlendParam.GetBlendedAttr(); } } } // If we have a blended attr, pBlendedNodeAttr != NULL if (pBlendedNodeAttr != NULL) { // Get the type of the blended attr CCRuntimeClass *pTypeBlend = pBlendedNodeAttr->GetAttributeType(); void *pValBlend; // If we already have an attr in the blended attr map of the same type, // remove it and delete it, before inserting a new attr of this type if (pBlendedAttrMap->Lookup(pTypeBlend,pValBlend)) { if (pValBlend != NULL) { pBlendedAttrMap->RemoveKey(pTypeBlend); delete (NodeAttribute*)pValBlend; } } // add it to the blend map pBlendedAttrMap->SetAt( pTypeBlend, pBlendedNodeAttr ); } } return TRUE; } /******************************************************************************************** > BOOL BrushRefBlender::BlendPaths(BlendPath* pBlendPathStart,BlendPath* pBlendPathEnd,double BlendRatio) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 27/3/2000 Inputs: pBlendPathStart = ptr to blend path to blend from pBlendPathEnd = ptr to blend path to blend to BlendRatio = amount to blend by (0.0 <= BlendPath <= 1.0) Outputs: The blended path is stored in three arrays: the coords, the verbs, and the flags. The arrays are: pTempCoords pTempVerbs pTempFlags ArrayLength = the length of all three arrays This allows the caller to decide what to do with the blended path in a very flexible way. Returns: TRUE if successful, FALSE otherwise Purpose: Blends two BlendPath objects by the amount specified in BlendRatio This is pretty much the same as the fn. by the same name in NodeBlender SeeAlso: ********************************************************************************************/ BOOL BrushRefBlender::BlendPaths(BlendPath* pBlendPathStart,BlendPath* pBlendPathEnd,double BlendRatio) { // Check entry params BOOL ok = (pBlendPathStart != NULL && pBlendPathEnd != NULL); if (ok) ok = (pBlendPathStart->GetBlendNode() != NULL && pBlendPathEnd->GetBlendNode() != NULL); ERROR3IF(!ok,"One or more NULL entry params"); if (!ok) return FALSE; // Get the types of the two paths PathTypeEnum PathTypeStart = pBlendPathStart->GetPathType(); PathTypeEnum PathTypeEnd = pBlendPathEnd ->GetPathType(); // The blended path will be closed if either of the paths is a shape BOOL Closed = (PathTypeStart == PATHTYPE_SHAPE) || (PathTypeEnd == PATHTYPE_SHAPE); Path * pPathStart = NULL; // Find the paths associated with the start and end blend paths if (pBlendPathStart->GetBlendNode()->IsNodePath()) { pPathStart = &(((NodePath *)pBlendPathStart->GetBlendNode())->InkPath); } else { pPathStart = &(((NodePath *)((NodeCompound *)pBlendPathStart->GetBlendNode())->GetNodeToBlend())->InkPath); } Path * pPathEnd = NULL; if (pBlendPathEnd->GetBlendNode()->IsNodePath()) { pPathEnd = &(((NodePath *)pBlendPathEnd->GetBlendNode())->InkPath); } else { pPathEnd = &(((NodePath *)((NodeCompound *)pBlendPathEnd->GetBlendNode())->GetNodeToBlend())->InkPath); } // Calculate how large the arrays have to be to store the blended path definition INT32 DestPathSize = ((pPathStart->GetNumCoords()+pPathEnd->GetNumCoords())*3)+500; // Get some arrays used to hold the blended path data, and error if any are NULL DocCoord* pDestCoords = GetCoordArray(DestPathSize); PathVerb* pDestVerbs = GetVerbArray(DestPathSize); PathFlags* pDestFlags = GetFlagArray(DestPathSize); UINT32* pBuff = GetGBlendBuff(DestPathSize); if (pDestCoords == NULL || pDestVerbs == NULL || pDestFlags == NULL || pBuff == NULL) return FALSE; // This section copes with the case when blending a line with a shape. // In this case we need to get a temp path the is actually a shape version of the line. // The line is simply reversed back onto itself to form a shape that would look identical to the // line if rendered. This allows the line to appear to open up to the shape when blended. Path Shape; if (PathTypeStart != PathTypeEnd) { BOOL ok = FALSE; if (!Shape.Initialise()) return FALSE; // if going from a line to a shape, convert the start path to a shape if (PathTypeStart == PATHTYPE_LINE && PathTypeEnd == PATHTYPE_SHAPE) { ok = NodeBlender::ConvertLineToShape(pPathStart,&Shape); pPathStart = &Shape; } // if going from a shape to a line, convert the end path to a shape if (PathTypeStart == PATHTYPE_SHAPE && PathTypeEnd == PATHTYPE_LINE) { ok = NodeBlender::ConvertLineToShape(pPathEnd,&Shape); pPathEnd = &Shape; } if (!ok) return FALSE; } BOOL OneToOne = FALSE; // Now actually blend the two paths GBlend GBlendObj; // Define the blend GBlendObj.Define( (PPOINT)pPathStart->GetCoordArray(), // Specify the start path pPathStart->GetVerbArray(), pPathStart->GetNumCoords(), (PPOINT)pPathEnd ->GetCoordArray(), // Specify the end path pPathEnd ->GetVerbArray(), pPathEnd ->GetNumCoords(), OneToOne, // The one-to-one flag FLATNESS, // Flatness pBuff, // Buffer for GBlend to use DestPathSize*sizeof(UINT32)); // The buffer size // Blend the paths m_ArrayLength = GBlendObj.Blend(BlendRatio, // The blend ratio, 0.0 < BlendRatio < 1.0 (PPOINT)pDestCoords, // Array to store blended coords pDestVerbs, // Array to store blended verbs DestPathSize); // The num elements in the two arrays // If we're blending a line to another line, we need to make sure that the blended line // is going in a direction that corresponds to the source lines. This ensures attributes // that depend on this direction (e.g. start and end arrows) look correct. // // When creating blend paths of lines, we can detect if the blend path has been reversed, // in relation to the original path, by the original mapping value. // If it's 0 it has NOT been reversed, otherwise it's been reversed. // // If the blend ratio is <=0.5, the blended path is closest to the start blend path, // so we look at the start blend path's original mapping. // // If blend ration > 0.5, look at the end blend path's original mapping. // // The (BlendRation <= 0.5) cut-off point is the same as the cut-off point used in the blending // of attributes. if (pBlendPathStart->IsLine() && pBlendPathEnd->IsLine()) { BlendPath* pBlendPath; if (BlendRatio <= 0.5) pBlendPath = pBlendPathStart; else pBlendPath = pBlendPathEnd; if (pBlendPath->GetOrigMapping() != 0) NodeBlender::ReversePath(pDestCoords,pDestVerbs,m_ArrayLength); } // We need to do some work on the blended path if (!NodeBlender::ProcessBlendedPath(pDestCoords,pDestVerbs,pDestFlags,m_ArrayLength,Closed)) return FALSE; return TRUE; }