Example #1
0
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
}