// $Id: tracectl.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============================ */ // Tracer control class /* */ #include "camtypes.h" #include "tracectl.h" #include "tracergn.h" //#include "ccobject.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "bitmap.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "nodebmp.h" #include "bitmpinf.h" #include "bfxalu.h" #include "bfxpixop.h" #include "nodepath.h" #include "lineattr.h" //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "fillval.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "oilprog.h" //#include "andy.h" //#include "fillattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "fitcurve.h" #include "pathproc.h" #include "progress.h" //#include "tracerc.h" #include "keypress.h" // This is not compulsory, but you may as well put it in so that the correct version // of your file can be registered in the .exe DECLARE_SOURCE("$Revision: 1282 $"); // An implement to match the Declare in the .h file. // If you have many classes, it is recommended to place them all together, here at the start of the file CC_IMPLEMENT_DYNCREATE(TraceControl, BitmapEffectBase) // This will get Camelot to display the filename and linenumber of any memory allocations // that are not released at program exit #define new CAM_DEBUG_NEW #define TRACEUSESTATS 0 const INT32 MARKED_PIXEL = 0xFFFFFFFF; const INT32 CLEAR_PIXEL = 0xFF808080; const INT32 REGION_PIXEL = 0xFF000000; const INT32 BIT_SCANCOLOUR = 0x80000000; const INT32 BIT_THRESHOLD = 0x40000000; const INT32 BIT_CACHES_THRESHOLD = 0x01000000; const INT32 BIT_CACHEV_THRESHOLD = 0x02000000; const INT32 BIT_CACHES_COLOURTHRESHOLD = 0x04000000; const INT32 BIT_CACHEV_COLOURTHRESHOLD = 0x08000000; const INT32 BIT_CACHES_POSITIVE = 0x10000000; const INT32 BIT_CACHEV_POSITIVE = 0x20000000; #ifdef _DEBUG #define SetDebugBitmap(n,b) { if (DebugSwitch[n]) { if (!pDebug[n]) pDebug[n]=ALU->NewBitmap(pUndithered,0,0,32); \ DebugInvalid[n]=TRUE;\ if (pDebug[n]) { ALU->SetA(pDebug[n]); if (b) {ALU->SetB(b); ALU->ByteCopyBA();} \ else ALU->ZeroA(); } } } #else #define SetDebugBitmap(n,b) { if (FALSE); } #endif /******************************************************************************************* > TraceControl::TraceControl() : CCObject() Author: Alex_Bligh (Xara Group Ltd) Created: 14/12/94 Purpose: Constructs the trace control object *******************************************************************************************/ TraceControl::TraceControl() : BitmapEffectBase() { pOriginal = NULL; pProposed = NULL; pUndithered = NULL; pCurrentPath = NULL; pFill = NULL; pTraceRegion = NULL; pStats = NULL; pErrorRegionList = NULL; pThresholdPixelOp = NULL; pColourThresholdPixelOp = NULL; pPositivePixelOp = NULL; pTree=NULL; pJoinNode=NULL; #ifdef _DEBUG for (INT32 i=0; i TraceControl::~TraceControl() Author: Alex_Bligh (Xara Group Ltd) Created: 14/12/94 Purpose: Destructor, does nothing *******************************************************************************************/ TraceControl::~TraceControl() { InitBitmap(); // Free bitmaps if there are any RemoveTree(); if (pTraceRegion) delete pTraceRegion; if (pCurrentPath) delete pCurrentPath; if (pFill) delete pFill; if (pStats) delete pStats; if (pErrorRegionList) delete pErrorRegionList; if (pThresholdPixelOp) delete pThresholdPixelOp; if (pColourThresholdPixelOp) delete pColourThresholdPixelOp; if (pPositivePixelOp) delete pPositivePixelOp; #ifdef _DEBUG for (INT32 i=0; i BOOL TraceControl::GetParams( TraceMethod * pMethod = NULL, INT32 * pMinimumArea = NULL, double * pMaximumInitialAreaErrorThreshold = NULL, double * pMinimumInitialAreaErrorThreshold = NULL, double * pInitialAreaErrorRatio = NULL, double * pMinGradFillError = NULL, double * pGradFillErrorRatio = NULL, double * pMinPixelError = NULL, double * pMaxPixelError = NULL, double * pPixelErrorRatio = NULL, double * pQuantColours = NULL, double * pBlur = NULL, double * pSmooth = NULL ); Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: None Outputs: double values of parameters to read. May be set to NULL to not read them Returns: TRUE on success, FALSE (& error set) on failure Purpose: Gets the tracing parameters Errors: mainly memory. Scope: Public SeeAlso: - ********************************************************************************************/ BOOL TraceControl::GetParams( TraceMethod * pMethod, INT32 * pMinimumArea, double * pMaximumInitialAreaErrorThreshold, double * pMinimumInitialAreaErrorThreshold, double * pInitialAreaErrorRatio, double * pMinGradFillError, double * pGradFillErrorRatio, double * pMinPixelError, double * pMaxPixelError, double * pPixelErrorRatio, double * pQuantColours, double * pBlur, double * pSmooth ) { if (pMethod) *pMethod =Method; if (pMinimumArea) *pMinimumArea =MinimumArea; if (pMaximumInitialAreaErrorThreshold) *pMaximumInitialAreaErrorThreshold =MaximumInitialAreaErrorThreshold; if (pMinimumInitialAreaErrorThreshold) *pMinimumInitialAreaErrorThreshold =MinimumInitialAreaErrorThreshold; if (pInitialAreaErrorRatio) *pInitialAreaErrorRatio =InitialAreaErrorRatio; if (pMinGradFillError) *pMinGradFillError =MinGradFillError; if (pGradFillErrorRatio) *pGradFillErrorRatio =GradFillErrorRatio; if (pMinPixelError) *pMinPixelError =MinPixelError; if (pMaxPixelError) *pMaxPixelError =MaxPixelError; if (pPixelErrorRatio) *pPixelErrorRatio =PixelErrorRatio; if (pQuantColours) *pQuantColours =QuantColours; if (pBlur) *pBlur =Blur; if (pSmooth) *pSmooth =Smooth; return TRUE; } /******************************************************************************************** > BOOL TraceControl::SetParams( TraceMethod * pMethod = NULL, INT32 * pMinimumArea = NULL, double * pMaximumInitialAreaErrorThreshold = NULL, double * pMinimumInitialAreaErrorThreshold = NULL, double * pInitialAreaErrorRatio = NULL, double * pMinGradFillError = NULL, double * pGradFillErrorRatio = NULL, double * pMinPixelError = NULL, double * pMaxPixelError = NULL, double * pPixelErrorRatio = NULL, double * pQuantColours = NULL, double * pBlur = NULL, double * pSmooth = NULL ) Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: ^double values of parameters to set. May be set to NULL to not change them Outputs: None Returns: TRUE on success, FALSE (& error set) on failure Purpose: Sets the tracing parameters Errors: mainly memory. Scope: Public SeeAlso: - This call can only run after InitBitmaps() as some of the parameters live in the TraceRegion (yes, this is a bit messy) ********************************************************************************************/ BOOL TraceControl::SetParams( TraceMethod * pMethod, INT32 * pMinimumArea, double * pMaximumInitialAreaErrorThreshold, double * pMinimumInitialAreaErrorThreshold, double * pInitialAreaErrorRatio, double * pMinGradFillError, double * pGradFillErrorRatio, double * pMinPixelError, double * pMaxPixelError, double * pPixelErrorRatio, double * pQuantColours, double * pBlur, double * pSmooth ) { ERROR2IF((!pTraceRegion), FALSE, "SetParams() called without prior call to InitBitmaps()"); if (pMethod) Method =*pMethod; if (pMinimumArea) MinimumArea =*pMinimumArea; if (pMaximumInitialAreaErrorThreshold) MaximumInitialAreaErrorThreshold =*pMaximumInitialAreaErrorThreshold; if (pMinimumInitialAreaErrorThreshold) MinimumInitialAreaErrorThreshold =*pMinimumInitialAreaErrorThreshold; if (pInitialAreaErrorRatio) InitialAreaErrorRatio =*pInitialAreaErrorRatio; if (pMinGradFillError) MinGradFillError =*pMinGradFillError; if (pGradFillErrorRatio) GradFillErrorRatio =*pGradFillErrorRatio; if (pMinPixelError) MinPixelError =*pMinPixelError; if (pMaxPixelError) MaxPixelError =*pMaxPixelError; if (pPixelErrorRatio) PixelErrorRatio =*pPixelErrorRatio; if (pQuantColours) QuantColours =*pQuantColours; if (pBlur) Blur =*pBlur; if (pSmooth) Smooth =*pSmooth; return TRUE; } /******************************************************************************************** > BOOL TraceControl::InitBitmap( KernelBitmap * theOriginal = NULL); Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: theOriginal = pointer to the bitmap to use as the original to trace or NULL to cancel use of bitmap Outputs: theProposed = pointer to a place to store a pointer to the proposed image we'll be using. May be NULL if no record required of this. theTemp = pointer to a place to store a pointer to the temp image we'll be using. May be NULL if no record required of this. Returns: TRUE on success, FALSE (& error set) on failure Purpose: Initialises trace control object to work on a given set of bitmaps Errors: mainly memory. Scope: Public SeeAlso: - Also, if we don't have a traceregion or a grad fill we claim these. ********************************************************************************************/ BOOL TraceControl::InitBitmap( KernelBitmap * theOriginal) { #ifdef _DEBUG for (INT32 i=0; i BOOL TraceControl::RemoveTree(BOOL DeleteIt = TRUE) Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: DeleteIt = TRUE to delete the tree, FALSE to merely disclaim responsibility for it Outputs: None Returns: TRUE on success, FALSE (& error set) on failure Purpose: Relinquishes control of the tree structure Errors: mainly memory. Scope: Public SeeAlso: - This call would be called with TRUE to delete the tree for instance on destruction of the object or if the trace was cancelled, but with FALSE if the tree was inserted into a doc. This call does nothing if the tree isn't there ********************************************************************************************/ BOOL TraceControl::RemoveTree(BOOL DeleteIt) { if (!DeleteIt) {pTree = NULL; pJoinNode=NULL; return TRUE;} if (pTree) { Node * pFirstChild = pTree->FindFirstChild(); if (pFirstChild) pTree->DeleteChildren(pFirstChild); delete pTree; pTree = NULL; pJoinNode = NULL; } return TRUE; } /******************************************************************************************** > BOOL TraceControl::GetBitmaps( KernelBitmap * * theOriginal = NULL, KernelBitmap * * theProposed = NULL, KernelBitmap * * theUndithered = NULL ) Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: None Outputs: theOriginal = pointer to a place to store a pointer to the original to trace we'll be using. May be NULL if no record required of this. theProposed = pointer to a place to store a pointer to the proposed image we'll be using. May be NULL if no record required of this. theTemp = pointer to a place to store a pointer to the temp image we'll be using. May be NULL if no record required of this. Returns: TRUE on success, FALSE (& error set) on failure Purpose: Returns the current bitmap pointers Errors: None yet Scope: Public SeeAlso: - None the bitmap pointers may be NULL if not initialised yet (for instance if no tracing has yet been done). ********************************************************************************************/ BOOL TraceControl::GetBitmaps( KernelBitmap * * theOriginal, KernelBitmap * * theProposed, KernelBitmap * * theUndithered) { if (theOriginal) *theOriginal = pOriginal; if (theProposed) *theProposed = pProposed; if (theUndithered) *theUndithered = pUndithered; return TRUE; } /******************************************************************************************** > BOOL TraceControl::Trace(BOOL * Done) Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: None Outputs: Done - pointer to BOOL to indicate completion Returns: TRUE on success, FALSE (& error set) on failure Purpose: Spits out a single object into the trace output stream Errors: Many possible Scope: Public SeeAlso: - This is the central routine that actually orchestrates tracing of a single object. Note that a return of FALSE indicates an error condition (and tracing should be abandonned). Successful completion is indicated by the setting of the Done flag. It is guaranteed that (a) the Done flag will be either cleared or set (unless you are silly enough to pass a Null pointer in 0 in which case you wouldn't be able to check it anyway), and (b) the Done flag will be clear if an error condition occurs. Thus Done ALWAYS indicates successful completion. ********************************************************************************************/ BOOL TraceControl::Trace(BOOL * Done) { // First set done flag to false after checking it exists. This way all // exits will be clean. ERROR2IF(!Done, FALSE, "Don't call TraceControl::Trace with a NULL Done ptr"); *Done = FALSE; ERROR2IF((!pOriginal), FALSE, "Don't call TraceControl::Trace without calling InitBitmaps() first"); // Welcome to control algorithm No.2. Hopefully this one won't turn out exponential. // // Minor introduction to state storage: // // Various things about the state of tracing are stored across path generation. These are: // InitialAreaErrorThreshold - this decreases monotonically throughout the trace // ErrorRegionList - (see MarkInitialArea()) and it's information in top byte of ProposedImage // // No, OK it wasn't exponential, merely square. Now I allege we have an O(n) algorithm or at least // an O(n^(3/2)) one. Welcome to algorithm no. 3 and the world of the BfxPixelOp. // Do some stuff on the first call if (FirstShape) { if (!RemoveTree(TRUE)) return FALSE; if ((pTree = /*assign*/ new NodeGroup) ==NULL ) return FALSE; if ((pJoinNode = /*assign*/ new AttrStrokeColour(pTree, FIRSTCHILD)) == NULL) return FALSE; DocColour TheColour(COLOUR_NONE); ((AttrStrokeColour *)pJoinNode)->SetStartColour(&TheColour); NodePath * pRect; AttrFillGeometry * pRectCol; if (!(((pRect = /*assign*/ new NodePath(pJoinNode, NEXT))!=NULL) && pRect->SetUpPath(32, 32))) return FALSE; if (!(((pRectCol = /*assign*/ new AttrFlatColourFill(pRect, FIRSTCHILD))!=NULL))) return FALSE; pJoinNode=pRect; TRACEUSER( "Alex", _T("> Removing dither\n")); if (pProposed) delete pProposed; if (pUndithered) delete pUndithered; pProposed = NULL; pUndithered = NULL; ERROR2IF(!ALU, FALSE, "OK, where's the ALU gone?"); if (!ALU->SetB(pOriginal)) return FALSE; INT32 NumColours = (INT32)(17.5+(255.0-17.0)*QuantColours); if (Method==TRACEMETHOD_GREYSCALE) NumColours = (INT32)(2.5+(255.0-2.0)*QuantColours); if (!ALU->RemoveDither(&pUndithered, (INT32)(Blur*2.55+0.5), NumColours, Method)) return FALSE; ERROR2IF(!pUndithered, FALSE, "How come remove dither failed to generate a KB without generating an error"); EndSlowJob(); String_64 ProcessMessage(_R(IDS_TRACEDLG_TRACING)); BeginSlowJob(100, FALSE, &ProcessMessage); // Now we should go and claim proposed bitmap the size of the undithered one if ((pProposed = /*assign*/ ALU->NewBitmap(pUndithered,0,0/*,32*/)) == NULL) { delete pUndithered; pUndithered = NULL; return FALSE; } ALU->SetA(pProposed); ALU->ZeroA(0xFF); if (!ALU->GetSize(pUndithered, &XSize, &YSize)) return FALSE; if (!( pThresholdPixelOp->SetBitmap(pUndithered, BIT_CACHEV_THRESHOLD, BIT_CACHES_THRESHOLD, FALSE) && pColourThresholdPixelOp->SetBitmap(pUndithered, BIT_CACHEV_COLOURTHRESHOLD, BIT_CACHES_COLOURTHRESHOLD, FALSE) && pPositivePixelOp->SetBitmap(pUndithered, BIT_CACHEV_POSITIVE, BIT_CACHES_POSITIVE, FALSE) && TRUE)) return FALSE; TRACEUSER( "Alex", _T("> First shape, here we go\n")); FirstShape = FALSE; ShapeFreePass = FALSE; InitialAreaErrorThreshold = MaximumInitialAreaErrorThreshold; GradFillError = 1.0; NewGradFillError = 0.0; if (!( pThresholdPixelOp->ClearEntireCache() && pColourThresholdPixelOp->ClearEntireCache() && pPositivePixelOp->ClearEntireCache() && TRUE)) return FALSE; // set aux bitmaps up so we can start reading colours if (!pPositivePixelOp->SetAuxilliaryBitmaps(NULL, pProposed, pUndithered, 0, 0)) return FALSE; #ifdef _DEBUG for (INT32 i=0; iInkPath.FindStartOfPath(); // Start at bottom left corner pRect->InkPath.InsertMoveTo(DocCoord(0,0)); pRect->InkPath.InsertLineTo(DocCoord(0,YSize<<8)); pRect->InkPath.InsertLineTo(DocCoord(XSize<<8,YSize<<8)); pRect->InkPath.InsertLineTo(DocCoord(XSize<<8,0)); pRect->InkPath.InsertLineTo(DocCoord(0,0)); // Close the path properly pRect->InkPath.CloseSubPath(); pRect->InkPath.IsFilled = TRUE; // What lovely encapsulation & abstraction... // And give it a vaguely appropriate colour GFStartC = GFEndC = (DWORD)(pPositivePixelOp->ReadOriginalPixel(0, 0)) &0x00FFFFFF; INT32 R = 0; INT32 G = 0; INT32 B = 0; pPositivePixelOp->TranslateToRGB(GFStartC, pUndithered, &R, &G, &B); DocColour RectColour( R, G, B ); GFStartX = 0; GFStartY = 0; GFEndX = XSize; GFEndY = YSize; pRectCol->SetStartColour(&RectColour); return PlotGradFill(TRUE, FALSE); // And next time do some more interesting shapes as FirstShape is FALSE } // Set proposed path to whole bitmap HavePath = FALSE; // There are two embedded exits from the main region finding loop - when we've found a beneficial region we // want to stick in the tree, and when we have decided the trace is complete. while (TRUE) { ERROR1IF(KeyPress::IsEscapePressed(), FALSE, _R(IDS_ABORT)); // Get the next error area out. // Occasionally (when there are no regions left in the error region list), this will result in a // complete pass through the bitmap determining threshold error (and a recalculation of the threshold) // and the building up of a new error list. At the end we know that the temp bitmap is marked with // REGION_PIXEL if (!MarkInitialArea()) return FALSE; if (!FoundRegion) { // Wow! We've found *no* regions whose errors are greater than the Minimum threshold // error specified. This means (amazingly) we've done the trace! *Done = TRUE; return TRUE; } //TRACEUSER( "Alex", _T("Found Initial region at %d, %d, colour %x\n"),InitialX, InitialY, ScanPointColour); // We do a number of passes to refine the area. These consist of tracing the current region, finding the best // grad fill for it, and finding the region that grad fill positiviely affects. We must do at least two // passes. INT32 Passes = 0; BOOL UseColourThresholdPixelOp = TRUE; do { // InitialX & InitialY are set up. Our next step is to trace the region. We trace at first with // the minimum accuracy (i.e. the max PixelError) and reduce the accuracy until we have got a path // that works. Works means it has at least n% of the pixels of the original region. PixelError = MaxPixelError; BOOL RegionTraceable = FALSE; while ((PixelError>=MinPixelError) && !RegionTraceable) { if (!( pTraceRegion->SetParams(&PixelError) && TracePath(UseColourThresholdPixelOp?pColourThresholdPixelOp:pPositivePixelOp) && // Trace region to path CalculateStatistics() && // Calculate grad fill stats TRUE)) return FALSE; if (PixelError>MinPixelError) { PixelError *= PixelErrorRatio; if (PixelErrorN > 2); // Grrr with all these fancy pixel ops we can't get the area // as it's not calculated yet. Oh well. //( ((double)(pStats->N))/((double)RegionArea)>PixelAccuracyRatio ); } UseColourThresholdPixelOp = FALSE; if (RegionTraceable) { HavePath = TRUE; // We have to plot the things twice at the moment to do the error calc until we've performed // more GDraw mungery if (!( FormGradFill() && // Determine best grad fill // PlotGradFill(FALSE, FALSE) && // Plot proposed + grad fill to temp TRUE)) return FALSE; /* We'll have to do this another way (like just do it on the cached area) if (FoundRegion && (NewGradFillError >= GradFillError)) { TRACEUSER( "Alex", _T("> This region doesn't reduce the error!\n")); FoundRegion=FALSE; // We've gone seriously awry! } */ INT32 theErrorThreshold = (INT32)(0.5+3.0*DoubleSquare(InitialAreaErrorThreshold * 255.0)); if (!pPositivePixelOp->ClearCachedArea()) return FALSE; // This is a bodge until we have a way to calculate the true grad fill colour if (!pPositivePixelOp->SetAuxilliaryBitmaps(NULL, pProposed, pUndithered, theErrorThreshold, GFStartC /*pPositivePixelOp->ReadOriginalPixel(InitialX, InitialY) &0x00FFFFFF */)) return FALSE; // BODGE! We start in the middle and look around for somewhere helpful FoundRegion = pPositivePixelOp->IsInRegion(InitialX, InitialY); INT32 xadj; INT32 yadj; if (!FoundRegion) for (yadj=-1; (yadj<=1) && !FoundRegion; yadj++) for (xadj=-1; (xadj<=1) && !FoundRegion; xadj++) if (pPositivePixelOp->IsInRegion(InitialX+xadj, InitialY+yadj)) { FoundRegion=TRUE; InitialX+=xadj; InitialY+=yadj; // break done by setting FoundRegion as we can't do 2 level break } // OK OK so this clears out the cache. We've lost a maximum of 9 pixels. if (FoundRegion) if (!CheckMinimumArea(pPositivePixelOp)) return FALSE; // Sets FoundRegion RegionArea = MinimumArea + 1; // Bodge // If we marked any points as OK if (FoundRegion) { // Is the current path & grad fill good enough for us? // We must have done at least one top to bottom and one top to here pass. If we have // done the maximum number of passes that's OK too. Other than that, we continue refining // if the rate of decent of error is looking good. if (TRUE /*don't refine while we're not doing grad fills*/ || (Passes>=/*MaxRefinementPasses*/1) || ((Passes != 0) && !(FALSE /* some other loop criterion*/))) { if (!PlotGradFill(TRUE, TRUE)) return FALSE; if ((Smooth>3.0) && pCurrentPath) { // if (!pCurrentPath->InkPath.Smooth(Smooth*(256.0*65536.0/100.0))) return FALSE; INT32 orignumcoords = pCurrentPath->InkPath.GetNumCoords(); // create a path and quantise to it Path QuantPath; INT32 startpoints=((orignumcoords>24)?orignumcoords:24)*5; QuantPath.Initialise(startpoints,(startpoints>2400)?2400:startpoints); // quantise original path to QuantPath //pCurrentPath->InkPath.Quantise(64.0, &QuantPath); double coordsmooth = Smooth*10.24; /* Smooth/100.0*4.0*256.0; */ ProcessFlags QuantFlags(TRUE,FALSE,TRUE); pCurrentPath->InkPath.CreateFlatPath(QuantFlags, coordsmooth, &QuantPath); // create and fit a smooth curve to the data points. // Full range up to 4 pixels out CurveFitObject Smoothed(&QuantPath, coordsmooth*coordsmooth); if (Smoothed.Initialise(&QuantPath,QuantPath.GetNumCoords())) { Smoothed.FitCurve(); QuantPath.CloseSubPath(); QuantPath.IsFilled=TRUE; // now that we've smoothed the curve lets check on the number // of coordinates we've generated. If its more, then we should // leave the original path as it was. If its less then replace if (QuantPath.GetNumCoords()InkPath.CloneFrom(QuantPath)) return FALSE; } } ShapeFreePass=FALSE; //if (pErrorRegionList) pErrorRegionList->Empty(); // This is a bodge to ensure reevaluation GradFillError = NewGradFillError; if (pCurrentPath) NumPoints+=pCurrentPath->InkPath.GetNumCoords()/3; NumPaths++; //TRACEUSER( "Alex", _T("> Inserting path covering %d points after %d refinement(s)\n\n"),pStats->N, Passes); INT32 R = 0; INT32 G = 0; INT32 B = 0; pPositivePixelOp->TranslateToRGB(GFStartC, pUndithered, &R, &G, &B); DocColour TheColour( R, G, B ); AttrFillGeometry * pFillGeometry = new AttrFlatColourFill; if (!pFillGeometry) return FALSE; //new sets error pFillGeometry->SetStartColour(&TheColour); pFillGeometry->AttachNode(pCurrentPath, FIRSTCHILD); // The first node has to be attached under pTree, the rest to the right if (pJoinNode) { pCurrentPath->AttachNode(pJoinNode, NEXT); } else { pCurrentPath->AttachNode(pTree, FIRSTCHILD); } pJoinNode = pCurrentPath; pCurrentPath = NULL; return TRUE; } // We do a refinement } } else { FoundRegion = FALSE; // OK, so technically we found a region, but as we can't find // a path that encloses it, we might as well pretend we haven't... } Passes++; } while (FoundRegion); // repeat refinement if pur region hasn't disappeared - else try next one TRACEUSER( "Alex", _T("< Rejecting malformed error region\n")); HavePath = FALSE; } // We should never get here ERROR3("OK, How come a while(TRUE) terminated? Has someone inserted a break or what?"); return FALSE; } /* Here's the old ROD code: // Don't loop if we have a low enough grad fill error, or it is reducing slower than GradFillErrorRatio } while (((GradFillError > MinGradFillError) && ( (PreviousGradFillError / GradFillError) > (1.0/GradFillErrorRatio))) || (PreviousGradFillError ==0.0 )); */ /******************************************************************************************** > BOOL TraceControl::GetProgress(INT32 * pPercent = NULL, INT32 * pNumPaths = NULL, INT32 * pNumCusps = NULL, INT32 * pNumPoints = NULL, BOOL * pProgressDone = NULL) Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: Pointers to things to fill in Outputs: *pPercent = percentage of way through trace *pNumPaths = number of paths *pNumCusps = number of cusps *pNumPoints = number of points *pProgressDone = TRUE if complete else false Returns: TRUE on success, FALSE (& error set) on failure Purpose: Returning information for status line function Errors: None yet Scope: Public SeeAlso: None The pointers may be NULL if you're not interested in getting them filled in ********************************************************************************************/ BOOL TraceControl::GetProgress(INT32 * pPercent, INT32 * pNumPaths, INT32 * pNumCusps, INT32 * pNumPoints, BOOL * pProgressDone) { if (pProgressDone) *pProgressDone = ProgressDone && pOriginal; if (pPercent) { INT32 percent=ProgressDone?100:(INT32)(1.0+100.0*( ((double)ScanPointY/(double)(YSize?YSize:1)+(double)NumPasses)/(double)(TotalPasses?TotalPasses:1) )); if ((percent<0 ) || (!pOriginal) || (!ProgressDone && ScanPointX==0 && ScanPointY==0 && NumPasses==0)) percent=0; if (percent>100) percent=100; *pPercent=percent; } if (pNumPaths) *pNumPaths=NumPaths; if (pNumCusps) *pNumCusps=NumCusps; if (pNumPoints) *pNumPoints=NumPoints; return TRUE; } /******************************************************************************************** > BOOL TraceControl::CheckMinimumArea(BfxPixelOp * pBfxPixelOp) Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: pBfxPixelOp = pointer to the pixel op to use Outputs: None Returns: TRUE on success, FALSE (& error set) on failure Purpose: Determines whether atleast (MinimumArea) pixels are within the region Errors: None yet Scope: Public SeeAlso: TraceControl::FloodSearch FoundRegion is set according the the result. The search starts at InitialX/InitialY This call *must* be done immediately (yes, immediately) after the cache is cleared. If not you lose all your caching. Har har. ********************************************************************************************/ BOOL TraceControl::CheckMinimumArea(BfxPixelOp * pBfxPixelOp) { return pBfxPixelOp->CheckMinimumArea(MinimumArea, InitialX, InitialY, &FoundRegion); } /******************************************************************************************** > BOOL TraceControl::SetPath(NodePath * pNodePath) Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: pNodePath = pointer to the NP to use or NULL for whole image Outputs: None Returns: TRUE on success, FALSE (& error set) on failure Purpose: Sets up the NP to use Errors: None yet Scope: Public SeeAlso: - It is the callers responsibility to deal with the old NP (in terms of memory management) if this call is used. ********************************************************************************************/ BOOL TraceControl::SetPath(NodePath * pNodePath) { pCurrentPath = pNodePath; if (!pCurrentPath) HavePath = FALSE; return TRUE; } /******************************************************************************************** > NodePath * TraceControl::GetPaths() Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: None Outputs: None Returns: ^CurrentPath or NULL if none Purpose: Returns the current path Errors: None yet Scope: Public SeeAlso: - ********************************************************************************************/ Node * TraceControl::GetPaths() { return pTree; } /******************************************************************************************** > BOOL TraceControl::MarkInitialArea() Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: None Outputs: None Returns: TRUE on success, FALSE (& error set) on failure Purpose: Marks out intial area in temp bitmap Errors: Many possible Scope: Public SeeAlso: - We find the next error region in the error region list and ensure that this is marked as REGION_PIXEL in the temporary bitmap. We do this by copying from the top byte of the Proposed bitmap if there are more regions on the list, otherwise we generate a new set of error regions in the following manner: The temporary bitmap is marked such that all those points whose squared errors exceed InitialAreaError^2 are set and all others cleared. Then BuildErrorList is called to build up the error list (well that's a surprise). Note we don't actually need the path for this as we can assume that the whole path is set, which is fair enough as the initial area will only be required when we haven't got a path. ********************************************************************************************/ BOOL TraceControl::MarkInitialArea() { ERROR3IF (HavePath, "MarkInitialArea() called when a path was set up. Wierd..."); ERROR3IF ((!pUndithered) || (!pProposed), "MarkInitialArea() missing some bitmaps"); BOOL FirstCall = FALSE; if (!pErrorRegionList) { if ((pErrorRegionList = /*assign*/ new BfxErrorRegionList) == NULL) return FALSE; if (!pErrorRegionList->Init(1000)) { delete pErrorRegionList; pErrorRegionList = NULL; return FALSE; } FirstCall = TRUE; } // What the main loop does: (just in case you were wondering) // // Tries to find something on the error region list // If it's now empty: // tries the next scan point. // (If the next scanpoint is off the end of the bitmap, resets it and tries again using a lower threshold, // if it still can't find one, we're done) // Marks regions of approximately that colour which form the new error region list (sorted by size) // Takes the next error region // Repeats until the next error region is greater or equal to 2 pixels in size. do { pErrorRegionList->Next(); // Move on to next one (doens't move if EOL) while (!pErrorRegionList->GetCurrent(&InitialX,&InitialY,&RegionArea)) // at end of list { // Go back to start pErrorRegionList->ResetRead(); if (TRUE || ShapeFreePass || !pErrorRegionList->GetCurrent(&InitialX,&InitialY,&RegionArea)) // if no items on new list { // double AError = 0.0; // double BError = 0.0; // INT32 TotalPixels = 0; // INT32 MarkedPixels = 0; INT32 theErrorThreshold = (INT32)(0.5+3.0*DoubleSquare(InitialAreaErrorThreshold * 255.0)); // Find the next ScanPoint BOOL FoundScanPoint=FALSE; do { // Don't look for one if we haven't done the very first area mark yet if (!FirstCall) { theErrorThreshold = (INT32)(0.5+3.0*DoubleSquare(InitialAreaErrorThreshold * 255.0)); if (!(ALU->SetB(pProposed) && ALU->SetT(pUndithered))) return FALSE; if (!(ALU->ScanThreshold(theErrorThreshold, &ScanPointX, &ScanPointY, NULL, &FoundScanPoint, TRUE))) return FALSE; } // If we haven't done the first area mark yet, or didn't find a point, we need to mark the area if (FirstCall || !FoundScanPoint) { // Reset ScanPoint ScanPointX=0; ScanPointY=0; // Only on noninitial calls do we need to reduce the threshold if (!FirstCall) { NumPasses++; if (InitialAreaErrorThreshold-1E-10<=MinimumInitialAreaErrorThreshold) { FoundRegion = FALSE; // We haven't & can't find an error region - so all done! ProgressDone=TRUE; return TRUE; // OK though } InitialAreaErrorThreshold *= InitialAreaErrorRatio; } // We're certainly not an initial call now FirstCall=FALSE; // Calculate new error threshold if (InitialAreaErrorThreshold<=MinimumInitialAreaErrorThreshold) InitialAreaErrorThreshold=MinimumInitialAreaErrorThreshold; TRACEUSER( "Alex", _T("> Reducing InitialAreaErrorThreshold to %f\n"),InitialAreaErrorThreshold); } } while (!FoundScanPoint); // OK, we've found a ScanPoint to start off with. Hurrah. Now we take it's colour and build an ErrorRegion // List based on that colour // Clear the bit off, we don't want to deal with this pixel again unless it is remarked at a lower // threshold. Note that if we actuially plot over this pixel, We later clear this bit for anything // positively affected //pUndithered->PlotPixel(ScanPointX,ScanPointY, pUndithered->ReadPixel(ScanPointX, ScanPointY) & ~BIT_THRESHOLD); // Obtain the pixels colour ScanPointColour =pPositivePixelOp->ReadOriginalPixel(ScanPointX, ScanPointY) & 0x00FFFFFF; // Now mark on thresholded error. What this means is we're marking all the pixels of a similar colour // to the one which we've just found which are 'wrong' in the proposed bitmap as well. We'll use this // as the first basis of our trace. We hope the area is large enough to give us a good approximation to // a grad fill. This will hopefully extend over a larger area on the first refinement if (say) we've // picked a grad filled band. if (!pColourThresholdPixelOp->ClearCachedArea()) return FALSE; if (!pColourThresholdPixelOp->SetAuxilliaryBitmaps(NULL, pProposed, pUndithered, theErrorThreshold, ScanPointColour)) return FALSE; // For the time being just insert the area we are using InitialX=ScanPointX; InitialY=ScanPointY; ScanPointX++; if (ScanPointX>=XSize) {ScanPointX=0;ScanPointY++;} // we deliberately let this overrun // Check minimum area & set FoundRegion if (!CheckMinimumArea(pColourThresholdPixelOp)) return FALSE; RegionArea = MinimumArea + 1; // Bodge if (!pColourThresholdPixelOp->IsInRegion(InitialX, InitialY)) { FoundRegion=FALSE; // ERROR3IF(IsUserName("Alex"),"ScanThreshold lied"); } if (!pErrorRegionList->Empty()) return FALSE; if ((FoundRegion) && (RegionArea>=MinimumArea)) if (!(pErrorRegionList->Insert(InitialX, InitialY, RegionArea))) return FALSE; if (!(pErrorRegionList->Sort() && pErrorRegionList->ResetRead())) return FALSE; } // Mark begining of (as yet) shape free pass ShapeFreePass = TRUE; } // We've got one FoundRegion = TRUE; } while // validation - Loop if it's NOT (valid region area, AND this pixel set AND any adjacent pixel set) (!( (RegionArea>MinimumArea) && (pColourThresholdPixelOp->IsInRegion(InitialX, InitialY)) && ( // an adjacent pixel is set pColourThresholdPixelOp->IsInRegion(InitialX-1, InitialY ) || pColourThresholdPixelOp->IsInRegion(InitialX+1, InitialY ) || pColourThresholdPixelOp->IsInRegion(InitialX, InitialY-1) || pColourThresholdPixelOp->IsInRegion(InitialX-1, InitialY-1) || pColourThresholdPixelOp->IsInRegion(InitialX-1, InitialY-1) || pColourThresholdPixelOp->IsInRegion(InitialX, InitialY+1) || pColourThresholdPixelOp->IsInRegion(InitialX-1, InitialY+1) || pColourThresholdPixelOp->IsInRegion(InitialX-1, InitialY+1) ) )); return (pErrorRegionList->MarkUsed()); } /******************************************************************************************** > BOOL TraceControl::TracePath(BfxPixelOp * pBfxPixelOp) Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: None Outputs: None Returns: TRUE on success, FALSE (& error set) on failure Purpose: A NodePath is traced from the temporary bitmap Errors: Many possible Scope: Public SeeAlso: - The marked pixels from the temporary bitmap which are connected to Initial{X,Y} are turned into a NodePath which is then stored in CurrentPath. ********************************************************************************************/ BOOL TraceControl::TracePath(BfxPixelOp * pBfxPixelOp) { if (!pCurrentPath) if (!(((pCurrentPath = /*assign*/ new NodePath)!=NULL) && pCurrentPath->SetUpPath(32, 32))) return FALSE; ERROR3IF ((!pUndithered) || (!pProposed) || (!pBfxPixelOp), "TracePath() missing some bitmaps"); return( pTraceRegion->UseBitmap(pUndithered) && pTraceRegion->UsePath(&(pCurrentPath->InkPath)) && pTraceRegion->Trace(InitialX, InitialY, pBfxPixelOp) && TRUE ); } /******************************************************************************************** > BOOL TraceControl::CalculateStatistics() Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: None Outputs: None Returns: TRUE on success, FALSE (& error set) on failure Purpose: To determine the stats that help us find the optimum grad fill. Errors: Many possible Scope: Public SeeAlso: - GDraw is called to plot the trace path in a way such that we obtain various useful stats. ********************************************************************************************/ BOOL TraceControl::CalculateStatistics() { #if TRACEUSESTATS if (!pStats) if ((pStats = /*assign*/ new KernelStatistics) == NULL) return FALSE; ERROR3IF ((!pUndithered) || (!pProposed), "CalculateStatistics() missing some bitmaps"); ERROR2IF ((!pCurrentPath), FALSE, "CalculateStatistics() called without path to grad fill"); if (!(ALU->SetA(pUndithered))) return FALSE; //TRACEUSER( "Alex", _T("Calc stats\n")); return (ALU->GetStatistics(&(pCurrentPath->InkPath), pStats)); #else return TRUE; #endif } /******************************************************************************************** > BOOL TraceControl::FormGradFill() Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: None Outputs: None Returns: TRUE on success, FALSE (& error set) on failure Purpose: Calculate the optimum grad fill from the stats available. Errors: Many possible Scope: Public SeeAlso: - We do some cunning processing and end up with a grad fill attribute. We use the statistics from CalculateStatistics above. ********************************************************************************************/ BOOL TraceControl::FormGradFill() { GFStartX = 0; GFStartY = 0; GFEndX = XSize; GFEndY = YSize; #if TRACEUSESTATS ERROR3IF((!pStats), "How come we got here without a statistics block then?!"); INT32 R = (INT32)(pStats->R * 255.0); INT32 G = (INT32)(pStats->G * 255.0); INT32 B = (INT32)(pStats->B * 255.0); #if 0 UINT32 pval = (UINT32)(pStats->Pixel); R = (pval/*>>0*/) & 0xFF; // Bitmaps are TRGB, colourrefs are TBGR G = (pval>>8) & 0xFF; B = (pval>>16) & 0xFF; #endif if (R<0) R=0; if (G<0) G=0; if (B<0) B=0; if (R>255) R=255; if (G>255) G=255; if (B>255) B=255; GFStartC = GFEndC = (DWORD)(R | (G<<8) | (B<<16)); //TRACEUSER( "Alex", _T("Trying colour R=%d G=%d B=%d N=%d\n"),R,G,B,pStats->N); return TRUE; #else GFStartC = GFEndC = (DWORD)(pPositivePixelOp->ReadOriginalPixel(InitialX, InitialY)) &0x00FFFFFF; return TRUE; #endif } /******************************************************************************************** > BOOL TraceControl::PlotGradFill(BOOL ToProposed, BOOL UsePath = TRUE) Author: Alex_Bligh (Xara Group Ltd) Created: 17/1/95 Inputs: ToProposed set if to plot to Proposed rather than temp UsePath = TRUE to plot path, FALSE to plot whole area Outputs: None Returns: TRUE on success, FALSE (& error set) on failure Purpose: Plots the grad fill on top of the proposed bitmap into the temp bitmap Errors: Many possible Scope: Public SeeAlso: - Pretty much a straight GDraw call or two. ********************************************************************************************/ BOOL TraceControl::PlotGradFill(BOOL ToProposed, BOOL UsePath) { ERROR3IF ((!pUndithered) || (!pProposed) || (!ToProposed && !pUndithered), "PlotGradFill() missing some bitmaps"); if (UsePath) ERROR2IF ((!pCurrentPath), FALSE, "PlotGradFill() called without path to grad fill"); if (ToProposed) { if (!(ALU->SetA(pProposed))) return FALSE; } else { if (!(ALU->SetA(pUndithered) && ALU->SetB(pProposed) && ALU->ByteCopyBA())) return FALSE; } return (ALU->GradFillPath(UsePath?&(pCurrentPath->InkPath):NULL, GFStartC, GFEndC, GFStartX, GFStartY, GFEndX, GFEndY, FALSE, TRUE)); }