// $Id: ppairbsh.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============================ */ // ppairbsh - Definition of PathProcessor class for rendering Airbrush strokes #include "camtypes.h" #include "ppairbsh.h" //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "cameleps.h" //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "fillval.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "grndbmp.h" //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "pathstrk.h" #include "pathtrap.h" #include "printctl.h" #include "qualattr.h" //#include "rndrgn.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "strkattr.h" #include "valfunc.h" CC_IMPLEMENT_DYNAMIC(PathProcessorStrokeAirbrush, PathProcessorStroke); // Declare smart memory handling in Debug builds #define new CAM_DEBUG_NEW const INT32 MaxAirbrushSteps = 48; // This is the max number of rings we'll plot // The user has to make a very thick stroke before // 48 rings are noticably "steppy". /******************************************************************************************** > PathProcessorStrokeAirbrush::~PathProcessorStrokeAirbrush() Author: Jason_Williams (Xara Group Ltd) Created: 18/2/97 Purpose: Destructor ********************************************************************************************/ PathProcessorStrokeAirbrush::~PathProcessorStrokeAirbrush() { if (pIntensityFunction != NULL) delete pIntensityFunction; } /******************************************************************************************** > virtual BOOL PathProcessorStrokeAirbrush::WillChangeFillAndStrokeSeparately(void) Author: Jason_Williams (Xara Group Ltd) Created: 31/12/96 Returns: TRUE Purpose: Called by the RenderRegion to determine if this PathProcessorStrokeAirbrush affects the "fill" and "stroke" portions of the path separately. (Generally speaking, only fill/stroke providers will cause these two different "bits" of a path to be rendered separately). This call is made BEFORE this Processor's ProcessPath function will be called. If the caller gets a TRUE back, the stroke and fill paths will be rendered separately. Notes: Base class implementation returns FALSE. Derived Stroke and Fill processors (such as this one) override this method to return TRUE. ********************************************************************************************/ BOOL PathProcessorStrokeAirbrush::WillChangeFillAndStrokeSeparately(void) { return(FALSE); } /******************************************************************************************** > virtual void PathProcessorStrokeAirbrush::ProcessPath(Path *pPath, RenderRegion *pRender, PathShape ShapePath=PATHSHAPE_PATH) Author: Jason_Williams (Xara Group Ltd) Created: 6/2/97 Purpose: Called by the RenderRegion to apply the path processing operation to the given path. The PathProcessorStrokeAirbrush class changes the stroking (only) of paths passed into it. Notes: * When rendering a path, always pass in your 'this' pointer to RenderRegion::DrawPath, so that you don't start an infinite recursion! * To stop rendering of the path, simply return without calling the RR * To render this path unchanged, simply call directly back to the RR: pRender->DrawPath(pPath, this); * Only affect the fill of this path if pPath->IsFilled * Only affect the stroke of this path if pPath->IsStroked * If converting a path into a "filled path" for stroking, the output path should be rendered with IsStroked=FALSE or it'll get a line around the outside! ********************************************************************************************/ void PathProcessorStrokeAirbrush::ProcessPath(Path *pPath, RenderRegion *pRender, PathShape ShapePath) { PORTNOTETRACE("other","PathProcessorStrokeAirbrush::ProcessPath - do nothing"); #ifndef EXCLUDE_FROM_XARALX ERROR3IF(pPath == NULL || pRender == NULL, "Illegal NULL Params"); // --- If the provided path is not stroked, then we'll just pass it straight through // We also don't touch it if we're doing EOR rendering, or click regions // BLOCK { StrokeColourAttribute *pStrokeColour = (StrokeColourAttribute *) pRender->GetCurrentAttribute(ATTR_STROKECOLOUR); if (pRender->DrawingMode != DM_COPYPEN || pRender->IsHitDetect() || !pPath->IsStroked || pStrokeColour == NULL || pStrokeColour->Colour.IsTransparent()) { pRender->DrawPath(pPath, this, ShapePath); return; } } // --- If the quality is set low, strokes are just rendered as centrelines // BLOCK { QualityAttribute *pQuality = (QualityAttribute *) pRender->GetCurrentAttribute(ATTR_QUALITY); if (pQuality != NULL && pQuality->QualityValue.GetLineQuality() != Quality::FullLine) { pRender->DrawPath(pPath, this, ShapePath); return; } } // --- If the attribute which created us is not the current StrokeType attribute, then // we have been overridden by a different stroke type, so we do nothing. // BLOCK { StrokeTypeAttrValue *pTypeAttr = (StrokeTypeAttrValue *) pRender->GetCurrentAttribute(ATTR_STROKETYPE); if (pTypeAttr != NULL && pTypeAttr != GetParentAttr()) { pRender->DrawPath(pPath, this, ShapePath); return; } } // --- Get the current line width from the render region // In case of failure, we initialise with suitable defaults INT32 LineWidth = 5000; // BLOCK { LineWidthAttribute *pWidthAttr = (LineWidthAttribute *) pRender->GetCurrentAttribute(ATTR_LINEWIDTH); if (pWidthAttr != NULL) LineWidth = pWidthAttr->LineWidth; } // Obtain an optimal number of steps for the line // When printing, we do heaps of steps to get top quality out the other end View *pView = pRender->GetRenderView(); ERROR3IF(pView == NULL, "No render view?!"); INT32 NumSteps = MaxAirbrushSteps; if (!pRender->IsPrinting()) GetNumSteps(pView, LineWidth); // --- Now, create a transparency mask bitmap for the airbrush Spread *pSpread = pRender->GetRenderSpread(); // ERROR3IF(pSpread == NULL, "No render spread!?"); // This can happen, rendering into a gallery // Get the render region's clip rectangle in Spread Coords. We don't need to // render anything bigger than this size, so it is the upper limit on our bitmap area. DocRect ClipRegion = pRender->GetClipRect(); // Intersect this with the path bounding rectangle to get the actual region we need to redraw // The smaller this is, the faster we go and the less memory we use. //DocRect PathRect = pPath->GetBoundingRect(); DocRect PathRect = pPath->GetBlobRect(); PathRect.Inflate(LineWidth); BOOL Intersects = ClipRegion.IsIntersectedWith(PathRect); if(!Intersects) { // Don't bother drawing anything - it's clipped out return; } ClipRegion = ClipRegion.Intersection(PathRect); // Round the ClipRegion edges up so they all lie exactly on screen pixel boundaries. // If we don't do this, then there can be a sub-pixel rounding error between the ClipRegion // and the actual bitmap size, so that the bitmap is scaled slightly when we plot it. // By making sure it's pixelised, we guarantee that the bitmap & clipregion are exactly equal sizes. // (It doesn't matter if the bitmap is a bit bigger than necessary) ClipRegion.Inflate(pRender->GetScaledPixelWidth()); ClipRegion.lo.Pixelise(pView); ClipRegion.hi.Pixelise(pView); // Get the current view's rendering matrix and view scale Matrix ConvMatrix = pRender->GetMatrix();//pView->ConstructRenderingMatrix(pSpread); FIXED16 ViewScale = pView->GetViewScale(); // Generate a 256-colour greyscale palette LOGPALETTE *pPalette = MakeGreyScalePalette(); if(pPalette == NULL) { pRender->DrawPath(pPath, this, ShapePath); return; } // Work out the DPI to use. Rather than just asking for PixelWidth or DPI from the // render region, we have to do a load of non-object-oriented stuff instead... double dpi = 96.0; if (pRender->IsPrinting()) { // we are printing, so ask the print options PrintControl *pPrintControl = pView->GetPrintControl(); if (pPrintControl != NULL) dpi = (double) pPrintControl->GetDotsPerInch(); } else if (IS_A(pRender, CamelotEPSRenderRegion)) { // Use DPI as set in EPS export options dialog. dpi = (double) EPSFilter::XSEPSExportDPI; } else { ERROR3IF(pRender->GetPixelWidth() <= 0, "Stupid (<1 millipoint) Pixel Width!"); if (pRender->GetPixelWidth() > 0) dpi = (double) (72000.0 / (double)pRender->GetPixelWidth()); } GRenderBitmap *pMaskRegion = new GRenderBitmap(ClipRegion, ConvMatrix, ViewScale, 8, dpi, pRender->IsPrinting(), XARADITHER_ORDERED_GREY, pPalette, FALSE); if (pMaskRegion == NULL) { pRender->DrawPath(pPath, this, ShapePath); return; } BOOL PathIsFilled = FALSE; // Will be set TRUE if this path should be filled at the bottom of the function //BLOCK { // Change the GDraw context in this render region so as to preserve the state // of the main render region. This is because GRenderRegions currently use // a single static GDrawContext! This also sets it up with a nice greyscale palette // so that we get the output we desire. pMaskRegion->UseGreyscaleContextDangerous(); // Attach our DC to the view and render region... if (pMaskRegion->AttachDevice(pView, NULL, pSpread)) { pMaskRegion->StartRender(); // We must save & restore the attribute state around all our rendering because // attributes otherwise stay on the renderstack until we delete the RndRgn, and as our // ones are on the program stack, they will have ceased to exist before then, which // makes for a wicked explosion. pMaskRegion->SaveContext(); ///////////////////////////////////////////////////////////////////////////////////// // --- Main Airbrush rendering loop // Make sure we've got an intensity function to use. This will create a new one if necessary ValueFunction *pvValueFunction = GetIntensityFunction(); if (pvValueFunction == NULL) { ERROR3("Failed to set an intensity function on an airbrush stroke"); pRender->DrawPath(pPath, this, ShapePath); return; } if(!RenderAirBrush(pPath, pMaskRegion, LineWidth, NumSteps, pvValueFunction, pRender, ShapePath)) { // Airbrush failed - just render the path without the airbrush effect pRender->DrawPath(pPath, this, ShapePath); return; } pMaskRegion->RestoreContext(); // --- ClipRect test code ///////////////////////////////////////////////////////////////////////////////////// // --- We have drawn the airbrushed stroke - now, if the path is filled, we // will render the filled area, so that in semi-transparent cases, the // stroke will not "show through" from behind the filled area. if (pPath->IsFilled) { ColourFillAttribute *pCFAttr = (ColourFillAttribute *) pRender->GetCurrentAttribute(ATTR_FILLGEOMETRY); if (pCFAttr != NULL && (!pCFAttr->Colour.IsTransparent() || pCFAttr->IsABitmapFill())) { PathIsFilled = TRUE; pMaskRegion->SaveContext(); ColourFillAttribute *pFillAttr = NULL; FillMappingAttribute *pMapAttr = NULL; // Obtain the object's transparent fill geometry TranspFillAttribute *pTransAttr = (TranspFillAttribute *) pRender->GetCurrentAttribute(ATTR_TRANSPFILLGEOMETRY); if (pTransAttr != NULL) { // Get a non-transparent version of the fill geometry pFillAttr = pTransAttr->MakeSimilarNonTranspFillGeometry(1.0); // Convert a fill mapping TranspFillMappingAttribute *pTransMapAttr = (TranspFillMappingAttribute *) pRender->GetCurrentAttribute(ATTR_TRANSPFILLMAPPING); if(pTransMapAttr != NULL) pMapAttr = pTransMapAttr->MakeSimilarNonTranspFillMapping(); } // Setup region and draw path into it if (pFillAttr != NULL) { pMaskRegion->SetFillGeometry(pFillAttr, TRUE); if(pMapAttr != NULL) pMaskRegion->SetFillMapping(pMapAttr, TRUE); } else pMaskRegion->SetFillColour(DocColour(0, 0, 0)); pMaskRegion->SetLineColour(DocColour(COLOUR_TRANS)); pMaskRegion->DrawPath(pPath, NULL, ShapePath); pMaskRegion->RestoreContext(); } } pMaskRegion->StopRender(); } pMaskRegion->StopUsingGreyscaleContextDangerous(); } // Extract the transparency mask bitmap from the render region OILBitmap *pOilBmp = pMaskRegion->ExtractBitmap(); // We no longer need the RenderRegion, so scrap it. delete pMaskRegion; pMaskRegion = NULL; pPalette = NULL; // Now, render a rectangle to the output render region, using the transparency mask if (pOilBmp == NULL) return; KernelBitmap *pMask = new KernelBitmap(pOilBmp, TRUE); if (pMask != NULL) { // Make sure the bitmap knows it's already a greyscale, else it will spend a lot of // time "converting" itself to a greyscale, and what's more, corrupting the grey levels // so that 255 (invisible) becomes 254 (slightly visible). Arrrrrgh! pMask->SetAsGreyscale(); // Create a transparency attribute from our mask bitmap BitmapTranspFillAttribute Trans; // We don't call pTrans->AttachBitmap because it seems to be stupid, and causes ructions // when we try to attach a temporary bitmap. We thus do the same thing, but avoiding // its attempts to automatically screw us about. Trans.BitmapRef.Detach(); Trans.BitmapRef.SetBitmap(pMask); Trans.SetStartPoint(&ClipRegion.lo); DocCoord EndPoint(ClipRegion.hi.x, ClipRegion.lo.y); Trans.SetEndPoint(&EndPoint); DocCoord EndPoint2(ClipRegion.lo.x, ClipRegion.hi.y); Trans.SetEndPoint2(&EndPoint2); UINT32 TValue = 0; Trans.SetStartTransp(&TValue); TValue = 255; Trans.SetEndTransp(&TValue); // Use the same transparency type as is set on the object being rendered (if any) { TranspFillAttribute *pTransAttr = (TranspFillAttribute *) pRender->GetCurrentAttribute(ATTR_TRANSPFILLGEOMETRY); if (pTransAttr != NULL) Trans.SetTranspType(pTransAttr->GetTranspType()); else Trans.SetTranspType(TT_Mix); // By default, we'll use Mix transparency } // --- OK, we finally got here! Render the stroke, using the transparency mask we just made pRender->SaveContext(); Trans.Render(pRender); // Render the path. If it is filled, then we render the entire thing (fill & stroke) using // the current fill geometry (to get a shadow/feather effect) if (PathIsFilled) { // Render the entire thing (fill & stroke) in one go. We render a rectangle over the cliprect // so that we do everything in one go (we can't render the fill &7 stroke separately, or // the transparency will overlap & it'll look wrong) pRender->SetLineColour(DocColour(COLOUR_TRANS)); // Don't render a line Path Rect; Rect.Initialise(); Rect.AddMoveTo(ClipRegion.lo); Rect.AddLineTo(DocCoord(ClipRegion.hix, ClipRegion.loy)); Rect.AddLineTo(ClipRegion.hi); Rect.AddLineTo(DocCoord(ClipRegion.lox, ClipRegion.hiy)); Rect.AddLineTo(ClipRegion.lo); Rect.IsFilled = TRUE; Rect.IsStroked = FALSE; pRender->DrawPath(&Rect, this, ShapePath); } else { // Otherwise, create a filled-outline path for the entire stroke, and render it // !!!!****ToDo - for now, strokes always render flat-filled with the stroke colour StrokeColourAttribute *pStrokeColour = (StrokeColourAttribute *) pRender->GetCurrentAttribute(ATTR_STROKECOLOUR); if (pStrokeColour != NULL) pRender->SetFillColour(pStrokeColour->Colour); // Fill the holes pRender->SetWindingRule(NonZeroWinding); Path *pOutput = CreateVarWidthStroke(pPath, pRender, LineWidth); if (pOutput != NULL) { pRender->DrawPath(pOutput, NULL, ShapePath); delete pOutput; pOutput = NULL; } } pRender->RestoreContext(); // Delete the kernel bitmap. This auto-deletes the OIL bitmap for us delete pMask; } #endif } /******************************************************************************************** > virtual BOOL PathProcessorStrokeAirbrush::IsDifferent(PathProcessorStroke *pOther); Author: Jason_Williams (Xara Group Ltd) Created: 6/2/97 Inputs: pOther - The other PathProcessorStroke Returns: TRUE if they're considered different, FALSE if they are equivalent Purpose: Equality operator Notes: The base class implementation compares the runtime classes of the 2 objects to see if they are different classes. If they are the same class, it assumes they're cnsidered equal - derived classes should override this behaviour if further comparisons are necessary. ********************************************************************************************/ BOOL PathProcessorStrokeAirbrush::IsDifferent(PathProcessorStroke *pOther) { ERROR3IF(pOther == NULL, "Illegal NULL param"); if (GetRuntimeClass() != pOther->GetRuntimeClass()) return(TRUE); PathProcessorStrokeAirbrush *pOtherAir = (PathProcessorStrokeAirbrush *)pOther; ValueFunction *pMyFunc = GetIntensityFunction(); ValueFunction *pOtherFunc = pOtherAir->GetIntensityFunction(); if (pMyFunc == NULL || pOtherFunc == NULL) return(TRUE); // We're the same if we share the same intensity function return(pMyFunc->IsDifferent(pOtherFunc)); } /******************************************************************************************** > virtual PathProcessorStrokeAirbrush *PathProcessorStrokeAirbrush::Clone(void) Author: Jason_Williams (Xara Group Ltd) Created: 6/2/97 Returns: NULL if we're out of memory, else a pointer to a clone (exact copy) of this object Purpose: To copy PathProcessorStrokeAirbrush or derived-class object ********************************************************************************************/ PathProcessorStroke *PathProcessorStrokeAirbrush::Clone(void) { // Clone this object - this can be done by just creating a new one PathProcessorStrokeAirbrush *pClone = new PathProcessorStrokeAirbrush; if (pClone != NULL) { // And copy the (base class) parent-attribute pointer so we know when our // parent attribute is "current" in the render region. pClone->SetParentAttr(GetParentAttr()); // And copy our intensity function, if any if (pIntensityFunction != NULL) { ValueFunction *pNewFunc = pIntensityFunction->Clone(); pClone->SetIntensityFunction(pNewFunc); // This is OK if it's NULL } } return(pClone); } /******************************************************************************************** > virtual INT32 GetNumSteps(View *pView, INT32 LineWidth) Author: Richard_Millican (Xara Group Ltd) Created: 11/2/97 Inputs: pView - Relevant View (for zoom info) LineWidth - LineWidth in millipoints Returns: Number of steps which will produce a smooth graduated airbrush for this line at the current zoom, etc... Purpose: To get the number of steps for an airbrush stroke ********************************************************************************************/ INT32 PathProcessorStrokeAirbrush::GetNumSteps(View *pView, INT32 LineWidth) { if(pView == NULL) return 1; // Find out how many millipoints 1 pixel is const INT32 spw = pView->GetScaledPixelWidth().MakeLong(); const INT32 sph = pView->GetScaledPixelHeight().MakeLong(); const MILLIPOINT OnePixel = min(spw, sph); TRACEUSER( "Richard", _T("1 pixel = %d\n"), OnePixel); if (OnePixel <= 0) return(1); // Now work out how many steps to render. The fewer steps, the faster we go, but this // trades off against quality. Because we start in the centre and increase in both // directions, we need to divide line width by the size of 2 pixels to get a step per pixel. // However, 3 is used because it means each ring of the airbrush is 1.5 pixels wider than the // last, which, with anti-aliasing, gives a very smooth effect. Going up to 4 makes each one // 2 pixels, and you can begin to see tree-ring effects in the airbrush. INT32 NumSteps = (INT32)(LineWidth / (OnePixel * 3)); // Limit it to a sensible range if(NumSteps < 1) NumSteps = 1; if(NumSteps > MaxAirbrushSteps) NumSteps = MaxAirbrushSteps; TRACEUSER( "Richard", _T("NumSteps = %d\n"), NumSteps); return NumSteps; } /******************************************************************************************** > LOGPALETTE *PathProcessorStrokeAirbrush::MakeGreyScalePalette(void) Author: Richard_Millican (Xara Group Ltd) Created: 11/2/97 Returns: A 256 entry greyscale palette, or NULL if it failed Purpose: Obtain a greyscale palette Notes: A static palette is cached to save building one for every redraw ********************************************************************************************/ LOGPALETTE *PathProcessorStrokeAirbrush::MakeGreyScalePalette(void) { static BYTE Pal[sizeof(LOGPALETTE) + 256*sizeof(PALETTEENTRY)]; static LOGPALETTE *pPal = NULL; if (pPal == NULL) // Only fill in the palette the first time we're called { pPal = (LOGPALETTE *) Pal; pPal->palVersion = 0x300; pPal->palNumEntries = 256; for (INT32 i = 0; i < 256; i++) { pPal->palPalEntry[i].peRed = i; pPal->palPalEntry[i].peGreen = i; pPal->palPalEntry[i].peBlue = i; pPal->palPalEntry[i].peFlags = 0; } } return pPal; } /******************************************************************************************** > virtual BOOL PathProcessorStrokeAirbrush::RenderAirBrush(Path *pPath, GRenderBitmap *pRegion, INT32 LineWidth, INT32 NumSteps, ValueFunction *pvValueFunction, RenderRegion *pDestRegion, PathShape ShapePath) Author: Richard_Millican (Xara Group Ltd) Created: 11/2/97 Inputs: pPath - Path to render pRegion - Region to render the airbrush tranparency mask into LineWidth - Maximum linewidth NumSteps - Number of Airbrush graduation steps pvValueFunction - Value function for the edge rampyness pDestRegion - Region we are rendering the airbrush to in the end (needed to get current stroke attributes from) ShapePath - The PathShape of the current shape. Returns: TRUE if things went ok Purpose: Main Airbrush rendering loop ********************************************************************************************/ BOOL PathProcessorStrokeAirbrush::RenderAirBrush(Path *pPath, GRenderBitmap *pRegion, INT32 LineWidth, INT32 NumSteps, ValueFunction *pvValueFunction, RenderRegion *pDestRegion, PathShape ShapePath) { PORTNOTETRACE("other","PathProcessorStrokeAirbrush::RenderAirBrush - do nothing"); #ifndef EXCLUDE_FROM_XARALX if(pPath == NULL || pRegion == NULL || NumSteps <= 0) { ERROR3("PathProcessorStrokeAirbrush::RenderAirBrush given dodgy params"); return FALSE; } // Fill the holes pRegion->SetWindingRule(NonZeroWinding); // Precalculate the trapezoid structure for stroking with TrapsList *pTrapezoids = PrepareTrapsForStroke(pPath, pDestRegion, LineWidth); if (pTrapezoids == NULL) return FALSE; INT32 LastIntensity = 255; // Use the previously obtained number of steps for the graduation for(INT32 i = NumSteps; i > 0; i--) { INT32 Intensity = 0; // What intensity should we use ? if(pvValueFunction == NULL) { // A linear ramp Intensity = (255 * i) / NumSteps; } else { // Use the value function double Position = (double)i / (double)NumSteps; Intensity = (INT32)((1.0 - pvValueFunction->GetValue(Position)) * 255.0); } // Bit dodgy for 1st stroke, but then it shouldn't be the same as the last if(LastIntensity != Intensity) { LastIntensity = Intensity; if(Intensity != 255) { // --- Create the stroke outline by calling our helper function INT32 ThisWidth = (LineWidth * i) / NumSteps; Path *pOutput = CreateVarWidthStroke(pPath, pDestRegion, ThisWidth, pTrapezoids); if(pOutput != NULL) { ColourFillAttribute *pFillAttr = NULL; FillMappingAttribute *pMapAttr = NULL; if(pDestRegion != NULL) { // Obtain the object's transparent fill geometry TranspFillAttribute *pTransAttr = (TranspFillAttribute *) pDestRegion->GetCurrentAttribute(ATTR_TRANSPFILLGEOMETRY); if (pTransAttr == NULL) { // There is no transparency on this object - just use a flat transparency pFillAttr = new FlatFillAttribute; if (pFillAttr) pFillAttr->SetStartColour(&DocColour(Intensity, Intensity, Intensity)); } else { // Get a non-transparent version of the fill geometry pFillAttr = pTransAttr->MakeSimilarNonTranspFillGeometry(1.0 - ((double)(Intensity) / 255.0)); // Convert a fill mapping TranspFillMappingAttribute *pTransMapAttr = (TranspFillMappingAttribute *) pDestRegion->GetCurrentAttribute(ATTR_TRANSPFILLMAPPING); if(pTransMapAttr != NULL) pMapAttr = pTransMapAttr->MakeSimilarNonTranspFillMapping(); } } // Setup region and draw path into it if(pFillAttr != NULL) { pRegion->SetFillGeometry(pFillAttr, TRUE); if(pMapAttr != NULL) pRegion->SetFillMapping(pMapAttr, TRUE); } else pRegion->SetFillColour(DocColour(Intensity, Intensity, Intensity)); pRegion->DrawPath(pOutput, NULL, ShapePath); delete pOutput; } } } } delete pTrapezoids; return TRUE; #else return FALSE; #endif } /******************************************************************************************** > ValueFunction *PathProcessorStrokeAirbrush::GetIntensityFunction(void) Author: Jason_Williams (Xara Group Ltd) Created: 18/2/97 Returns: NULL (if it fails horribly), else a pointer to the intensity ValueFunction Purpose: Sets the intensity function which will be used when rendering this airbrush. The function controls the intensity (opacity) of the airbrush at Position values where Position 0.0 represents the airbrush centreline, and 1.0 represents the maximum width. SeeAlso: PathProcessorStrokeAirbrush::GetIntensityFunction ********************************************************************************************/ ValueFunction *PathProcessorStrokeAirbrush::GetIntensityFunction(void) { if (pIntensityFunction == NULL) pIntensityFunction = new ValueFunctionRampS(1.0, 0.0); return(pIntensityFunction); } /******************************************************************************************** > void PathProcessorStrokeAirbrush::SetIntensityFunction(ValueFunction *pFunc) Author: Jason_Williams (Xara Group Ltd) Created: 18/2/97 Inputs: pFunc - The new ValueFunction to use A value of NULL will set the airbrush to the default setting (using an ValueFunctionRampS from 1.0 to 0.0) Purpose: Sets the intensity function which will be used when rendering this airbrush. The function controls the intensity (opacity) of the airbrush at Position values where Position 0.0 represents the airbrush centreline, and 1.0 represents the maximum width. SeeAlso: PathProcessorStrokeAirbrush::GetIntensityFunction ********************************************************************************************/ void PathProcessorStrokeAirbrush::SetIntensityFunction(ValueFunction *pFunc) { if (pIntensityFunction != NULL) delete pIntensityFunction; pIntensityFunction = pFunc; }