00001 // $Id: ppairbsh.cpp 1282 2006-06-09 09:46:49Z alex $ 00002 /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE 00003 ================================XARAHEADERSTART=========================== 00004 00005 Xara LX, a vector drawing and manipulation program. 00006 Copyright (C) 1993-2006 Xara Group Ltd. 00007 Copyright on certain contributions may be held in joint with their 00008 respective authors. See AUTHORS file for details. 00009 00010 LICENSE TO USE AND MODIFY SOFTWARE 00011 ---------------------------------- 00012 00013 This file is part of Xara LX. 00014 00015 Xara LX is free software; you can redistribute it and/or modify it 00016 under the terms of the GNU General Public License version 2 as published 00017 by the Free Software Foundation. 00018 00019 Xara LX and its component source files are distributed in the hope 00020 that it will be useful, but WITHOUT ANY WARRANTY; without even the 00021 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 00022 See the GNU General Public License for more details. 00023 00024 You should have received a copy of the GNU General Public License along 00025 with Xara LX (see the file GPL in the root directory of the 00026 distribution); if not, write to the Free Software Foundation, Inc., 51 00027 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 00028 00029 00030 ADDITIONAL RIGHTS 00031 ----------------- 00032 00033 Conditional upon your continuing compliance with the GNU General Public 00034 License described above, Xara Group Ltd grants to you certain additional 00035 rights. 00036 00037 The additional rights are to use, modify, and distribute the software 00038 together with the wxWidgets library, the wxXtra library, and the "CDraw" 00039 library and any other such library that any version of Xara LX relased 00040 by Xara Group Ltd requires in order to compile and execute, including 00041 the static linking of that library to XaraLX. In the case of the 00042 "CDraw" library, you may satisfy obligation under the GNU General Public 00043 License to provide source code by providing a binary copy of the library 00044 concerned and a copy of the license accompanying it. 00045 00046 Nothing in this section restricts any of the rights you have under 00047 the GNU General Public License. 00048 00049 00050 SCOPE OF LICENSE 00051 ---------------- 00052 00053 This license applies to this program (XaraLX) and its constituent source 00054 files only, and does not necessarily apply to other Xara products which may 00055 in part share the same code base, and are subject to their own licensing 00056 terms. 00057 00058 This license does not apply to files in the wxXtra directory, which 00059 are built into a separate library, and are subject to the wxWindows 00060 license contained within that directory in the file "WXXTRA-LICENSE". 00061 00062 This license does not apply to the binary libraries (if any) within 00063 the "libs" directory, which are subject to a separate license contained 00064 within that directory in the file "LIBS-LICENSE". 00065 00066 00067 ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS 00068 ---------------------------------------------- 00069 00070 Subject to the terms of the GNU Public License (see above), you are 00071 free to do whatever you like with your modifications. However, you may 00072 (at your option) wish contribute them to Xara's source tree. You can 00073 find details of how to do this at: 00074 http://www.xaraxtreme.org/developers/ 00075 00076 Prior to contributing your modifications, you will need to complete our 00077 contributor agreement. This can be found at: 00078 http://www.xaraxtreme.org/developers/contribute/ 00079 00080 Please note that Xara will not accept modifications which modify any of 00081 the text between the start and end of this header (marked 00082 XARAHEADERSTART and XARAHEADEREND). 00083 00084 00085 MARKS 00086 ----- 00087 00088 Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara 00089 designs are registered or unregistered trademarks, design-marks, and/or 00090 service marks of Xara Group Ltd. All rights in these marks are reserved. 00091 00092 00093 Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. 00094 http://www.xara.com/ 00095 00096 =================================XARAHEADEREND============================ 00097 */ 00098 // ppairbsh - Definition of PathProcessor class for rendering Airbrush strokes 00099 00100 #include "camtypes.h" 00101 00102 #include "ppairbsh.h" 00103 00104 //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00105 #include "cameleps.h" 00106 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00107 //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00108 //#include "fillval.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00109 //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00110 //#include "grndbmp.h" 00111 //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00112 #include "pathstrk.h" 00113 #include "pathtrap.h" 00114 #include "printctl.h" 00115 #include "qualattr.h" 00116 //#include "rndrgn.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00117 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00118 #include "strkattr.h" 00119 #include "valfunc.h" 00120 00121 CC_IMPLEMENT_DYNAMIC(PathProcessorStrokeAirbrush, PathProcessorStroke); 00122 00123 // Declare smart memory handling in Debug builds 00124 #define new CAM_DEBUG_NEW 00125 00126 const INT32 MaxAirbrushSteps = 48; // This is the max number of rings we'll plot 00127 // The user has to make a very thick stroke before 00128 // 48 rings are noticably "steppy". 00129 00130 00131 /******************************************************************************************** 00132 00133 > PathProcessorStrokeAirbrush::~PathProcessorStrokeAirbrush() 00134 00135 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00136 Created: 18/2/97 00137 00138 Purpose: Destructor 00139 00140 ********************************************************************************************/ 00141 00142 PathProcessorStrokeAirbrush::~PathProcessorStrokeAirbrush() 00143 { 00144 if (pIntensityFunction != NULL) 00145 delete pIntensityFunction; 00146 } 00147 00148 00149 00150 /******************************************************************************************** 00151 00152 > virtual BOOL PathProcessorStrokeAirbrush::WillChangeFillAndStrokeSeparately(void) 00153 00154 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00155 Created: 31/12/96 00156 00157 Returns: TRUE 00158 00159 Purpose: Called by the RenderRegion to determine if this PathProcessorStrokeAirbrush affects 00160 the "fill" and "stroke" portions of the path separately. (Generally 00161 speaking, only fill/stroke providers will cause these two different 00162 "bits" of a path to be rendered separately). This call is made BEFORE 00163 this Processor's ProcessPath function will be called. 00164 00165 If the caller gets a TRUE back, the stroke and fill paths will be 00166 rendered separately. 00167 00168 Notes: Base class implementation returns FALSE. Derived Stroke and Fill 00169 processors (such as this one) override this method to return TRUE. 00170 00171 ********************************************************************************************/ 00172 00173 BOOL PathProcessorStrokeAirbrush::WillChangeFillAndStrokeSeparately(void) 00174 { 00175 return(FALSE); 00176 } 00177 00178 00179 00180 /******************************************************************************************** 00181 00182 > virtual void PathProcessorStrokeAirbrush::ProcessPath(Path *pPath, 00183 RenderRegion *pRender, 00184 PathShape ShapePath=PATHSHAPE_PATH) 00185 00186 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00187 Created: 6/2/97 00188 00189 Purpose: Called by the RenderRegion to apply the path processing operation to 00190 the given path. 00191 00192 The PathProcessorStrokeAirbrush class changes the stroking (only) of paths 00193 passed into it. 00194 00195 Notes: * When rendering a path, always pass in your 'this' pointer to 00196 RenderRegion::DrawPath, so that you don't start an infinite 00197 recursion! 00198 00199 * To stop rendering of the path, simply return without calling the RR 00200 00201 * To render this path unchanged, simply call directly back to the RR: 00202 pRender->DrawPath(pPath, this); 00203 00204 * Only affect the fill of this path if pPath->IsFilled 00205 00206 * Only affect the stroke of this path if pPath->IsStroked 00207 00208 * If converting a path into a "filled path" for stroking, the output 00209 path should be rendered with IsStroked=FALSE or it'll get a line 00210 around the outside! 00211 00212 ********************************************************************************************/ 00213 00214 void PathProcessorStrokeAirbrush::ProcessPath(Path *pPath, 00215 RenderRegion *pRender, 00216 PathShape ShapePath) 00217 { 00218 PORTNOTETRACE("other","PathProcessorStrokeAirbrush::ProcessPath - do nothing"); 00219 #ifndef EXCLUDE_FROM_XARALX 00220 ERROR3IF(pPath == NULL || pRender == NULL, "Illegal NULL Params"); 00221 00222 // --- If the provided path is not stroked, then we'll just pass it straight through 00223 // We also don't touch it if we're doing EOR rendering, or click regions 00224 // BLOCK 00225 { 00226 StrokeColourAttribute *pStrokeColour = (StrokeColourAttribute *) pRender->GetCurrentAttribute(ATTR_STROKECOLOUR); 00227 if (pRender->DrawingMode != DM_COPYPEN || pRender->IsHitDetect() 00228 || !pPath->IsStroked || pStrokeColour == NULL || pStrokeColour->Colour.IsTransparent()) 00229 { 00230 pRender->DrawPath(pPath, this, ShapePath); 00231 return; 00232 } 00233 } 00234 00235 // --- If the quality is set low, strokes are just rendered as centrelines 00236 // BLOCK 00237 { 00238 QualityAttribute *pQuality = (QualityAttribute *) pRender->GetCurrentAttribute(ATTR_QUALITY); 00239 if (pQuality != NULL && pQuality->QualityValue.GetLineQuality() != Quality::FullLine) 00240 { 00241 pRender->DrawPath(pPath, this, ShapePath); 00242 return; 00243 } 00244 } 00245 00246 // --- If the attribute which created us is not the current StrokeType attribute, then 00247 // we have been overridden by a different stroke type, so we do nothing. 00248 // BLOCK 00249 { 00250 StrokeTypeAttrValue *pTypeAttr = (StrokeTypeAttrValue *) pRender->GetCurrentAttribute(ATTR_STROKETYPE); 00251 if (pTypeAttr != NULL && pTypeAttr != GetParentAttr()) 00252 { 00253 pRender->DrawPath(pPath, this, ShapePath); 00254 return; 00255 } 00256 } 00257 00258 // --- Get the current line width from the render region 00259 // In case of failure, we initialise with suitable defaults 00260 INT32 LineWidth = 5000; 00261 // BLOCK 00262 { 00263 LineWidthAttribute *pWidthAttr = (LineWidthAttribute *) pRender->GetCurrentAttribute(ATTR_LINEWIDTH); 00264 if (pWidthAttr != NULL) 00265 LineWidth = pWidthAttr->LineWidth; 00266 } 00267 00268 // Obtain an optimal number of steps for the line 00269 // When printing, we do heaps of steps to get top quality out the other end 00270 View *pView = pRender->GetRenderView(); 00271 ERROR3IF(pView == NULL, "No render view?!"); 00272 00273 INT32 NumSteps = MaxAirbrushSteps; 00274 if (!pRender->IsPrinting()) 00275 GetNumSteps(pView, LineWidth); 00276 00277 // --- Now, create a transparency mask bitmap for the airbrush 00278 Spread *pSpread = pRender->GetRenderSpread(); 00279 // ERROR3IF(pSpread == NULL, "No render spread!?"); // This can happen, rendering into a gallery 00280 00281 // Get the render region's clip rectangle in Spread Coords. We don't need to 00282 // render anything bigger than this size, so it is the upper limit on our bitmap area. 00283 DocRect ClipRegion = pRender->GetClipRect(); 00284 00285 // Intersect this with the path bounding rectangle to get the actual region we need to redraw 00286 // The smaller this is, the faster we go and the less memory we use. 00287 //DocRect PathRect = pPath->GetBoundingRect(); 00288 DocRect PathRect = pPath->GetBlobRect(); 00289 PathRect.Inflate(LineWidth); 00290 00291 BOOL Intersects = ClipRegion.IsIntersectedWith(PathRect); 00292 if(!Intersects) 00293 { 00294 // Don't bother drawing anything - it's clipped out 00295 return; 00296 } 00297 00298 ClipRegion = ClipRegion.Intersection(PathRect); 00299 00300 // Round the ClipRegion edges up so they all lie exactly on screen pixel boundaries. 00301 // If we don't do this, then there can be a sub-pixel rounding error between the ClipRegion 00302 // and the actual bitmap size, so that the bitmap is scaled slightly when we plot it. 00303 // By making sure it's pixelised, we guarantee that the bitmap & clipregion are exactly equal sizes. 00304 // (It doesn't matter if the bitmap is a bit bigger than necessary) 00305 ClipRegion.Inflate(pRender->GetScaledPixelWidth()); 00306 ClipRegion.lo.Pixelise(pView); 00307 ClipRegion.hi.Pixelise(pView); 00308 00309 // Get the current view's rendering matrix and view scale 00310 Matrix ConvMatrix = pRender->GetMatrix();//pView->ConstructRenderingMatrix(pSpread); 00311 FIXED16 ViewScale = pView->GetViewScale(); 00312 00313 // Generate a 256-colour greyscale palette 00314 LOGPALETTE *pPalette = MakeGreyScalePalette(); 00315 if(pPalette == NULL) 00316 { 00317 pRender->DrawPath(pPath, this, ShapePath); 00318 return; 00319 } 00320 00321 // Work out the DPI to use. Rather than just asking for PixelWidth or DPI from the 00322 // render region, we have to do a load of non-object-oriented stuff instead... 00323 double dpi = 96.0; 00324 if (pRender->IsPrinting()) 00325 { 00326 // we are printing, so ask the print options 00327 PrintControl *pPrintControl = pView->GetPrintControl(); 00328 if (pPrintControl != NULL) 00329 dpi = (double) pPrintControl->GetDotsPerInch(); 00330 } 00331 else if (IS_A(pRender, CamelotEPSRenderRegion)) 00332 { 00333 // Use DPI as set in EPS export options dialog. 00334 dpi = (double) EPSFilter::XSEPSExportDPI; 00335 } 00336 else 00337 { 00338 ERROR3IF(pRender->GetPixelWidth() <= 0, "Stupid (<1 millipoint) Pixel Width!"); 00339 if (pRender->GetPixelWidth() > 0) 00340 dpi = (double) (72000.0 / (double)pRender->GetPixelWidth()); 00341 } 00342 00343 GRenderBitmap *pMaskRegion = new GRenderBitmap(ClipRegion, ConvMatrix, ViewScale, 8, dpi, 00344 pRender->IsPrinting(), XARADITHER_ORDERED_GREY, 00345 pPalette, FALSE); 00346 if (pMaskRegion == NULL) 00347 { 00348 pRender->DrawPath(pPath, this, ShapePath); 00349 return; 00350 } 00351 00352 BOOL PathIsFilled = FALSE; // Will be set TRUE if this path should be filled at the bottom of the function 00353 00354 //BLOCK 00355 { 00356 // Change the GDraw context in this render region so as to preserve the state 00357 // of the main render region. This is because GRenderRegions currently use 00358 // a single static GDrawContext! This also sets it up with a nice greyscale palette 00359 // so that we get the output we desire. 00360 pMaskRegion->UseGreyscaleContextDangerous(); 00361 00362 // Attach our DC to the view and render region... 00363 if (pMaskRegion->AttachDevice(pView, NULL, pSpread)) 00364 { 00365 pMaskRegion->StartRender(); 00366 00367 // We must save & restore the attribute state around all our rendering because 00368 // attributes otherwise stay on the renderstack until we delete the RndRgn, and as our 00369 // ones are on the program stack, they will have ceased to exist before then, which 00370 // makes for a wicked explosion. 00371 pMaskRegion->SaveContext(); 00372 00374 // --- Main Airbrush rendering loop 00375 00376 // Make sure we've got an intensity function to use. This will create a new one if necessary 00377 ValueFunction *pvValueFunction = GetIntensityFunction(); 00378 if (pvValueFunction == NULL) 00379 { 00380 ERROR3("Failed to set an intensity function on an airbrush stroke"); 00381 pRender->DrawPath(pPath, this, ShapePath); 00382 return; 00383 } 00384 00385 00386 if(!RenderAirBrush(pPath, pMaskRegion, LineWidth, NumSteps, pvValueFunction, 00387 pRender, ShapePath)) 00388 { 00389 // Airbrush failed - just render the path without the airbrush effect 00390 pRender->DrawPath(pPath, this, ShapePath); 00391 return; 00392 } 00393 00394 pMaskRegion->RestoreContext(); 00395 00396 // --- ClipRect test code 00397 00399 00400 00401 // --- We have drawn the airbrushed stroke - now, if the path is filled, we 00402 // will render the filled area, so that in semi-transparent cases, the 00403 // stroke will not "show through" from behind the filled area. 00404 if (pPath->IsFilled) 00405 { 00406 ColourFillAttribute *pCFAttr = (ColourFillAttribute *) pRender->GetCurrentAttribute(ATTR_FILLGEOMETRY); 00407 if (pCFAttr != NULL && (!pCFAttr->Colour.IsTransparent() || pCFAttr->IsABitmapFill())) 00408 { 00409 PathIsFilled = TRUE; 00410 00411 pMaskRegion->SaveContext(); 00412 00413 ColourFillAttribute *pFillAttr = NULL; 00414 FillMappingAttribute *pMapAttr = NULL; 00415 00416 // Obtain the object's transparent fill geometry 00417 TranspFillAttribute *pTransAttr = (TranspFillAttribute *) pRender->GetCurrentAttribute(ATTR_TRANSPFILLGEOMETRY); 00418 if (pTransAttr != NULL) 00419 { 00420 // Get a non-transparent version of the fill geometry 00421 pFillAttr = pTransAttr->MakeSimilarNonTranspFillGeometry(1.0); 00422 00423 // Convert a fill mapping 00424 TranspFillMappingAttribute *pTransMapAttr = (TranspFillMappingAttribute *) pRender->GetCurrentAttribute(ATTR_TRANSPFILLMAPPING); 00425 if(pTransMapAttr != NULL) 00426 pMapAttr = pTransMapAttr->MakeSimilarNonTranspFillMapping(); 00427 } 00428 00429 // Setup region and draw path into it 00430 if (pFillAttr != NULL) 00431 { 00432 pMaskRegion->SetFillGeometry(pFillAttr, TRUE); 00433 00434 if(pMapAttr != NULL) 00435 pMaskRegion->SetFillMapping(pMapAttr, TRUE); 00436 } 00437 else 00438 pMaskRegion->SetFillColour(DocColour(0, 0, 0)); 00439 00440 pMaskRegion->SetLineColour(DocColour(COLOUR_TRANS)); 00441 pMaskRegion->DrawPath(pPath, NULL, ShapePath); 00442 00443 pMaskRegion->RestoreContext(); 00444 } 00445 } 00446 00447 pMaskRegion->StopRender(); 00448 } 00449 00450 pMaskRegion->StopUsingGreyscaleContextDangerous(); 00451 } 00452 00453 // Extract the transparency mask bitmap from the render region 00454 OILBitmap *pOilBmp = pMaskRegion->ExtractBitmap(); 00455 00456 // We no longer need the RenderRegion, so scrap it. 00457 delete pMaskRegion; 00458 pMaskRegion = NULL; 00459 pPalette = NULL; 00460 00461 // Now, render a rectangle to the output render region, using the transparency mask 00462 if (pOilBmp == NULL) 00463 return; 00464 00465 KernelBitmap *pMask = new KernelBitmap(pOilBmp, TRUE); 00466 00467 if (pMask != NULL) 00468 { 00469 // Make sure the bitmap knows it's already a greyscale, else it will spend a lot of 00470 // time "converting" itself to a greyscale, and what's more, corrupting the grey levels 00471 // so that 255 (invisible) becomes 254 (slightly visible). Arrrrrgh! 00472 pMask->SetAsGreyscale(); 00473 00474 // Create a transparency attribute from our mask bitmap 00475 BitmapTranspFillAttribute Trans; 00476 00477 // We don't call pTrans->AttachBitmap because it seems to be stupid, and causes ructions 00478 // when we try to attach a temporary bitmap. We thus do the same thing, but avoiding 00479 // its attempts to automatically screw us about. 00480 Trans.BitmapRef.Detach(); 00481 Trans.BitmapRef.SetBitmap(pMask); 00482 00483 Trans.SetStartPoint(&ClipRegion.lo); 00484 DocCoord EndPoint(ClipRegion.hi.x, ClipRegion.lo.y); 00485 Trans.SetEndPoint(&EndPoint); 00486 DocCoord EndPoint2(ClipRegion.lo.x, ClipRegion.hi.y); 00487 Trans.SetEndPoint2(&EndPoint2); 00488 00489 UINT32 TValue = 0; 00490 Trans.SetStartTransp(&TValue); 00491 TValue = 255; 00492 Trans.SetEndTransp(&TValue); 00493 00494 // Use the same transparency type as is set on the object being rendered (if any) 00495 { 00496 TranspFillAttribute *pTransAttr = (TranspFillAttribute *) pRender->GetCurrentAttribute(ATTR_TRANSPFILLGEOMETRY); 00497 00498 if (pTransAttr != NULL) 00499 Trans.SetTranspType(pTransAttr->GetTranspType()); 00500 else 00501 Trans.SetTranspType(TT_Mix); // By default, we'll use Mix transparency 00502 00503 } 00504 00505 // --- OK, we finally got here! Render the stroke, using the transparency mask we just made 00506 pRender->SaveContext(); 00507 00508 Trans.Render(pRender); 00509 00510 // Render the path. If it is filled, then we render the entire thing (fill & stroke) using 00511 // the current fill geometry (to get a shadow/feather effect) 00512 if (PathIsFilled) 00513 { 00514 // Render the entire thing (fill & stroke) in one go. We render a rectangle over the cliprect 00515 // so that we do everything in one go (we can't render the fill &7 stroke separately, or 00516 // the transparency will overlap & it'll look wrong) 00517 pRender->SetLineColour(DocColour(COLOUR_TRANS)); // Don't render a line 00518 00519 Path Rect; 00520 Rect.Initialise(); 00521 Rect.AddMoveTo(ClipRegion.lo); 00522 Rect.AddLineTo(DocCoord(ClipRegion.hix, ClipRegion.loy)); 00523 Rect.AddLineTo(ClipRegion.hi); 00524 Rect.AddLineTo(DocCoord(ClipRegion.lox, ClipRegion.hiy)); 00525 Rect.AddLineTo(ClipRegion.lo); 00526 Rect.IsFilled = TRUE; 00527 Rect.IsStroked = FALSE; 00528 pRender->DrawPath(&Rect, this, ShapePath); 00529 } 00530 else 00531 { 00532 // Otherwise, create a filled-outline path for the entire stroke, and render it 00533 // !!!!****ToDo - for now, strokes always render flat-filled with the stroke colour 00534 StrokeColourAttribute *pStrokeColour = (StrokeColourAttribute *) pRender->GetCurrentAttribute(ATTR_STROKECOLOUR); 00535 if (pStrokeColour != NULL) 00536 pRender->SetFillColour(pStrokeColour->Colour); 00537 00538 // Fill the holes 00539 pRender->SetWindingRule(NonZeroWinding); 00540 00541 Path *pOutput = CreateVarWidthStroke(pPath, pRender, LineWidth); 00542 if (pOutput != NULL) 00543 { 00544 pRender->DrawPath(pOutput, NULL, ShapePath); 00545 delete pOutput; 00546 pOutput = NULL; 00547 } 00548 } 00549 00550 pRender->RestoreContext(); 00551 00552 // Delete the kernel bitmap. This auto-deletes the OIL bitmap for us 00553 delete pMask; 00554 } 00555 #endif 00556 } 00557 00558 00559 00560 /******************************************************************************************** 00561 00562 > virtual BOOL PathProcessorStrokeAirbrush::IsDifferent(PathProcessorStroke *pOther); 00563 00564 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00565 Created: 6/2/97 00566 00567 Inputs: pOther - The other PathProcessorStroke 00568 00569 Returns: TRUE if they're considered different, FALSE if they are equivalent 00570 00571 Purpose: Equality operator 00572 00573 Notes: The base class implementation compares the runtime classes of the 00574 2 objects to see if they are different classes. If they are the same 00575 class, it assumes they're cnsidered equal - derived classes should 00576 override this behaviour if further comparisons are necessary. 00577 00578 ********************************************************************************************/ 00579 00580 BOOL PathProcessorStrokeAirbrush::IsDifferent(PathProcessorStroke *pOther) 00581 { 00582 ERROR3IF(pOther == NULL, "Illegal NULL param"); 00583 00584 if (GetRuntimeClass() != pOther->GetRuntimeClass()) 00585 return(TRUE); 00586 00587 PathProcessorStrokeAirbrush *pOtherAir = (PathProcessorStrokeAirbrush *)pOther; 00588 00589 ValueFunction *pMyFunc = GetIntensityFunction(); 00590 ValueFunction *pOtherFunc = pOtherAir->GetIntensityFunction(); 00591 if (pMyFunc == NULL || pOtherFunc == NULL) 00592 return(TRUE); 00593 00594 // We're the same if we share the same intensity function 00595 return(pMyFunc->IsDifferent(pOtherFunc)); 00596 } 00597 00598 00599 00600 /******************************************************************************************** 00601 00602 > virtual PathProcessorStrokeAirbrush *PathProcessorStrokeAirbrush::Clone(void) 00603 00604 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00605 Created: 6/2/97 00606 00607 Returns: NULL if we're out of memory, else 00608 a pointer to a clone (exact copy) of this object 00609 00610 Purpose: To copy PathProcessorStrokeAirbrush or derived-class object 00611 00612 ********************************************************************************************/ 00613 00614 PathProcessorStroke *PathProcessorStrokeAirbrush::Clone(void) 00615 { 00616 // Clone this object - this can be done by just creating a new one 00617 PathProcessorStrokeAirbrush *pClone = new PathProcessorStrokeAirbrush; 00618 00619 if (pClone != NULL) 00620 { 00621 // And copy the (base class) parent-attribute pointer so we know when our 00622 // parent attribute is "current" in the render region. 00623 pClone->SetParentAttr(GetParentAttr()); 00624 00625 // And copy our intensity function, if any 00626 if (pIntensityFunction != NULL) 00627 { 00628 ValueFunction *pNewFunc = pIntensityFunction->Clone(); 00629 pClone->SetIntensityFunction(pNewFunc); // This is OK if it's NULL 00630 } 00631 } 00632 00633 return(pClone); 00634 } 00635 00636 00637 00638 /******************************************************************************************** 00639 > virtual INT32 GetNumSteps(View *pView, INT32 LineWidth) 00640 00641 Author: Richard_Millican (Xara Group Ltd) <camelotdev@xara.com> 00642 Created: 11/2/97 00643 Inputs: pView - Relevant View (for zoom info) 00644 LineWidth - LineWidth in millipoints 00645 Returns: Number of steps which will produce a smooth graduated airbrush for this 00646 line at the current zoom, etc... 00647 00648 Purpose: To get the number of steps for an airbrush stroke 00649 ********************************************************************************************/ 00650 00651 INT32 PathProcessorStrokeAirbrush::GetNumSteps(View *pView, INT32 LineWidth) 00652 { 00653 if(pView == NULL) 00654 return 1; 00655 00656 // Find out how many millipoints 1 pixel is 00657 const INT32 spw = pView->GetScaledPixelWidth().MakeLong(); 00658 const INT32 sph = pView->GetScaledPixelHeight().MakeLong(); 00659 const MILLIPOINT OnePixel = min(spw, sph); 00660 TRACEUSER( "Richard", _T("1 pixel = %d\n"), OnePixel); 00661 00662 if (OnePixel <= 0) 00663 return(1); 00664 00665 // Now work out how many steps to render. The fewer steps, the faster we go, but this 00666 // trades off against quality. Because we start in the centre and increase in both 00667 // directions, we need to divide line width by the size of 2 pixels to get a step per pixel. 00668 // However, 3 is used because it means each ring of the airbrush is 1.5 pixels wider than the 00669 // last, which, with anti-aliasing, gives a very smooth effect. Going up to 4 makes each one 00670 // 2 pixels, and you can begin to see tree-ring effects in the airbrush. 00671 INT32 NumSteps = (INT32)(LineWidth / (OnePixel * 3)); 00672 00673 // Limit it to a sensible range 00674 if(NumSteps < 1) 00675 NumSteps = 1; 00676 if(NumSteps > MaxAirbrushSteps) 00677 NumSteps = MaxAirbrushSteps; 00678 00679 TRACEUSER( "Richard", _T("NumSteps = %d\n"), NumSteps); 00680 00681 return NumSteps; 00682 } 00683 00684 00685 00686 /******************************************************************************************** 00687 > LOGPALETTE *PathProcessorStrokeAirbrush::MakeGreyScalePalette(void) 00688 00689 Author: Richard_Millican (Xara Group Ltd) <camelotdev@xara.com> 00690 Created: 11/2/97 00691 Returns: A 256 entry greyscale palette, or NULL if it failed 00692 00693 Purpose: Obtain a greyscale palette 00694 00695 Notes: A static palette is cached to save building one for every redraw 00696 00697 ********************************************************************************************/ 00698 00699 LOGPALETTE *PathProcessorStrokeAirbrush::MakeGreyScalePalette(void) 00700 { 00701 static BYTE Pal[sizeof(LOGPALETTE) + 256*sizeof(PALETTEENTRY)]; 00702 static LOGPALETTE *pPal = NULL; 00703 00704 if (pPal == NULL) // Only fill in the palette the first time we're called 00705 { 00706 pPal = (LOGPALETTE *) Pal; 00707 00708 pPal->palVersion = 0x300; 00709 pPal->palNumEntries = 256; 00710 for (INT32 i = 0; i < 256; i++) 00711 { 00712 pPal->palPalEntry[i].peRed = i; 00713 pPal->palPalEntry[i].peGreen = i; 00714 pPal->palPalEntry[i].peBlue = i; 00715 pPal->palPalEntry[i].peFlags = 0; 00716 } 00717 } 00718 00719 return pPal; 00720 } 00721 00722 00723 00724 /******************************************************************************************** 00725 00726 > virtual BOOL PathProcessorStrokeAirbrush::RenderAirBrush(Path *pPath, 00727 GRenderBitmap *pRegion, INT32 LineWidth, INT32 NumSteps, 00728 ValueFunction *pvValueFunction, RenderRegion *pDestRegion, 00729 PathShape ShapePath) 00730 00731 Author: Richard_Millican (Xara Group Ltd) <camelotdev@xara.com> 00732 Created: 11/2/97 00733 Inputs: pPath - Path to render 00734 pRegion - Region to render the airbrush tranparency mask into 00735 LineWidth - Maximum linewidth 00736 NumSteps - Number of Airbrush graduation steps 00737 pvValueFunction - Value function for the edge rampyness 00738 pDestRegion - Region we are rendering the airbrush to in the end 00739 (needed to get current stroke attributes from) 00740 ShapePath - The PathShape of the current shape. 00741 Returns: TRUE if things went ok 00742 00743 Purpose: Main Airbrush rendering loop 00744 ********************************************************************************************/ 00745 00746 BOOL PathProcessorStrokeAirbrush::RenderAirBrush(Path *pPath, GRenderBitmap *pRegion, 00747 INT32 LineWidth, INT32 NumSteps, ValueFunction *pvValueFunction, 00748 RenderRegion *pDestRegion, PathShape ShapePath) 00749 { 00750 PORTNOTETRACE("other","PathProcessorStrokeAirbrush::RenderAirBrush - do nothing"); 00751 #ifndef EXCLUDE_FROM_XARALX 00752 if(pPath == NULL || pRegion == NULL || NumSteps <= 0) 00753 { 00754 ERROR3("PathProcessorStrokeAirbrush::RenderAirBrush given dodgy params"); 00755 return FALSE; 00756 } 00757 00758 // Fill the holes 00759 pRegion->SetWindingRule(NonZeroWinding); 00760 00761 // Precalculate the trapezoid structure for stroking with 00762 TrapsList *pTrapezoids = PrepareTrapsForStroke(pPath, pDestRegion, LineWidth); 00763 if (pTrapezoids == NULL) 00764 return FALSE; 00765 00766 INT32 LastIntensity = 255; 00767 00768 // Use the previously obtained number of steps for the graduation 00769 for(INT32 i = NumSteps; i > 0; i--) 00770 { 00771 INT32 Intensity = 0; 00772 00773 // What intensity should we use ? 00774 if(pvValueFunction == NULL) 00775 { 00776 // A linear ramp 00777 Intensity = (255 * i) / NumSteps; 00778 } 00779 else 00780 { 00781 // Use the value function 00782 double Position = (double)i / (double)NumSteps; 00783 Intensity = (INT32)((1.0 - pvValueFunction->GetValue(Position)) * 255.0); 00784 } 00785 00786 // Bit dodgy for 1st stroke, but then it shouldn't be the same as the last 00787 if(LastIntensity != Intensity) 00788 { 00789 LastIntensity = Intensity; 00790 00791 if(Intensity != 255) 00792 { 00793 // --- Create the stroke outline by calling our helper function 00794 INT32 ThisWidth = (LineWidth * i) / NumSteps; 00795 00796 Path *pOutput = CreateVarWidthStroke(pPath, pDestRegion, ThisWidth, pTrapezoids); 00797 if(pOutput != NULL) 00798 { 00799 ColourFillAttribute *pFillAttr = NULL; 00800 FillMappingAttribute *pMapAttr = NULL; 00801 00802 if(pDestRegion != NULL) 00803 { 00804 // Obtain the object's transparent fill geometry 00805 TranspFillAttribute *pTransAttr = (TranspFillAttribute *) pDestRegion->GetCurrentAttribute(ATTR_TRANSPFILLGEOMETRY); 00806 if (pTransAttr == NULL) 00807 { 00808 // There is no transparency on this object - just use a flat transparency 00809 pFillAttr = new FlatFillAttribute; 00810 if (pFillAttr) 00811 pFillAttr->SetStartColour(&DocColour(Intensity, Intensity, Intensity)); 00812 } 00813 else 00814 { 00815 // Get a non-transparent version of the fill geometry 00816 pFillAttr = pTransAttr->MakeSimilarNonTranspFillGeometry(1.0 - ((double)(Intensity) / 255.0)); 00817 00818 // Convert a fill mapping 00819 TranspFillMappingAttribute *pTransMapAttr = (TranspFillMappingAttribute *) pDestRegion->GetCurrentAttribute(ATTR_TRANSPFILLMAPPING); 00820 if(pTransMapAttr != NULL) 00821 pMapAttr = pTransMapAttr->MakeSimilarNonTranspFillMapping(); 00822 } 00823 } 00824 00825 // Setup region and draw path into it 00826 if(pFillAttr != NULL) 00827 { 00828 pRegion->SetFillGeometry(pFillAttr, TRUE); 00829 00830 if(pMapAttr != NULL) 00831 pRegion->SetFillMapping(pMapAttr, TRUE); 00832 } 00833 else 00834 pRegion->SetFillColour(DocColour(Intensity, Intensity, Intensity)); 00835 00836 pRegion->DrawPath(pOutput, NULL, ShapePath); 00837 delete pOutput; 00838 } 00839 } 00840 } 00841 } 00842 00843 delete pTrapezoids; 00844 00845 return TRUE; 00846 #else 00847 return FALSE; 00848 #endif 00849 } 00850 00851 00852 00853 /******************************************************************************************** 00854 00855 > ValueFunction *PathProcessorStrokeAirbrush::GetIntensityFunction(void) 00856 00857 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00858 Created: 18/2/97 00859 00860 Returns: NULL (if it fails horribly), else a pointer to the intensity ValueFunction 00861 00862 Purpose: Sets the intensity function which will be used when rendering this airbrush. 00863 The function controls the intensity (opacity) of the airbrush at Position 00864 values where Position 0.0 represents the airbrush centreline, and 1.0 00865 represents the maximum width. 00866 00867 SeeAlso: PathProcessorStrokeAirbrush::GetIntensityFunction 00868 00869 ********************************************************************************************/ 00870 00871 ValueFunction *PathProcessorStrokeAirbrush::GetIntensityFunction(void) 00872 { 00873 if (pIntensityFunction == NULL) 00874 pIntensityFunction = new ValueFunctionRampS(1.0, 0.0); 00875 00876 return(pIntensityFunction); 00877 } 00878 00879 00880 00881 /******************************************************************************************** 00882 00883 > void PathProcessorStrokeAirbrush::SetIntensityFunction(ValueFunction *pFunc) 00884 00885 Author: Jason_Williams (Xara Group Ltd) <camelotdev@xara.com> 00886 Created: 18/2/97 00887 00888 Inputs: pFunc - The new ValueFunction to use 00889 A value of NULL will set the airbrush to the default setting (using 00890 an ValueFunctionRampS from 1.0 to 0.0) 00891 00892 Purpose: Sets the intensity function which will be used when rendering this airbrush. 00893 The function controls the intensity (opacity) of the airbrush at Position 00894 values where Position 0.0 represents the airbrush centreline, and 1.0 00895 represents the maximum width. 00896 00897 SeeAlso: PathProcessorStrokeAirbrush::GetIntensityFunction 00898 00899 ********************************************************************************************/ 00900 00901 void PathProcessorStrokeAirbrush::SetIntensityFunction(ValueFunction *pFunc) 00902 { 00903 if (pIntensityFunction != NULL) 00904 delete pIntensityFunction; 00905 00906 pIntensityFunction = pFunc; 00907 } 00908