Re-merge fixes for bug 1362, bug 1666, bug 1863, KhronosRI and bld.inf.
// Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// Unless stated otherwise, all co-ordinates are recorded and calculated using
// Symbian graphics format: origin at top left of image, 'y' increasing towards bottom
// of image. Note that OpenVG places the origin at an image's bottom left corner, with
// 'y' increasing towards the top of the image.
//
//
#include "vgengine.h"
#include "directgdiadapter.h"
#include "directgdidriverimpl.h"
#include "directgdiimagetargetimpl.h"
#include "directgdiimagesourceimpl.h"
#include "confighelper.h"
#include <graphics/directgdiimagetarget.h>
#include <graphics/directgdidrawablesource.h>
#include <fbs.h>
#include <e32cmn.h>
#include <e32math.h>
#include <s32strm.h>
#include <pixelformats.h>
#include <bitdraworigin.h>
#include <bitdrawinterfaceid.h>
#include "blendingalgorithms.inl"
/**
Pre-calculation for normalising a 0-255 colour channel to 0..1.
*/
const VGfloat KColorConversion = 1.0f/255.0f;
inline TInt Scale(TInt aValue,TInt aNum,TInt aDen)
{
if (aNum==aDen)
return aValue;
TInt64 result(aValue);
result=(result*aNum+(aDen/2))/aDen;
return I64INT(result);
}
/**
Maps TDisplayMode onto a supported VGImageFormat. Only compatible formats are returned,
anything else results in VG_IMAGE_FORMAT_INVALID being returned.
@param aDisplayMode Mode to convert from.
@return The VGImageFormat which matches the specified TDisplayMode pixel format exactly.
If no exact match exists, then VG_IMAGE_FORMAT_INVALID is returned.
*/
static VGImageFormat MapToVgDisplayMode(TDisplayMode aDisplayMode)
{
switch(aDisplayMode)
{
case ENone:
return VG_IMAGE_FORMAT_INVALID;
case EGray2:
return VG_BW_1;
case EGray4:
case EGray16:
return VG_IMAGE_FORMAT_INVALID;
case EGray256:
return VG_sL_8;
case EColor16:
case EColor256:
case EColor4K:
return VG_IMAGE_FORMAT_INVALID;
case EColor64K:
return VG_sRGB_565;
case EColor16M:
case ERgb:
return VG_IMAGE_FORMAT_INVALID;
case EColor16MU:
return VG_sXRGB_8888;
case EColor16MA:
return VG_sARGB_8888;
case EColor16MAP:
return VG_sARGB_8888_PRE;
case EColorLast:
return VG_IMAGE_FORMAT_INVALID;
}
return VG_IMAGE_FORMAT_INVALID;
}
/**
Destructor
*/
CVgEngine::~CVgEngine()
{
iRegionManager.Close();
if (iBrushPatternUser != VG_INVALID_HANDLE)
vgDestroyImage(iBrushPatternUser);
if (iBrushPatternStandard != VG_INVALID_HANDLE)
vgDestroyImage(iBrushPatternStandard);
if (iBrushPatternStandardRegion != VG_INVALID_HANDLE)
vgDestroyImage(iBrushPatternStandardRegion);
if (iBrushPatternNonZeroOrigin != VG_INVALID_HANDLE)
vgDestroyImage(iBrushPatternNonZeroOrigin);
User::Free(iPathCommands);
User::Free(iPathCoords);
Deactivate();
}
/**
Constructor
*/
CVgEngine::CVgEngine(CDirectGdiDriverImpl& aDriver)
:iDriver(aDriver),
iPen(VG_INVALID_HANDLE),
iBrush(VG_INVALID_HANDLE),
iClearBrush(VG_INVALID_HANDLE),
iVgPath(VG_INVALID_HANDLE),
iTextBrush(VG_INVALID_HANDLE),
iBrushPatternUser(VG_INVALID_HANDLE),
iBrushPatternStandard(VG_INVALID_HANDLE),
iBrushPatternStandardRegion(VG_INVALID_HANDLE),
iBrushPatternNonZeroOrigin(VG_INVALID_HANDLE)
{
// Set the default paint mode for drawing and filling shapes to
// just draw the pen and not the brush as default (to match BitGdi)
iPaintMode = VG_STROKE_PATH;
//cache interface to use every time glyph gets drawn
GetInterface(TUid::Uid(KDirectGdiGetGlyphStorageUid), (TAny*&) iFontGlyphImageStorage);
}
/**
Applies the engine's state to OpenVG, re-applying all the VgEngine settings. It is called by
Activate() as well as when the engine is being made current.
*/
void CVgEngine::SetVgState()
{
ResetVgMatrix();
vgSeti(VG_RENDERING_QUALITY, VG_RENDERING_QUALITY_NONANTIALIASED);
vgSeti(VG_IMAGE_QUALITY, VG_IMAGE_QUALITY_NONANTIALIASED);
vgSeti(VG_STROKE_CAP_STYLE, VG_CAP_ROUND);
vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_ROUND);
vgSetPaint(iPen, VG_STROKE_PATH);
vgSetPaint(iBrush, VG_FILL_PATH);
SetDrawMode(iDrawMode);
SetPenColor(iPenColor);
SetPenStyle(iPenStyle);
SetPenSize(iPenSize);
SetBrushColor(iBrushColor);
SetBrushStyle(iBrushStyle);
SetBrushOrigin(iBrushOrigin);
vgSetParameteri(iClearBrush, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
vgSetParameteri(iTextBrush, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
}
/**
Binds a rendering target to this OpenVG rendering engine.
Activates the target, which increments the reference count of target as it can be shared across many
DirectGDI contexts.
The rendering target, for this implementation, specifies an Embedded OpenVG rendering surface.
@see MDirectGdiEngine::Activate()
@see Deactivate()
@panic DGDIAdapter 34, if the passed target has a NULL handle.
@panic DGDIAdapter 39, if target associated with the handle is NULL.
*/
TInt CVgEngine::Activate(RDirectGdiImageTarget& aTarget)
{
TInt result = KErrNone;
GRAPHICS_ASSERT_ALWAYS(aTarget.Handle(), EDirectGdiPanicActivateWithNullHandle);
CDirectGdiImageTargetImpl* target = iDriver.GetImageTargetFromHandle(aTarget.Handle());
GRAPHICS_ASSERT_ALWAYS(target, EDirectGdiPanicNullTargetActivate);
if (target == iRenderingTarget)
{
return KErrNone;
}
Deactivate();
iDriver.Activate(target);
iRenderingTarget = target;
iDriver.SetCurrentEngine(this);
iDriver.SetCurrentTarget(target);
iSize = iRenderingTarget->Size();
iTargetRegion.Clear();
iTargetRegion.AddRect(iSize);
iRegionManager.Initialize(vgGeti(VG_MAX_SCISSOR_RECTS), iSize);
// Set the origin to top-left for path and image rendering. iIdentityMatrix is set so that we have a
// record of the "identity" transform. After modifying the transform for offsets, rotations, etc, etc
// just set to iIdentityMatrix to get back to "normal".
//
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
vgLoadIdentity();
vgScale(1, -1);
vgTranslate(0, -iSize.iHeight);
vgGetMatrix(iIdentityMatrix);
// Set the origin to top-left for image matrix.
vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
vgLoadMatrix(iIdentityMatrix);
// Create the path.
if (!PreparePath(iVgPath, 1) || (iVgPath == VG_INVALID_HANDLE))
result = KErrNoMemory;
// Create the pen object and set it as the object for stroking (OpenVG equivalent of a pen).
if (result == KErrNone)
{
iPen = vgCreatePaint();
if (iPen != VG_INVALID_HANDLE)
vgSetPaint(iPen, VG_STROKE_PATH);
else
result = KErrNoMemory;
}
if (result == KErrNone)
{
// Create the brush object
// This only need to be done the first time the context is activated
iBrush = vgCreatePaint();
if (iBrush != VG_INVALID_HANDLE)
vgSetPaint(iBrush, VG_FILL_PATH);
else
result = KErrNoMemory;
}
if (result == KErrNone)
{
// Create the brush object for Clear operations.
// This only need to be done the first time the context is activated
iClearBrush = vgCreatePaint();
if (iClearBrush == VG_INVALID_HANDLE)
{
result = KErrNoMemory;
}
}
if (result == KErrNone)
{
iTextBrush = vgCreatePaint();
if (iTextBrush == VG_INVALID_HANDLE)
{
result = KErrNoMemory;
}
}
if (result == KErrNone)
{
result = iDriver.PreAllocateFontGlyphImages();
}
if (result == KErrNone)
{
SetVgState();
}
return result;
}
/**
@see MDirectGdiEngine::Deactivate()
@see Activate()
*/
void CVgEngine::Deactivate()
{
if (iPen != VG_INVALID_HANDLE)
{
vgDestroyPaint(iPen);
}
if (iBrush != VG_INVALID_HANDLE)
{
vgDestroyPaint(iBrush);
}
if (iClearBrush != VG_INVALID_HANDLE)
{
vgDestroyPaint(iClearBrush);
}
if (iTextBrush != VG_INVALID_HANDLE)
{
vgDestroyPaint(iTextBrush);
}
if (iVgPath != VG_INVALID_HANDLE)
{
vgDestroyPath(iVgPath);
}
iPen = VG_INVALID_HANDLE;
iBrush = VG_INVALID_HANDLE;
iClearBrush = VG_INVALID_HANDLE;
iVgPath = VG_INVALID_HANDLE;
iTextBrush = VG_INVALID_HANDLE;
if (iRenderingTarget)
{
// Deactivating the render target could potentially unbind the current EGL context
// which would make the above vgDestroy() calls do nothing, therefore call Deactivate() last.
iDriver.Deactivate(iRenderingTarget);
}
}
/**
Checks whether the engine and target are current. If current, then nothing is done, else all the OpenVG settings
and EGL context are reapplied. This function is called in every drawing function to ensure that the engine and
target are current.
If this function returns EFalse, it means any subsequent setting of OpenVG state may be invalid
and should be avoided as it is setting a null EGL context.
@pre None.
@post Applies the current state to OpenVG and is made the active EGL context, if the engine or target is
not current.
@return ETrue if as a result of calling this function, the underlying OpenVG context is now current. This
effectively means whether we have a target or not. EFalse is returned otherwise.
*/
TBool CVgEngine::MakeEngineCurrent()
{
TBool vgCurrent = iRenderingTarget!=NULL;
if(!iDriver.IsCurrentEngine(this) || !iDriver.IsCurrentTarget(iRenderingTarget))
{
iDriver.SetCurrentEngine(this);
iDriver.SetCurrentTarget(iRenderingTarget);
// Must reactivate the target (i.e. make it current to EGL) before resetting the OpenVG parameters.
if (iRenderingTarget)
{
iDriver.Reactivate(iRenderingTarget);
SetVgState();
}
else
vgCurrent = EFalse;
}
return vgCurrent;
}
/**
@see MDirectGdiEngine::SetOrigin()
*/
void CVgEngine::SetOrigin(const TPoint& aOrigin)
{
iOrigin = aOrigin + iDrawOrigin;
if (!MakeEngineCurrent())
return;
ResetVgMatrix();
}
/**
@see MDrawDeviceOrigin::Set()
*/
TInt CVgEngine::Set(const TPoint& aDrawOrigin)
{
TPoint moveOrigin=aDrawOrigin;
moveOrigin-=iDrawOrigin;
iOrigin+=moveOrigin;
//offset clipping region
TInt result = KErrNone;
RRegion clippingRegion;
clippingRegion.Copy(iRegionManager.ClippingRegion());
if(!clippingRegion.CheckError())
{
clippingRegion.Offset(moveOrigin);
result = iRegionManager.SetClippingRegion(clippingRegion);
}
else
{
result = KErrNoMemory;
}
if(result != KErrNone)
{
iDriver.SetError(result);
}
clippingRegion.Close();
iDrawOrigin = aDrawOrigin;
return result;
}
/**
@see MDrawDeviceOrigin::Get()
*/
void CVgEngine::Get(TPoint& aDrawOrigin)
{
aDrawOrigin=iDrawOrigin;
}
/**
@see MDirectGdiEngine::SetClippingRegion(const TRegion&)
*/
void CVgEngine::SetClippingRegion(const TRegion& aRegion)
{
TInt result = KErrNone;
TRect boundingRect=iTargetRegion.BoundingRect();
boundingRect.iTl-=iDrawOrigin;
boundingRect.iBr-=iDrawOrigin;
if (!aRegion.IsContainedBy(boundingRect))
{
result = KErrArgument;
}
else
{
RRegion clippingRegion;
clippingRegion.Copy(aRegion);
if(!clippingRegion.CheckError())
{
clippingRegion.Offset(iDrawOrigin);
result = iRegionManager.SetClippingRegion (clippingRegion);
}
else
{
result = KErrNoMemory;
}
clippingRegion.Close();
}
if (result != KErrNone)
{
iDriver.SetError(result);
}
}
/**
@see MDirectGdiEngine::ResetClippingRegion()
*/
void CVgEngine::ResetClippingRegion()
{
iRegionManager.Reset();
if (!MakeEngineCurrent())
return;
vgSeti(VG_SCISSORING, VG_FALSE);
}
/**
@see MDirectGdiEngine::SetDrawMode()
The generic layer has already checked whether the draw mode is already aMode.
Draw mode is referred to as "blend" mode in OpenVG.
Note that only EDrawModePEN and EDrawModeWriteAlpha style blending are supported by OpenVG.
The default OpenVG blend mode is VG_BLEND_SRC_OVER.
*/
void CVgEngine::SetDrawMode(DirectGdi::TDrawMode aMode)
{
iDrawMode = aMode;
if (!MakeEngineCurrent())
return;
// Invalid modes are filtered out in the generic layer.
if (aMode == DirectGdi::EDrawModePEN)
{
// Blend the destination with the source using the source alpha for blending if
// alpha is available.
vgSeti(VG_BLEND_MODE, VG_BLEND_SRC_OVER);
}
else // DirectGdi::EDrawModeWriteAlpha
{
// Destination colors and alpha are overwritten with source colors and alpha.
vgSeti(VG_BLEND_MODE, VG_BLEND_SRC);
}
}
/**
@see MDirectGdiEngine::SetPenColor()
*/
void CVgEngine::SetPenColor(const TRgb& aColor)
{
iPenColor = aColor;
if (!MakeEngineCurrent())
return;
// Make sure our pen is the current selected pen for stroking before we set the color
vgSetPaint(iPen, VG_STROKE_PATH);
// Set the color
vgSetParameteri(iPen, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
SetVgPaintColor(iPen, iPenColor);
SetVgPaintColor(iTextBrush, iPenColor);
}
/**
Set the current pen style for drawing lines. This corresponds to setting the "stroke dash" pattern in OpenVG.
@see MDirectGdiEngine::SetPenStyle()
*/
void CVgEngine::SetPenStyle(DirectGdi::TPenStyle aStyle)
{
iPenStyle = aStyle;
if (!MakeEngineCurrent())
return;
iPaintMode = iPaintMode | VG_STROKE_PATH;
switch (aStyle)
{
case DirectGdi::ENullPen:
{
iPaintMode = iPaintMode & (~VG_STROKE_PATH); // Unset stroke bit
vgSetfv(VG_STROKE_DASH_PATTERN, 0, NULL);
vgSetf(VG_STROKE_DASH_PHASE, 0.f);
break;
}
case DirectGdi::ESolidPen:
{
vgSetfv(VG_STROKE_DASH_PATTERN, 0, NULL);
vgSetf(VG_STROKE_DASH_PHASE, 0.f);
break;
}
case DirectGdi::EDottedPen:
{
VGfloat offset = (iPenSize.iWidth > 1) ? 2.f : 0.f;
VGfloat dashPattern[2] = {(1*iPenSize.iWidth)-offset, (3*iPenSize.iWidth)+offset};
vgSetfv(VG_STROKE_DASH_PATTERN, 2, dashPattern);
vgSetf(VG_STROKE_DASH_PHASE, 1.f);
break;
}
case DirectGdi::EDashedPen:
{
VGfloat offset = (iPenSize.iWidth > 1) ? 2.f : 0.f;
VGfloat dashPattern[2] = {(3*iPenSize.iWidth)-offset, (3*iPenSize.iWidth)+offset};
vgSetfv(VG_STROKE_DASH_PATTERN, 2, dashPattern);
vgSetf(VG_STROKE_DASH_PHASE, (iPenSize.iWidth != 1) ? 1.f : 0.f);
break;
}
case DirectGdi::EDotDashPen:
{
VGfloat offset = (iPenSize.iWidth > 1) ? 2.f : 0.f;
VGfloat dashPattern[4] = {(1*iPenSize.iWidth)-offset, (3*iPenSize.iWidth)+offset, (3*iPenSize.iWidth)-offset, (3*iPenSize.iWidth)+offset};
vgSetfv(VG_STROKE_DASH_PATTERN, 4, dashPattern);
vgSetf(VG_STROKE_DASH_PHASE, 1.f);
break;
}
case DirectGdi::EDotDotDashPen:
{
VGfloat offset = (iPenSize.iWidth > 1) ? 2.f : 0.f;
VGfloat dashPattern[6] = {(1*iPenSize.iWidth)-offset, (3*iPenSize.iWidth)+offset, (1*iPenSize.iWidth)-offset, (3*iPenSize.iWidth)+offset, (3*iPenSize.iWidth)-offset, (3*iPenSize.iWidth)+offset};
vgSetfv(VG_STROKE_DASH_PATTERN, 6, dashPattern);
vgSetf(VG_STROKE_DASH_PHASE, (iPenSize.iWidth != 1) ? 1.f : 0.f);
break;
}
default:
{
// Copy BitGdi behaviour here and draw a solid line for any unknown pen style
vgSetfv(VG_STROKE_DASH_PATTERN, 0, NULL);
vgSetf(VG_STROKE_DASH_PHASE, 0.f);
break;
}
}
}
/**
@see MDirectGdiEngine::SetPenSize()
*/
void CVgEngine::SetPenSize(const TSize& aSize)
{
iPenSize = aSize;
if (!MakeEngineCurrent())
return;
// VG_STROKE_LINE_WIDTH expects a float
// Note that we set the pen size using just the width in the assumption that the pen width
// and height are normally the same. For the special cases where the pen width and height
// are different, the pen size is set to (1,1) then scaled to give the effect of a pen with
// different width and height. This is done for all functions in this file that draws shapes,
// see Plot(), DrawLine(), DrawArc(), DrawRect() etc.
vgSetf(VG_STROKE_LINE_WIDTH, aSize.iWidth);
// Re-set the pen style as the pen size has changed, SetPenStyle() uses the pen size to set
// the dotted line styles.
SetPenStyle(iPenStyle);
}
/**
@see MDirectGdiEngine::SetTextShadowColor()
*/
void CVgEngine::SetTextShadowColor(const TRgb& aColor)
{
iTextShadowColor = aColor; //just cache this color
}
/**
@see MDirectGdiEngine::SetBrushColor()
*/
void CVgEngine::SetBrushColor(const TRgb& aColor)
{
iBrushColor = aColor;
if (!MakeEngineCurrent())
return;
// Convert the color components as that they are between 0.0f and 1.0f
VGfloat color[4];
color[0] = aColor.Red() * KColorConversion;
color[1] = aColor.Green() * KColorConversion;
color[2] = aColor.Blue() * KColorConversion;
color[3] = aColor.Alpha() * KColorConversion;
// Make sure our brush is the current selected brush for filling before we set the color
if (iBrushStyle != DirectGdi::ENullBrush)
vgSetPaint(iBrush, VG_FILL_PATH);
vgSetParameteri(iBrush, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
vgSetParameterfv(iBrush, VG_PAINT_COLOR, 4, color);
// Set the clear color and the tile fill color as well as these should both
// be the same as the brush color
vgSetParameterfv(iClearBrush, VG_PAINT_COLOR, 4, color);
vgSetfv(VG_CLEAR_COLOR, 4, color);
vgSetfv(VG_TILE_FILL_COLOR, 4, color);
// If using a patterned brush, need to reconstruct it as the colours may be out of sync.
if ((iBrushStyle != DirectGdi::ENullBrush) && (iBrushStyle != DirectGdi::ESolidBrush))
{
SetBrushStyle(iBrushStyle);
}
}
/**
The DirectGDI brush styles do not map directly to OpenVG, so brushes for styles > DirectGdi::EPatternedBrush
are created as bitmaps before being set.
@see MDirectGdiEngine::SetBrushColor()
@see CreateStandardBrush()
*/
void CVgEngine::SetBrushStyle(DirectGdi::TBrushStyle aStyle)
{
iBrushStyle = aStyle;
if (!MakeEngineCurrent())
return;
TInt standardBrushErr = KErrNone;
const TInt standardBrushSize = 3;
const TInt standardBrushArea = standardBrushSize*standardBrushSize;
const TInt diamondCrossHatchBrushSize = 4;
const TInt diamondCrossHatchBrushArea = diamondCrossHatchBrushSize*diamondCrossHatchBrushSize;
// Select the brush for drawing any style that is not ENullBrush
iPaintMode = iPaintMode | VG_FILL_PATH;
if (aStyle != DirectGdi::ENullBrush)
vgSetPaint(iBrush, VG_FILL_PATH);
// Paint using a pattern for all styles that are not ENullBrush or ESolidBrush
if ((aStyle != DirectGdi::ENullBrush) && (aStyle != DirectGdi::ESolidBrush))
{
vgSetParameteri(iBrush, VG_PAINT_TYPE, VG_PAINT_TYPE_PATTERN);
vgSetParameteri(iBrush, VG_PAINT_PATTERN_TILING_MODE, VG_TILE_REPEAT);
}
switch (aStyle)
{
case DirectGdi::ENullBrush:
iPaintMode = iPaintMode & (~VG_FILL_PATH);
// Clear the current brush so that no brush color is drawn
vgSetPaint(VG_INVALID_HANDLE, VG_FILL_PATH);
break;
case DirectGdi::ESolidBrush:
// Paint using a solid color
vgSetParameteri(iBrush, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
break;
case DirectGdi::EPatternedBrush:
// Make sure the user has set a patterned brush for use
GRAPHICS_ASSERT_ALWAYS(iBrushPatternUser != VG_INVALID_HANDLE, EDirectGdiPanicPatternedBrushNotSet);
if (NonZeroBrushPatternOrigin())
{
// The brush origin is non-zero, update the non-zero origin brush
// with the user brush and use that instead of the user brush
CopyCurrentBrushPatternForNonZeroOrigin();
}
else
{
// Set the brush to fill with the user selected bitmap pattern
vgPaintPattern(iBrush, iBrushPatternUser);
}
break;
case DirectGdi::EVerticalHatchBrush:
{
// The brush fills with vertical hatching lines going from top to bottom
TSize size(standardBrushSize, standardBrushSize);
VGbyte brushPattern[standardBrushArea] = {0,0,1, 0,0,1, 0,0,1};
standardBrushErr = CreateStandardBrush(size, brushPattern);
break;
}
case DirectGdi::EForwardDiagonalHatchBrush:
{
// The brush fills with diagonal hatching lines going from bottom left to top right
TSize size(standardBrushSize, standardBrushSize);
VGbyte brushPattern[standardBrushArea] = {1,0,0, 0,0,1, 0,1,0};
standardBrushErr = CreateStandardBrush(size, brushPattern);
break;
}
case DirectGdi::EHorizontalHatchBrush:
{
// The brush fills with horizontal hatching lines going from left to right
TSize size(standardBrushSize, standardBrushSize);
VGbyte brushPattern[standardBrushArea] = {0,0,0, 0,0,0, 1,1,1};
standardBrushErr = CreateStandardBrush(size, brushPattern);
break;
}
case DirectGdi::ERearwardDiagonalHatchBrush:
{
// The brush fills with rearward diagonal hatching lines going from top left to bottom right
TSize size(standardBrushSize, standardBrushSize);
VGbyte brushPattern[standardBrushArea] = {1,0,0, 0,1,0, 0,0,1};
standardBrushErr = CreateStandardBrush(size, brushPattern);
break;
}
case DirectGdi::ESquareCrossHatchBrush:
{
// The brush fills with horizontal and vertical hatching lines going from left to right
// plus lines going from top to bottom giving the effect of a grid of small squares
TSize size(standardBrushSize, standardBrushSize);
VGbyte brushPattern[standardBrushArea] = {0,0,1, 0,0,1, 1,1,1};
standardBrushErr = CreateStandardBrush(size, brushPattern);
break;
}
case DirectGdi::EDiamondCrossHatchBrush:
{
// The brush fills with forward diagonal and rearward diagonal hatching lines going from
// bottom left to top right plus lines going from top left to bottom right giving the effect
// of a grid of small diamonds
// The brush fills with diagonal hatching lines going from bottom left to top right
TSize size(diamondCrossHatchBrushSize, diamondCrossHatchBrushSize);
VGbyte brushPattern[diamondCrossHatchBrushArea] = {0,0,1,0, 0,1,0,1, 1,0,0,0, 0,1,0,1};
standardBrushErr = CreateStandardBrush(size, brushPattern);
break;
}
}
// Select the standard brush for all styles > EPatternedBrush
if (aStyle > DirectGdi::EPatternedBrush)
{
if (standardBrushErr == KErrNone)
{
if (NonZeroBrushPatternOrigin())
{
// The brush origin is non-zero, update the non-zero origin brush
// with the standard brush and use that instead of the standard brush
CopyCurrentBrushPatternForNonZeroOrigin();
}
else
{
// Use the standard brush region
vgPaintPattern(iBrush, iBrushPatternStandardRegion);
}
}
else
{
iDriver.SetError(standardBrushErr);
}
}
else
{
vgSeti(VG_IMAGE_MODE, VG_DRAW_IMAGE_NORMAL);
}
}
/**
@see MDirectGdiEngine::SetBrushOrigin()
*/
void CVgEngine::SetBrushOrigin(const TPoint& aOrigin)
{
iBrushOrigin = aOrigin;
if (NonZeroBrushPatternOrigin())
{
// Copy the current brush pattern into iBrushPatternNonZeroOrigin, but shift it to
// take into account the current non-zero brush origin.
CopyCurrentBrushPatternForNonZeroOrigin();
}
}
/**
@see MDirectGdiEngine::SetBrushPattern()
*/
TInt CVgEngine::SetBrushPattern(const CFbsBitmap& aPattern)
{
if (aPattern.ExtendedBitmapType() != KNullUid)
{
return KErrNotSupported; // Not supported for extended bitmaps
}
// Destroy any previously set brush pattern
MakeEngineCurrent();
ResetBrushPattern();
iBrushPatternUser = CreateSourceVGImage(aPattern);
if (iBrushPatternUser == VG_INVALID_HANDLE)
{
return KErrNoMemory;
}
iBrushPatternUserSize = aPattern.SizeInPixels();
iBrushPatternUserBitmapHandle = aPattern.Handle();
return KErrNone;
}
/**
@see MDirectGdiEngine::ResetBrushPattern()
*/
void CVgEngine::ResetBrushPattern()
{
MakeEngineCurrent();
if (iBrushPatternUser != VG_INVALID_HANDLE)
{
vgDestroyImage(iBrushPatternUser);
iBrushPatternUser = VG_INVALID_HANDLE;
iBrushPatternUserBitmapHandle = KNullHandle;
iBrushPatternUserSize = TSize(0,0);
}
}
/**
@see MDirectGdiEngine::SetFont()
*/
void CVgEngine::SetFont(TUint32 aFontId)
{
iFontId = aFontId;
}
/**
@see MDirectGdiEngine::ResetFont()
*/
void CVgEngine::ResetFont()
{
iFontId = 0;
}
/**
Reset all the VgEngine-specific settings. Generic settings such as paint colour and pen colour
are set by calls from the CDirectGdiContext.
@see MDirectGdiEngine::Reset()
@post All VgEngine-specific settings have been reset to their default values.
*/
void CVgEngine::Reset()
{
if (!MakeEngineCurrent())
return;
ResetVgMatrix();
}
/**
@see MDirectGdiEngine::Clear(const TRect&)
@panic DGDIAdapter 62, if the brush for clearing is not valid (debug-only).
*/
void CVgEngine::Clear(const TRect& aRect)
{
MakeEngineCurrent();
if (255 == iBrushColor.Alpha())
{
const TPoint rectOrigin = ConvertToVgCoords(aRect);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgClear(rectOrigin.iX, rectOrigin.iY, aRect.Width(), aRect.Height());
}
else
{
// If blending is enabled, we cannot use vgClear as it ignores the current blendmode and performs a WriteAlpha.
// Therefore a clear is done by a filled rectangle.
// The Clear brush should always be a solid brush.
GRAPHICS_ASSERT_DEBUG(vgGetParameteri(iClearBrush, VG_PAINT_TYPE) == VG_PAINT_TYPE_COLOR, EDirectGdiPanicClearBrushInvalid);
if (PreparePath(iVgPath, 5))
{
// Before any vgu command, call SetError() as this stores the current OpenVG error state (if any)
// on the driver. Some implementations of vgu clears error state so we'd lose error codes otherwise.
iDriver.SetError(KErrNone);
VGUErrorCode err = vguRect(iVgPath, aRect.iTl.iX, aRect.iTl.iY, aRect.Width(), aRect.Height());
if (err == VGU_NO_ERROR)
{
// Temporarily set the brush to the clear brush and fill the path.
vgSetPaint(iClearBrush, VG_FILL_PATH);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, VG_FILL_PATH);
vgSetPaint(iBrush, VG_FILL_PATH);
}
else
{
SetVguError(err);
}
}
}
}
/**
@see MDirectGdiEngine::Clear()
*/
void CVgEngine::Clear()
{
Clear(TRect(iSize));
}
/**
@see MDirectGdiEngine::MoveTo()
*/
void CVgEngine::MoveTo(const TPoint& aPoint)
{
iLinePos = aPoint;
}
/**
@see MDirectGdiEngine::MoveBy()
*/
void CVgEngine::MoveBy(const TPoint& aVector)
{
iLinePos += aVector;
}
/**
@see MDirectGdiEngine::Plot()
*/
void CVgEngine::Plot(const TPoint& aPoint)
{
MakeEngineCurrent();
GRAPHICS_ASSERT_DEBUG(vgGeti(VG_STROKE_CAP_STYLE) == VG_CAP_ROUND, EDirectGdiPanicPenEndCapStyleNotRound);
// If the pen width and height are equal just plot as normal.
if (iPenSize.iWidth == iPenSize.iHeight)
{
if (PreparePath(iVgPath, 2))
{
// A point is plotted by drawing a line with start and end points the same.
AppendPathCommand(VG_MOVE_TO_ABS, aPoint.iX + 0.5f, aPoint.iY + 0.5f);
AppendPathCommand(VG_HLINE_TO_REL, 0.f);
FinishPath(iVgPath);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, iPaintMode&VG_STROKE_PATH);
}
}
else if ((iPenSize.iWidth != 0) && (iPenSize.iHeight != 0))
{
// Setting a pen with different width and height is not available on OpenVG, so we need to scale
// the coordinates we are drawing and apply a scaling matrix that scales by the width and height
// of the pen to get the effect of a pen width different width and height.
if (PreparePath(iVgPath, 2))
{
TSize penSize = iPenSize;
SetPenSize(TSize(1, 1));
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
vgScale(penSize.iWidth, penSize.iHeight);
// A point is plotted by drawing a line with start and end points the same.
AppendPathCommand(VG_MOVE_TO_ABS, (aPoint.iX + 0.5f)/(float)penSize.iWidth, (aPoint.iY + 0.5f)/(float)penSize.iHeight);
AppendPathCommand(VG_HLINE_TO_REL, 0.f);
FinishPath(iVgPath);
vgDrawPath(iVgPath, iPaintMode&VG_STROKE_PATH);
ResetVgMatrix(VG_MATRIX_PATH_USER_TO_SURFACE);
SetPenSize(penSize);
}
}
}
/**
@see MDirectGdiEngine::DrawLine()
*/
void CVgEngine::DrawLine(const TPoint& aStart, const TPoint& aEnd)
{
MakeEngineCurrent();
GRAPHICS_ASSERT_DEBUG(vgGeti(VG_STROKE_CAP_STYLE) == VG_CAP_ROUND, EDirectGdiPanicPenEndCapStyleNotRound);
if (iPaintMode == 0)
{
return;
}
if (PreparePath(iVgPath, 2))
{
// If the pen width and height are the same then draw as normal
if (iPenSize.iWidth == iPenSize.iHeight)
{
// 0.5 is appended to all OpenVG drawing co-ordinates as when specifying them, the spec says
// co-ordinates are relative to pixel boundaries, not pixel centres, so 0,0 is the top left of the
// top pixel. We need to add 0.5 to specify the centre of pixels.
AppendPathCommand(VG_MOVE_TO_ABS, aStart.iX + 0.5f, aStart.iY + 0.5f);
if (aStart.iX == aEnd.iX)
{
AppendPathCommand(VG_VLINE_TO_ABS, aEnd.iY + 0.5f);
}
else if (aStart.iY == aEnd.iY)
{
AppendPathCommand(VG_HLINE_TO_ABS, aEnd.iX + 0.5f);
}
else
{
AppendPathCommand(VG_LINE_TO_ABS, aEnd.iX + 0.5f, aEnd.iY + 0.5f);
}
FinishPath(iVgPath);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, iPaintMode);
}
else if ((iPenSize.iWidth != 0) && (iPenSize.iHeight != 0))
{
// Setting a pen with different width and height is not available on OpenVG, so we need to scale
// the coordinates we are drawing and apply a scaling matrix that scales by the width and height
// of the pen to get the effect of a pen width different width and height.
TSize penSize = iPenSize;
SetPenSize(TSize(1, 1));
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
vgScale(penSize.iWidth, penSize.iHeight);
VGfloat scaleX = 1.0f/(float)penSize.iWidth;
VGfloat scaleY = 1.0f/(float)penSize.iHeight;
AppendPathCommand(VG_MOVE_TO_ABS, (aStart.iX + 0.5f)*scaleX, (aStart.iY + 0.5f)*scaleY);
if (aStart.iX == aEnd.iX)
{
AppendPathCommand(VG_VLINE_TO_ABS, (aEnd.iY + 0.5f)*scaleY);
}
else if (aStart.iY == aEnd.iY)
{
AppendPathCommand(VG_HLINE_TO_ABS, (aEnd.iX + 0.5f)*scaleX);
}
else
{
AppendPathCommand(VG_LINE_TO_ABS, (aEnd.iX + 0.5f)*scaleX, (aEnd.iY + 0.5f)*scaleY);
}
FinishPath(iVgPath);
vgDrawPath(iVgPath, iPaintMode);
ResetVgMatrix(VG_MATRIX_PATH_USER_TO_SURFACE);
SetPenSize(penSize);
}
}
iLinePos = aEnd;
}
/**
@see MDirectGdiEngine::DrawLineTo()
*/
void CVgEngine::DrawLineTo(const TPoint& aPoint)
{
DrawLine(iLinePos, aPoint);
}
/**
@see MDirectGdiEngine::DrawLineBy()
*/
void CVgEngine::DrawLineBy(const TPoint& aVector)
{
DrawLine(iLinePos, iLinePos + aVector);
}
/**
@see MDirectGdiEngine::DrawRect()
*/
void CVgEngine::DrawRect(const TRect& aRect)
{
MakeEngineCurrent();
// Only draw if we are not painting with a NULL pen and a NULL brush
if (iPaintMode == 0)
return;
// If the pen size is larger then 1, make sure we are using the ESolidPen
// pen style (to match BitGdi behaviour)
DirectGdi::TPenStyle savedPenStyle = iPenStyle;
if (((iPenSize.iWidth > 1) || (iPenSize.iHeight > 1)) && (iPenStyle > DirectGdi::ESolidPen))
SetPenStyle(DirectGdi::ESolidPen);
// Create a copy of the rect. If the pen style is not null, we have to shrink the
// width and height of the rect by one pixel at the bottom left corner for conformance.
TRect copyRect(aRect.iTl.iX, aRect.iTl.iY, aRect.iBr.iX, aRect.iBr.iY-1);
if (iPenStyle != DirectGdi::ENullPen)
{
--copyRect.iBr.iX;
}
else
{
--copyRect.iTl.iY;
}
const TBool symmetricalPenSize = iPenSize.iWidth == iPenSize.iHeight;
// If the pen is so thick it covers the entire area of the rect, don't do the fill, as per BitGdi.
const TBool penThickerThanRect = (iPenSize.iWidth >= copyRect.Width()) || (iPenSize.iHeight >= copyRect.Height());
// If the pen width and height are the same then draw as normal. If they are different but we should be filling
// this shape we need to draw the filled area only as normal (not the outline). The outline of the shape is drawn
// in the block of code below to allow the effect of a different width and height pen to be applied.
if (!penThickerThanRect || (iPenStyle == DirectGdi::ENullPen))
{
if (symmetricalPenSize || (iPaintMode & VG_FILL_PATH))
{
if (PreparePath(iVgPath, 5))
{
vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_MITER);
AppendPathCommand(VG_MOVE_TO_ABS, copyRect.iTl.iX + 0.5f, copyRect.iTl.iY + 0.5f);
AppendPathCommand(VG_HLINE_TO_ABS, copyRect.iBr.iX + 0.5f);
AppendPathCommand(VG_VLINE_TO_ABS, copyRect.iBr.iY + 0.5f);
AppendPathCommand(VG_HLINE_TO_ABS, copyRect.iTl.iX + 0.5f);
AppendPathCommand(VG_CLOSE_PATH);
FinishPath(iVgPath);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, symmetricalPenSize ? iPaintMode : VG_FILL_PATH);
vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_ROUND);
}
}
}
if((penThickerThanRect && (iPenStyle != DirectGdi::ENullPen)) || //we shouldn't draw if pen style is null
(!symmetricalPenSize && (iPaintMode & VG_STROKE_PATH) && (iPenSize.iWidth != 0) && (iPenSize.iHeight != 0)))
{
// Setting a pen with different width and height is not available on OpenVG, so we need to scale
// the coordinates we are drawing and apply a scaling matrix that scales by the width and height
// of the pen to get the effect of a pen width different width and height.
TSize penSize = iPenSize;
SetPenSize(TSize(1, 1));
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
vgScale(penSize.iWidth, penSize.iHeight);
if (PreparePath(iVgPath, 5))
{
vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_MITER);
VGfloat scaleX = 1.0f/(float)penSize.iWidth;
VGfloat scaleY = 1.0f/(float)penSize.iHeight;
AppendPathCommand(VG_MOVE_TO_ABS, (copyRect.iTl.iX + 0.5f)*scaleX, (copyRect.iTl.iY + 0.5f)*scaleY);
AppendPathCommand(VG_HLINE_TO_ABS, (copyRect.iBr.iX + 0.5f)*scaleX);
AppendPathCommand(VG_VLINE_TO_ABS, (copyRect.iBr.iY + 0.5f)*scaleY);
AppendPathCommand(VG_HLINE_TO_ABS, (copyRect.iTl.iX + 0.5f)*scaleX);
AppendPathCommand(VG_CLOSE_PATH);
FinishPath(iVgPath);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, VG_STROKE_PATH);
vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_ROUND);
}
ResetVgMatrix(VG_MATRIX_PATH_USER_TO_SURFACE);
SetPenSize(penSize);
}
// Reset the pen style if we changed it above
if (savedPenStyle != iPenStyle)
SetPenStyle(savedPenStyle);
}
/**
@see MDirectGdiEngine::DrawRoundRect()
*/
void CVgEngine::DrawRoundRect(const TRect& aRect, const TSize& aCornerSize)
{
MakeEngineCurrent();
// Only draw if we are not painting with a NULL pen and a NULL brush
if (iPaintMode == 0)
return;
// Create a copy of the rect. If the pen style is not null, we have to shrink the
// width and height of the rect by one pixel at the bottom left corner for conformance.
TRect copyRect(aRect.iTl.iX, aRect.iTl.iY, aRect.iBr.iX, aRect.iBr.iY-1);
if (iPenStyle != DirectGdi::ENullPen)
{
--copyRect.iBr.iX;
}
//If the penstyle is null and brush style is not null, then reduce the width and height by
//two pixels for conformation.
else if(iBrushStyle != DirectGdi::ENullBrush)
{
----copyRect.iBr.iX;
--copyRect.iBr.iY;
}
else if(iPenStyle == DirectGdi::ENullPen)
{
--copyRect.iTl.iY;
}
// check that the corner size is less than the rectangle size
if ((aRect.Width() > aCornerSize.iWidth) || (aRect.Height() > aCornerSize.iHeight))
{
// Before any vgu command, call SetError() as this stores the current vg error state (if any) in the
// driver. Some implementations of vgu clears error state so we'd lose error codes otherwise.
iDriver.SetError(KErrNone);
// If the pen width and height are the same then draw as normal. If they are different but we should be filling
// this shape we need to draw the filled area only as normal (not the outline). The outline of the shape is drawn
// in the block of code below to allow the effect of a different width and height pen to be applied.
if ((iPenSize.iWidth == iPenSize.iHeight) || (iPaintMode & VG_FILL_PATH))
{
if (PreparePath(iVgPath, 10))
{
VGUErrorCode err = vguRoundRect(iVgPath,
copyRect.iTl.iX + 0.5f,
copyRect.iTl.iY + 0.5f,
copyRect.Width(),
copyRect.Height(),
aCornerSize.iWidth * 2,
aCornerSize.iHeight * 2);
if (err == VGU_NO_ERROR)
{
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, (iPenSize.iWidth == iPenSize.iHeight) ? iPaintMode : VG_FILL_PATH);
}
else
{
SetVguError(err);
}
}
}
if ((iPenSize.iWidth != iPenSize.iHeight)
&& (iPaintMode & VG_STROKE_PATH)
&& (iPenSize.iWidth != 0) && (iPenSize.iHeight != 0))
{
// Setting a pen with different width and height is not available on OpenVG, so we need to scale
// the coordinates we are drawing and apply a scaling matrix that scales by the width and height
// of the pen to get the effect of a pen width different width and height.
TSize penSize = iPenSize;
SetPenSize(TSize(1, 1));
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
vgScale(penSize.iWidth, penSize.iHeight);
if (PreparePath(iVgPath, 10))
{
VGfloat scaleX = 1.0f/(float)penSize.iWidth;
VGfloat scaleY = 1.0f/(float)penSize.iHeight;
VGUErrorCode err = vguRoundRect(iVgPath,
(copyRect.iTl.iX + 0.5f) * scaleX,
(copyRect.iTl.iY + 0.5f) * scaleY,
copyRect.Width() * scaleX,
copyRect.Height() * scaleY,
(aCornerSize.iWidth * 2) * scaleX,
(aCornerSize.iHeight * 2) * scaleY);
if (err == VGU_NO_ERROR)
{
vgDrawPath(iVgPath, VG_STROKE_PATH);
}
else
{
SetVguError(err);
}
}
ResetVgMatrix(VG_MATRIX_PATH_USER_TO_SURFACE);
SetPenSize(penSize);
}
}
else
{
// Draw an ellipse as the corner size is greater than or equal to the rectangle size
DrawEllipse(copyRect);
}
}
/**
@see MDirectGdiEngine::DrawPolyLine()
@panic DGDIAdapter 27, if the passed point list has zero points (debug-only).
*/
void CVgEngine::DrawPolyLine(const TArray<TPoint>& aPointList)
{
DrawPolyLineNoEndPoint(aPointList);
// Do a plot at the end point to improve the appearance. For larger pen-sizes, a plot
// improves the correlation with BitGDI polylines, giving a subtly more rounded finish.
if (DirectGdi::ESolidPen == iPenStyle)
{
Plot(iLinePos);
}
}
/**
@see MDirectGdiEngine::DrawPolyLineNoEndPoint()
@panic DGDIAdapter 27, if the passed point list has zero points (debug-only).
*/
void CVgEngine::DrawPolyLineNoEndPoint(const TArray<TPoint>& aPointList)
{
MakeEngineCurrent();
GRAPHICS_ASSERT_DEBUG(aPointList.Count() > 0, EDirectGdiPanicInvalidPointArray);
DoDrawPoly(aPointList, EFalse);
}
/**
@see MDirectGdiEngine::DrawPolygon()
@panic DGDIAdapter 26, if the passed fill-rule is invalid (debug-only).
*/
void CVgEngine::DrawPolygon(const TArray<TPoint>& aPoints, DirectGdi::TFillRule aRule)
{
MakeEngineCurrent();
GRAPHICS_ASSERT_DEBUG(aPoints.Count() > 0, EDirectGdiPanicInvalidPointArray);
switch(aRule)
{
case DirectGdi::EAlternate:
vgSeti(VG_FILL_RULE, VG_EVEN_ODD);
break;
case DirectGdi::EWinding:
vgSeti(VG_FILL_RULE, VG_NON_ZERO);
break;
default:
GRAPHICS_PANIC_DEBUG(EDirectGdiPanicInvalidFillRule);
return;
};
DoDrawPoly(aPoints, ETrue);
}
/**
Helper function to assist with drawing polygons with DrawPolygon()/DrawPolyLine(). It takes care of
drawing the array of points given to it. It sets the internal drawing poisition to the last TPoint
in the array.
@see DrawPolyLine()
@see DrawPolygon()
@param aPoints Array of points specifying the vertices of the polygon. There must be at least one
vertex.
@param aClosed If ETrue, the start and end points are joined, and the polygon filled using current
brush settings, otherwise just a polyline is drawn.
*/
void CVgEngine::DoDrawPoly(const TArray<TPoint>& aPoints, TBool aClosed)
{
GRAPHICS_ASSERT_DEBUG(aPoints.Count() > 0, EDirectGdiPanicInvalidPointArray);
const TInt numPoints = aPoints.Count();
// Set drawing position to last point in the array (regardless of whether the poly is open/closed)
iLinePos = aPoints[numPoints - 1];
// Set the paint mode depending on whether we are drawing a line (not closed) or a poly (closed)
VGbitfield paintMode = iPaintMode;
if (!aClosed)
{
paintMode &= ~VG_FILL_PATH;
}
if (!paintMode)
{
return;
}
// If the pen width and height are the same then draw as normal. If they are different but we should be filling
// this shape we need to draw the filled area only as normal (not the outline). The outline of the shape is drawn
// in the block of code below to allow the effect of a different width and height pen to be applied.
if ((iPenSize.iWidth == iPenSize.iHeight) || (paintMode & VG_FILL_PATH))
{
if (PreparePath(iVgPath, aClosed ? numPoints + 1 : numPoints))
{
AppendPathCommand(VG_MOVE_TO_ABS, aPoints[0].iX + 0.5f, aPoints[0].iY + 0.5f);
for (TInt point = 0; point < numPoints; ++point)
{
AppendPathCommand(VG_LINE_TO_ABS, aPoints[point].iX + 0.5f, aPoints[point].iY + 0.5f);
}
if (aClosed)
{
AppendPathCommand(VG_CLOSE_PATH);
}
FinishPath(iVgPath);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, (iPenSize.iWidth == iPenSize.iHeight) ? paintMode : VG_FILL_PATH);
}
}
if ((iPenSize.iWidth != iPenSize.iHeight)
&& (paintMode & VG_STROKE_PATH)
&& (iPenSize.iWidth != 0) && (iPenSize.iHeight != 0))
{
// Setting a pen with different width and height is not available on OpenVG, so we need to scale
// the coordinates we are drawing and apply a scaling matrix that scales by the width and height
// of the pen to get the effect of a pen width different width and height.
TSize penSize = iPenSize;
SetPenSize(TSize(1, 1));
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
vgScale(penSize.iWidth, penSize.iHeight);
if (PreparePath(iVgPath, aClosed ? numPoints + 1 : numPoints))
{
AppendPathCommand(VG_MOVE_TO_ABS, (aPoints[0].iX + 0.5f)/(float)penSize.iWidth, (aPoints[0].iY + 0.5f)/(float)penSize.iHeight);
for (TInt point = 0; point < numPoints; ++point)
{
AppendPathCommand(VG_LINE_TO_ABS, (aPoints[point].iX + 0.5f)/(float)penSize.iWidth, (aPoints[point].iY + 0.5f)/(float)penSize.iHeight);
}
if (aClosed)
{
AppendPathCommand(VG_CLOSE_PATH);
}
FinishPath(iVgPath);
vgDrawPath(iVgPath, VG_STROKE_PATH);
}
ResetVgMatrix(VG_MATRIX_PATH_USER_TO_SURFACE);
SetPenSize(penSize);
}
}
/**
@see MDirectGdiEngine::DrawArc()
@see DrawPie()
@see DrawArc(const TRect&, const TPoint&, const TPoint&, VGUArcType)
*/
void CVgEngine::DrawArc(const TRect& aRect, const TPoint& aStart, const TPoint& aEnd)
{
DoDrawArc(aRect, aStart, aEnd, VGU_ARC_OPEN);
}
/**
@see MDirectGdiEngine::DrawPie()
@see DrawArc(const TRect&, const TPoint&, const TPoint&)
@see DrawArc(const TRect&, const TPoint&, const TPoint&, VGUArcType)
*/
void CVgEngine::DrawPie(const TRect& aRect, const TPoint& aStart, const TPoint& aEnd)
{
DoDrawArc(aRect, aStart, aEnd, VGU_ARC_PIE);
}
/**
@see MDirectGdiEngine::DrawEllipse()
*/
void CVgEngine::DrawEllipse(const TRect& aRect)
{
// Null brush and pen, draw nothing.
if (iPaintMode == 0)
return;
MakeEngineCurrent();
VGfloat x = (aRect.iTl.iX + aRect.iBr.iX) * 0.5;
VGfloat y = (aRect.iTl.iY + aRect.iBr.iY) * 0.5;
// Before any vgu command, call SetError() as this stores the current vg error state (if any) in the
// driver. Some implementations of vgu clears error state so we'd lose error codes otherwise.
iDriver.SetError(KErrNone);
TInt width = aRect.Width();
TInt height = aRect.Height();
//If the penstyle is null and brush style is not null, then reduce the width and height by
//two pixels for conformation.
if(iPenStyle == DirectGdi::ENullPen && iBrushStyle != DirectGdi::ENullBrush)
{
width = aRect.Width() > 2 ? aRect.Width() - 2 : 1;
height = aRect.Height() > 2 ? aRect.Height() - 2 : 1;
}
// If the pen width and height are the same then draw as normal. If they are different but we should be filling
// this shape we need to draw the filled area only as normal (not the outline). The outline of the shape is drawn
// in the block of code below to allow the effect of a different width and height pen to be applied.
if ((iPenSize.iWidth == iPenSize.iHeight) || (iPaintMode & VG_FILL_PATH))
{
if (PreparePath(iVgPath, 4))
{
VGUErrorCode err = vguEllipse(iVgPath, x, y, width, height);
if (err == VGU_NO_ERROR)
{
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, (iPenSize.iWidth == iPenSize.iHeight) ? iPaintMode : VG_FILL_PATH);
}
else
{
SetVguError(err);
}
}
}
if ((iPenSize.iWidth != iPenSize.iHeight)
&& (iPaintMode & VG_STROKE_PATH)
&& (iPenSize.iWidth != 0) && (iPenSize.iHeight != 0))
{
// Setting a pen with different width and height is not available on OpenVG, so we need to scale
// the coordinates we are drawing and apply a scaling matrix that scales by the width and height
// of the pen to get the effect of a pen width different width and height.
TSize penSize = iPenSize;
SetPenSize(TSize(1, 1));
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
vgScale(penSize.iWidth, penSize.iHeight);
VGfloat scaleX = 1.0f/(float)penSize.iWidth;
VGfloat scaleY = 1.0f/(float)penSize.iHeight;
if (PreparePath(iVgPath, 4))
{
VGUErrorCode err = vguEllipse(iVgPath, x*scaleX, y*scaleY, (float)width*scaleX, (float)height*scaleY);
if (err == VGU_NO_ERROR)
{
vgDrawPath(iVgPath, VG_STROKE_PATH);
}
else
{
SetVguError(err);
}
}
ResetVgMatrix(VG_MATRIX_PATH_USER_TO_SURFACE);
SetPenSize(penSize);
}
}
/**
Given a TDisplayMode, returns the closest TDisplayMode that is pixel-for-pixel-compatible
with an OpenVG format, such that the given TDisplayMode may be converted into the result
without loss of colour information.
@param aDisplayMode Pixel format to find a match for.
@return Closest TDisplayMode for which there is a OpenVG-compatible match.
*/
static TDisplayMode ClosestVgCompatibleDisplayMode(TDisplayMode aDisplayMode)
{
switch (aDisplayMode)
{
case EGray2:
case EGray4:
case EGray16:
return EGray256;
case EColor16:
case EColor256:
case EColor4K:
return EColor64K;
case EColor16M:
return EColor16MU;
default:
return aDisplayMode;
}
}
/**
Converts a CFbsBitmap into a VGImage.
If the CFbsBitmap is not a volatile bitmap, the VGImage created will be stored in the thread-wide
VGImage cache. If the CFbsBitmap has been touched (i.e. its data has been changed since it was last
used), a new VGImage will be created and will replace that currently stored in the cache. An untouched
bitmap will store the created VGImage in the cache upon first use, and on subsequent use (if it is
still untouched), will just retrieve the VGImage stored in the cache.
@param aBitmap The CFbsBitmap to create a VGImage from.
@param aImage Returns the VGImage created from the CFbsBitmap.
@param aIsMask True if the CFbsBitmap is to be used as a mask.
@param aOrigin Position of the first pixel in the mask bitmap.
@return ETrue if the VGimage has been stored in the cache, EFalse if not.
*/
TBool CVgEngine::ConvertBitmapToVgImage(const CFbsBitmap& aBitmap, VGImage& aImage, TBool aIsMask, const TPoint& aOrigin)
{
TBool createImage = EFalse;
TBool storeImageInCache = EFalse;
TBool imageCached = EFalse;
// Set the options createImage, storeImageInCache and imageCached depending on
// whether the bitmap is volatile, has been touched since last used,
// and whether it already exists in the cache
if (aBitmap.IsVolatile())
{
// Source bitmap is volatile and so should not be stored in cache.
// Therefore create image only.
createImage = ETrue;
}
else //bitmap not volatile
{
// Source bitmap has not changed since last used.
// Retrieve from cache the image created from the bitmap and the touchCount of that image.
aImage = iDriver.GetVgImageFromCache(aBitmap, aOrigin);
// If the source bitmap already has an associated VGImage stored in the cache,
// just use that VGImage. Otherwise, need to create a VGImage and add it to the cache.
if (aImage == VG_INVALID_HANDLE)
{
// VGImage not in cache
// Create image, and store in cache
createImage = ETrue;
storeImageInCache = ETrue;
}
else
{
// Image already in cache
imageCached = ETrue;
}
}
// Create a new VGImage if needed
if (createImage)
{
aImage = CreateSourceVGImage(aBitmap, aIsMask, aOrigin);
// Error set on creation of VGImage if aImage == VG_INVALID_HANDLE.
}
// Store the VGImage in the cache if appropriate
if (storeImageInCache && aImage != VG_INVALID_HANDLE)
{
imageCached = iDriver.AddVgImageToCache(aBitmap, aImage, aOrigin);
}
return imageCached;
}
/**
Transforms coordinates for a TRect from Symbian Graphics to OpenVG surface coordinates.
This is required for OpenVG operations which are not subject to user-to-surface
coordinate system transformations.
OpenVG coordinates locate the BOTTOM LEFT corner of the object relative to an origin
at the BOTTOM LEFT of the rendering area.
Symbian Graphics coordinates locate the TOP LEFT corner of the object relative to an
origin located at the TOP LEFT of the rendering area.
@param aCoord Top-left of the rectangle, in Symbian graphics coordinates.
@param aWidth The width of the desired rectangle.
@param aHeight The height of the desired rectangle.
@return A TRect in OpenVG coordinates which describes a rectangle at aCoord with aWidth and aHeight.
*/
TRect CVgEngine::SgMetricsToVgTRect(const TPoint& aCoord, const TInt aWidth, const TInt aHeight) const
{
return TRect (TPoint (aCoord.iX + iOrigin.iX, iSize.iHeight - (aCoord.iY + aHeight) - iOrigin.iY), TSize (aWidth, aHeight));
}
/**
@see MDirectGdiEngine::BitBlt()
@see BitBltMasked(const TPoint&, const CFbsBitmap&, const TRect&, const CFbsBitmap&, TBool)
@see BitBltMasked(const TPoint&, const CFbsBitmap&, const TRect&, const CFbsBitmap&, const TPoint&)
*/
void CVgEngine::BitBlt(const TPoint& aDestPos, const CFbsBitmap& aSourceBitmap, const TRect& aSourceRect)
{
if (aSourceBitmap.ExtendedBitmapType() != KNullUid)
{
iDriver.SetError(KErrNotSupported); // Not supported for extended bitmaps
return;
}
DoVgImageDraw (TRect (aDestPos, aSourceRect.Size()), aSourceBitmap, aSourceRect);
}
/**
@see MDirectGdiEngine::DrawBitmap()
@see DrawBitmapMasked()
*/
void CVgEngine::DrawBitmap(const TRect& aDestRect, const CFbsBitmap& aSourceBitmap, const TRect& aSourceRect)
{
if (aSourceBitmap.ExtendedBitmapType() != KNullUid)
{
iDriver.SetError(KErrNotSupported); // Not supported for extended bitmaps
return;
}
DoVgImageDraw (aDestRect, aSourceBitmap, aSourceRect);
}
/**
Helper method to perform basic VgDrawImage operations, explictly optimised for the case where
the extents of the source image equal the specified source region.
@pre aSource image is a valid VG image handle.
@pre Destination position and/or scaling has already been set in OpenVG.
@param aDestRect Destination rectangle to draw to.
@param aSourceBitmap Source bitmap to draw.
@param aSourceRect Source rectangle to render.
*/
void CVgEngine::DoVgImageDraw (const TRect& aDestRect, const CFbsBitmap& aSourceBitmap, const TRect& aSourceRect)
{
MakeEngineCurrent();
TRect destRect(aDestRect);
TRect srcRect(aSourceRect);
if (!IntersectsClippingRegion (TRect(iOrigin, destRect.Size())))
return;
VGImage sourceImage = VG_INVALID_HANDLE;
TBool imageCached = ConvertBitmapToVgImage(aSourceBitmap, sourceImage);
// Error set on creation of VGImage.
if (sourceImage == VG_INVALID_HANDLE) return;
vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
vgTranslate(destRect.iTl.iX, destRect.iTl.iY);
if (aDestRect.Size() != aSourceRect.Size())
vgScale((VGfloat)destRect.Width()/aSourceRect.Width(), (VGfloat)destRect.Height()/aSourceRect.Height());
if(aSourceBitmap.SizeInPixels() == aSourceRect.Size())
{
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(sourceImage);
}
else
{
VGImage sourceImageRegion =
vgChildImage(
sourceImage,
srcRect.iTl.iX,
srcRect.iTl.iY,
srcRect.Width(),
srcRect.Height());
if (sourceImageRegion != VG_INVALID_HANDLE)
{
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(sourceImageRegion);
vgDestroyImage(sourceImageRegion);
}
}
if (!imageCached) vgDestroyImage (sourceImage);
ResetVgMatrix(VG_MATRIX_IMAGE_USER_TO_SURFACE); // Reset the transform matrix.
}
/**
@see MDirectGdiEngine::BitBltMasked()
@see BitBltMasked(const TPoint&, const CFbsBitmap&, const TRect&, const CFbsBitmap&, TBool)
@see BitBlt()
*/
void CVgEngine::BitBltMasked(
const TPoint& aDestPos,
const CFbsBitmap& aBitmap,
const TRect& aSourceRect,
const CFbsBitmap& aMask,
const TPoint& aMaskPt)
{
if ((aBitmap.ExtendedBitmapType() != KNullUid) || (aMask.ExtendedBitmapType() != KNullUid))
{
iDriver.SetError(KErrNotSupported); // Not supported for extended bitmaps
return;
}
DoBitBltMasked(aDestPos, aBitmap, aSourceRect, aMask, EFalse, aMaskPt);
}
/**
@see MDirectGdiEngine::BitBlt()
@see BitBltMasked(const TPoint&, const CFbsBitmap&, const TRect&, const CFbsBitmap&, const TPoint&)
@see BitBlt()
*/
void CVgEngine::BitBltMasked(
const TPoint& aDestPos,
const CFbsBitmap& aSourceBitmap,
const TRect& aSourceRect,
const CFbsBitmap& aMaskBitmap,
TBool aInvertMask)
{
if ((aSourceBitmap.ExtendedBitmapType() != KNullUid) || (aMaskBitmap.ExtendedBitmapType() != KNullUid))
{
iDriver.SetError(KErrNotSupported); // Not supported for extended bitmaps
return;
}
DoBitBltMasked(aDestPos, aSourceBitmap, aSourceRect, aMaskBitmap, aInvertMask, TPoint(0, 0));
}
/**
Helper method for performing BitBltMasked().
Note that aInvertMask is ignored if aMaskPos is not at 0,0.
@see CDirectGdiContext::BitBlt(const TPoint& aPoint, const CFbsBitmap& aBitmap, const TRect& aSourceRect);
@see CDirectGdiContext::BitBltMasked(const TPoint&, const CFbsBitmap&,const TRect&, const CFbsBitmap&, const TPoint&);
@param aDestPos The destination for the top left corner of the transferred bitmap.
It is relative to the top left corner of the destination bitmap, which may be the screen.
@param aSourceBitmap A memory-resident source bitmap.
@param aSourceRect A rectangle defining the piece of the bitmap to be drawn,
with co-ordinates relative to the top left corner of the bitmap.
@param aMaskBitmap Mask bitmap.
@param aInvertMask If EFalse, a source pixel that is masked by a black pixel is not transferred to
the destination rectangle. If ETrue, then a source pixel that is masked by a
white pixel is not transferred to the destination rectangle. If alpha blending
is used instead of masking, this flag is ignored and no inversion takes place.
Note that this parameter is ignored if aMaskPos does not equal TPoint(0,0).
@param aMaskPos The point on the mask bitmap to use as the top left corner
*/
void CVgEngine::DoBitBltMasked (
const TPoint& aDestPos,
const CFbsBitmap& aSourceBitmap,
const TRect& aSourceRect,
const CFbsBitmap& aMaskBitmap,
TBool aInvertMask,
const TPoint& aMaskPos)
{
MakeEngineCurrent();
ResetVgMatrix(VG_MATRIX_IMAGE_USER_TO_SURFACE);
if (!IntersectsClippingRegion (TRect (aDestPos+iOrigin, aSourceRect.Size())))
return;
VGImage sourceImage = VG_INVALID_HANDLE;
TBool imageCached = ConvertBitmapToVgImage(aSourceBitmap, sourceImage);
if (sourceImage == VG_INVALID_HANDLE)
{
// Error set on creation of VGImage.
return;
}
VGImage maskImage = VG_INVALID_HANDLE;
TBool maskImageCached = ConvertBitmapToVgImage(aMaskBitmap, maskImage, ETrue, aMaskPos);
if (maskImage == VG_INVALID_HANDLE)
{
// Error set on creation of VGImage.
if (!imageCached)
{
vgDestroyImage(sourceImage);
}
return;
}
DoVgMaskedImageDraw(
aDestPos,
sourceImage,
aSourceBitmap.SizeInPixels(),
aSourceRect,
maskImage,
aMaskBitmap.SizeInPixels(),
aSourceRect.Size(),
aInvertMask);
if (!maskImageCached)
{
vgDestroyImage(maskImage);
}
if (!imageCached)
{
vgDestroyImage(sourceImage);
}
}
/**
This implementation stretches the mask first, and then performs mask tiling. Another approach is to
tile first and then perform stretching. The latter method requires more memory and stretches
once. Results between these methods are different. When stretching first, all tiles will be completely
uniform. When stretching last, different tiles are affected differently, based on the tile's position
and stretch factor.
@see MDirectGdiEngine::DrawBitmapMasked()
@see DrawBitmap()
*/
void CVgEngine::DrawBitmapMasked(
const TRect& aDestRect,
const CFbsBitmap& aSourceBitmap,
const TRect& aSourceRect,
const CFbsBitmap& aMaskBitmap,
TBool aInvertMask)
{
if ((aSourceBitmap.ExtendedBitmapType() != KNullUid) || (aMaskBitmap.ExtendedBitmapType() != KNullUid))
{
iDriver.SetError(KErrNotSupported); // Not supported for extended bitmaps
return;
}
MakeEngineCurrent();
TRect destRect(aDestRect);
if (!IntersectsClippingRegion (TRect(iOrigin, destRect.Size())))
{
return;
}
// Create source image
VGImage sourceImage = VG_INVALID_HANDLE;
TBool imageCached = ConvertBitmapToVgImage(aSourceBitmap, sourceImage);
// Return if VGImage failed to be created (error set on creation of VGImage).
if (sourceImage == VG_INVALID_HANDLE)
{
return;
}
// Convert aMask to a VGImage.
VGImage maskImage = VG_INVALID_HANDLE;
TBool maskImageCached = ConvertBitmapToVgImage(aMaskBitmap, maskImage, ETrue);
// Error set on creation of VGImage if mask == VG_INVALID_HANDLE
if (maskImage != VG_INVALID_HANDLE)
{
TSize destSize = destRect.Size();
TSize sourceSize = aSourceRect.Size();
if ((destSize.iWidth == sourceSize.iWidth) && (destSize.iHeight == sourceSize.iHeight))
{
// No scaling of masked bitmap involved
DoVgMaskedImageDraw(
destRect.iTl,
sourceImage,
aSourceBitmap.SizeInPixels(),
aSourceRect,
maskImage,
aMaskBitmap.SizeInPixels(),
destSize,
aInvertMask);
}
else
{
// Unfortunately, the current implementation of VG does not support
// mask scaling. So, we render the mask into a VGImage pbuffer surface,
// and apply user to surface scaling to get a stretch. The stretched
// mask is then used for rendering.
// Generate the VGImage to act as a pbuffer surface and receive the stretched mask.
const TSize maskSizeInPixels = aMaskBitmap.SizeInPixels();
const VGImageFormat vgFormat = MapToVgDisplayMode(ClosestVgCompatibleDisplayMode(aMaskBitmap.DisplayMode()));
TInt scaledMaskWidth = Scale(maskSizeInPixels.iWidth,destSize.iWidth,sourceSize.iWidth);
TInt scaledMaskHeight = Scale(maskSizeInPixels.iHeight,destSize.iHeight,sourceSize.iHeight);
VGImage stretchedMask = DoVgCreateImage(vgFormat,
scaledMaskWidth,
scaledMaskHeight,
VG_IMAGE_QUALITY_NONANTIALIASED);
if (stretchedMask != VG_INVALID_HANDLE)
{
// Get a configuration handle that is compatible with the mask pixel format.
EGLConfig utilConfig = 0;
TInt err = TConfigHelper::GetConfigForFbsBitmap (aMaskBitmap, utilConfig);
if (err == KErrNone)
{
TBool eglSuccess = EFalse;
EGLDisplay eglDisplay = iDriver.EglDisplay();
EGLSurface lastSurface = eglGetCurrentSurface(EGL_DRAW);
// Create a Pbuffer surface from the stretched mask VGImage.
EGLSurface utilSurface = eglCreatePbufferFromClientBuffer(
eglDisplay,
EGL_OPENVG_IMAGE,
static_cast<EGLClientBuffer>(stretchedMask),
utilConfig,
NULL);
if (utilSurface != EGL_NO_SURFACE)
{
EGLContext lastContext = eglGetCurrentContext();
// Create config. compatible context.
EGLContext utilContext = eglCreateContext(eglDisplay, utilConfig, lastContext, NULL);
if (utilContext != EGL_NO_CONTEXT)
{
// Make the utility surface and context current, and stretch the mask.
if (eglMakeCurrent(eglDisplay, utilSurface, utilSurface, utilContext) == EGL_TRUE)
{
// Set up the scaling transform for the current surface.
vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
// Scaling factors for x and y.
VGfloat xScale = ((VGfloat)destSize.iWidth/sourceSize.iWidth);
VGfloat yScale = ((VGfloat)destSize.iHeight/sourceSize.iHeight);
vgScale(xScale, yScale);
// Render the stretched mask.
vgDrawImage(maskImage);
ResetVgMatrix(VG_MATRIX_IMAGE_USER_TO_SURFACE);
// All done, make current the pre-existing rendering state.
eglMakeCurrent(eglDisplay, lastSurface, lastSurface, lastContext);
eglSuccess = ETrue;
}
eglDestroyContext(eglDisplay, utilContext);
}
eglDestroySurface(eglDisplay, utilSurface);
}
if (eglSuccess)
{
DoVgMaskedImageDraw(destRect.iTl, sourceImage, aSourceBitmap.SizeInPixels(),
aSourceRect, stretchedMask, maskSizeInPixels, destSize, aInvertMask);
}
else
{
// coverity[check_return]
// coverity[unchecked_value]
LogEglError();
}
}
else
{
iDriver.SetError(err);
}
vgDestroyImage (stretchedMask);
}
if (!maskImageCached) vgDestroyImage (maskImage);
}
}
if (!imageCached) vgDestroyImage(sourceImage);
}
/**
Helper method that implements the core blitting functionality.
@param aDestPos The destination for the top left corner of the transferred bitmap.
It is relative to the top left corner of the destination bitmap, which may be the screen.
@param aSourceImage A valid VGImage to draw.
@param aSourceImageSize Extents of source bitmap.
@param aSourceRect A rectangle defining the piece of the bitmap to be drawn,
with co-ordinates relative to the top left corner of the bitmap.
@param aScaledMaskImage A valid mask VGImage - pre-scaled, ready for rendering.
@param aSrcMaskSize The size of the (unscaled) mask image.
@param aXscale Scaling factor to apply to x axis (already applied to mask).
@param aYscale Scaling factor to apply to y axis (already applied to mask).
@param aDestSize The size of the destination (used for calculating any scaling)
@param aInvertMask If EFalse, a source pixel that is masked by a black pixel
is not transferred to the destination rectangle. If ETrue, then a source
pixel that is masked by a white pixel is not transferred to the destination
rectangle.
@pre The rendering target has been activated. aBitmap and aMaskBitmap are not NULL and hold Handles.
Destination rectangle extents must intersect clipping region.
aSourceImage is a valid VGImage handle.
@post Request to draw the masked bitmap content has been accepted.
There is no guarantee that the request has been processed when the method returns.
*/
void CVgEngine::DoVgMaskedImageDraw(
const TPoint& aDestPos,
VGImage aSourceImage,
const TRect& aSourceImageSize,
const TRect& aSourceRect,
const VGImage aScaledMaskImage,
const TSize& aSrcMaskSize,
const TSize& aDestSize,
TBool aInvertMask)
{
TBool destroySourceImageAtEnd = EFalse;
if(aSourceImageSize != aSourceRect)
{
aSourceImage =
vgChildImage(
aSourceImage,
aSourceRect.iTl.iX,
aSourceRect.iTl.iY,
aSourceRect.Width(),
aSourceRect.Height());
if (aSourceImage == VG_INVALID_HANDLE) return;
destroySourceImageAtEnd = ETrue;
}
TBool maskOK = EFalse;
TSize sourceSize = aSourceRect.Size();
TRect destRect(aDestPos, TSize(Scale(aSourceRect.Width(),aDestSize.iWidth,sourceSize.iWidth),
Scale(aSourceRect.Height(),aDestSize.iHeight,sourceSize.iHeight)));
// VG does not provide mask tiling...we currently perform multiple
// vgMask operations to implement tiling. It should be possible to use
// pattern tiling to render the mask to a surface, convert surface region
// to VGImage and apply that as a mask (to take advantage of native VG
// tiling), though cost/benefit is has not yet been determined.
// NOTE: It may be worth optimising for cases where xScale or yScale equal one.
TRect destVgRect = SgMetricsToVgTRect(aDestPos, destRect.Width(), destRect.Height());
if (aScaledMaskImage != VG_INVALID_HANDLE)
{
const TSize scaledMaskSize(Scale(aSrcMaskSize.iWidth,aDestSize.iWidth,sourceSize.iWidth),
Scale(aSrcMaskSize.iHeight,aDestSize.iHeight,sourceSize.iHeight));
if (scaledMaskSize.iHeight > 0 && scaledMaskSize.iWidth > 0)
{
maskOK = ETrue;
// Determine mask image offset for rendering.
TInt scaledMaskXOffset = Scale(aSourceRect.iTl.iX%aSrcMaskSize.iWidth,aDestSize.iWidth,sourceSize.iWidth);
// Sg coordinates are relative to top left, Vg are reletive to bottom left. As we
// tile from the bottom up we subtract from maskSize.iHeight to get the bottom edge
// offset.
TInt scaledMaskYOffset = (aSourceRect.iTl.iY + aSourceRect.Height()) % aSrcMaskSize.iHeight;
if (scaledMaskYOffset != 0)
{
scaledMaskYOffset = Scale(aSrcMaskSize.iHeight-scaledMaskYOffset,aDestSize.iHeight,sourceSize.iHeight);
}
// If inverting the mask, we use a difference operation against the existing mask, so
// we need to ensure the existing mask is set to the correct state.
// Fill the existing mask so that it is completly transparent (set to all ones).
if(aInvertMask)
{
vgMask(
VG_INVALID_HANDLE,
VG_FILL_MASK,
destVgRect.iTl.iX,
destVgRect.iTl.iY,
destVgRect.Width(),
destVgRect.Height());
}
VGMaskOperation vgMaskOp = aInvertMask ? VG_SUBTRACT_MASK : VG_SET_MASK;
// NOTE: in VG destVgRect.iTl is physically at the bottom and destVgRect.iBr at the top
for (
TInt maskY = destVgRect.iTl.iY - scaledMaskYOffset;
maskY < destVgRect.iBr.iY;
maskY += scaledMaskSize.iHeight)
{
for (
TInt maskX = destVgRect.iTl.iX - scaledMaskXOffset;
maskX < destVgRect.iBr.iX;
maskX += scaledMaskSize.iWidth)
{
vgMask(
aScaledMaskImage,
vgMaskOp,
maskX,
maskY,
scaledMaskSize.iWidth,
scaledMaskSize.iHeight);
}
}
}
}
// Set up translation and scale for the current surface - note that translation must
// occur first, as is unscaled.
vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
vgTranslate(aDestPos.iX, aDestPos.iY);
VGfloat xScale = ((VGfloat)aDestSize.iWidth/sourceSize.iWidth);
VGfloat yScale = ((VGfloat)aDestSize.iHeight/sourceSize.iHeight);
vgScale(xScale, yScale);
// Rather than bracketing vgDrawImage with VG_MASKING on/off we may want to always enable masking,
// and remove the mask when finished:
// vgMask(VG_INVALID_HANDLE, VG_CLEAR_MASK, 0, 0, iRenderingTarget->Size().iWidth, iRenderingTarget->Size().iHeight);
// If the mask is not removed in some way, then subsequent rendering operations which intersect with the
// masking region will be affected.
if (maskOK)
{
vgSeti (VG_MASKING, VG_TRUE);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(aSourceImage);
vgSeti (VG_MASKING, VG_FALSE);
}
else
{
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(aSourceImage);
}
// Reset the transform matrix.
ResetVgMatrix(VG_MATRIX_IMAGE_USER_TO_SURFACE);
if (destroySourceImageAtEnd)
{
vgDestroyImage (aSourceImage);
}
}
/**
@see MDirectGdiEngine::DrawResource(const TPoint&, const RDirectGdiDrawableSource&, DirectGdi::TGraphicsRotation)
@see DrawResource(const TRect&, const RDirectGdiDrawableSource&, const TRect&, DirectGdi::TGraphicsRotation)
@see DrawResource(const TRect&, const RDirectGdiDrawableSource&, const TDesC8&)
*/
void CVgEngine::DrawResource(const TPoint& aPos, const RDirectGdiDrawableSource& aSource, DirectGdi::TGraphicsRotation aRotation)
{
MakeEngineCurrent();
CDirectGdiImageSourceImpl* source = iDriver.GetImageSourceFromHandle(aSource.Handle());
if (source)
{
const TSize sourceSize = source->Size();
if ((sourceSize.iWidth > 0) && (sourceSize.iHeight > 0))
{
VGImage vgImage = source->VgImage();
if (vgImage != VG_INVALID_HANDLE)
{
vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
TPoint pos(aPos);
if (aRotation == DirectGdi::EGraphicsRotationNone ||
aRotation == DirectGdi::EGraphicsRotation180)
{
// Pixel-data in EGLImages appears to be upside down due to the Y-inversion
// effect of the Identity matrix. Therefore must undo the Y-inversion here
// and adjust destination rect accordingly.
pos.iY = iSize.iHeight - aPos.iY - sourceSize.iHeight - iOrigin.iY;
pos.iX += iOrigin.iX;
vgLoadIdentity();
}
else
{
// But if rotation is 90 or 270 degrees we need to mirror in the X-axis
// and adjust destination translation accordingly.
vgScale(-1, 1);
}
switch (aRotation)
{
case DirectGdi::EGraphicsRotation90:
vgTranslate(-pos.iX, pos.iY);
vgRotate(90.0f);
break;
case DirectGdi::EGraphicsRotation180:
vgTranslate(pos.iX+sourceSize.iWidth, pos.iY+sourceSize.iHeight);
vgRotate(180.0f);
break;
case DirectGdi::EGraphicsRotation270:
vgTranslate(-pos.iX-sourceSize.iHeight, pos.iY+sourceSize.iWidth);
vgRotate(270.0f);
break;
default:
// No rotation
vgTranslate(pos.iX, pos.iY);
break;
}
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(vgImage);
ResetVgMatrix(VG_MATRIX_IMAGE_USER_TO_SURFACE);
}
}
}
}
/**
@see MDirectGdiEngine::DrawResource(const TRect&, const RDirectGdiDrawableSource&, DirectGdi::TGraphicsRotation)
*/
void CVgEngine::DrawResource(const TRect& aDestRect,
const RDirectGdiDrawableSource& aSource,
DirectGdi::TGraphicsRotation aRotation)
{
CDirectGdiImageSourceImpl* source = iDriver.GetImageSourceFromHandle(aSource.Handle());
if (source)
{
TRect srcRect(TPoint(0, 0), source->Size());
DoDrawResource(aDestRect, source, srcRect, aRotation);
}
}
/**
@see MDirectGdiEngine::DrawResource(const TRect&, const RDirectGdiDrawableSource&, const TRect&, DirectGdi::TGraphicsRotation)
*/
void CVgEngine::DrawResource(const TRect& aDestRect,
const RDirectGdiDrawableSource& aSource,
const TRect& aSourceRect,
DirectGdi::TGraphicsRotation aRotation)
{
CDirectGdiImageSourceImpl* source = iDriver.GetImageSourceFromHandle(aSource.Handle());
if (source)
{
// check source rectangle is fully contained within the image resource extent before drawing
TSize size = source->Size();
if ((aSourceRect.iTl.iX < 0)
|| (aSourceRect.iTl.iY < 0)
|| (aSourceRect.iBr.iX > size.iWidth)
|| (aSourceRect.iBr.iY > size.iHeight)
|| (aSourceRect.Width() <= 0)
|| (aSourceRect.Height() <= 0))
{
iDriver.SetError(KErrArgument);
return;
}
if (((aSourceRect.Width() == aDestRect.Width())
&& (aSourceRect.Height() == aDestRect.Height())
&& (aRotation == DirectGdi::EGraphicsRotationNone || aRotation == DirectGdi::EGraphicsRotation180))
||
((aSourceRect.Width() == aDestRect.Height())
&& (aSourceRect.Height() == aDestRect.Width())
&& (aRotation == DirectGdi::EGraphicsRotation90 || aRotation == DirectGdi::EGraphicsRotation270)))
{
// No scaling
DrawResource(TPoint(aDestRect.iTl.iX, aDestRect.iTl.iY), aSource, aRotation);
}
else
{
MakeEngineCurrent();
DoDrawResource(aDestRect, source, aSourceRect, aRotation);
}
}
}
/**
This method only supports drawing of image sources as Drawables. An attempt to draw
a Drawable that is not an image will result in a panic.
@see MDirectGdiEngine::DrawResource(const TRect&, const RDirectGdiDrawableSource&, const TDesC8&)
@panic DGDIAdapter 1, if an attempt is made to draw a drawable that is not an image source.
*/
void CVgEngine::DrawResource(
const TRect& aDestRect,
const RDirectGdiDrawableSource& aSource,
const TDesC8& /*aParam*/)
{
MakeEngineCurrent();
// Check to see if the passed drawable is actually an image as we only support drawing of images at present
if (iDriver.IsImageSource(aSource.Handle()))
{
CDirectGdiImageSourceImpl* source = iDriver.GetImageSourceFromHandle(aSource.Handle());
if (source)
{
DoDrawResource(aDestRect, source, DirectGdi::EGraphicsRotationNone);
}
}
else
{
// This method only supports drawing image sources at present
GRAPHICS_ASSERT_ALWAYS(0, EDirectGdiPanicNotImplemented);
}
}
/**
@see MDirectGdiEngine::BeginDrawGlyph()
Sets the necessary OpenVG and engine state ready for receiving DrawGlyph() commands.
Any OpenVG state that is common for all DrawGlyph() operations are set.
*/
void CVgEngine::BeginDrawGlyph()
{
MakeEngineCurrent();
vgSetPaint(iTextBrush, VG_FILL_PATH);
vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
#ifdef DRAWGLYPH_MULTIPLY_MODE
vgSeti(VG_IMAGE_MODE, VG_DRAW_IMAGE_MULTIPLY);
#else
vgSeti(VG_IMAGE_MODE, VG_DRAW_IMAGE_STENCIL);
#endif
#ifdef DRAWGLYPH_BUFFERED
iDrawGlyphCount = 0;
#endif
}
/**
Draws Glyph image to the given position and orientation.
The function crops the output image to the clipping rectangle specified as a parameter.
If the clipping region is defined on the current context, it will also be taken into consideration
@param aScrPos Position to start drawing glyph after rotation has been applied (if necessary).
@param aChar Character being drawn. Signifies general Unicode character value.
@param aGlyphImage Pointer to the glyph image data.
@param aGlyphBitmapType Type of bitmap format.
@param aGlyphImageSize Glyph image size.
@param aScrClipRect Clipping rect.
@param aRotation Rotation specifying how the glyph will be drawn. The number can only be divisible by 90 degrees,
i.e. horizontal and vertical rotation.
@see CFont::TTextDirection
@see MDirectGdiEngine::DrawGlyph()
@pre The rendering target has been activated.
@post Request to draw the Glyph has been accepted.
@panic DGDIAdapter 8, if aClipRect is empty (debug-only).
@panic DGDIAdapter 61, if font glyph image storage does not exist (debug-only).
*/
void CVgEngine::DrawGlyph(
const TPoint& aScrPos,
const TChar aChar,
const TUint8* aGlyphImage,
const TGlyphBitmapType aGlyphBitmapType,
const TSize& aGlyphImageSize,
const TRect& aScrClipRect,
const DirectGdi::TGraphicsRotation aRotation)
{
GRAPHICS_ASSERT_DEBUG(!aScrClipRect.IsEmpty(), EDirectGdiPanicInvalidRegion);
GRAPHICS_ASSERT_DEBUG(iFontGlyphImageStorage, EDirectGdiPanicGlyphImageStorageNotCreated);
TPoint pos = aScrPos;
pos += iDrawOrigin;
TRect clipRect = aScrClipRect;
clipRect.iTl += iDrawOrigin;
clipRect.iBr += iDrawOrigin;
if( aGlyphImageSize.iHeight <= 0 || aGlyphImageSize.iWidth <= 0 ||
!clipRect.Intersects(iTargetRegion.BoundingRect()) || !iRegionManager.Intersects(clipRect))
{
// Just leave silently, as spaces could be passed with empty size.
return;
}
// Clip the glyph against the target and the clipping rects.
// Calculate the axis-aligned bounding box of the glyph.
TRect glyphBoundingBox;
switch(aRotation)
{
case DirectGdi::EGraphicsRotation90:
glyphBoundingBox = TRect(TPoint(1+pos.iX-aGlyphImageSize.iHeight, pos.iY), TSize(aGlyphImageSize.iHeight, aGlyphImageSize.iWidth));
break;
case DirectGdi::EGraphicsRotation180:
glyphBoundingBox = TRect(TPoint(pos.iX-aGlyphImageSize.iHeight, pos.iY-aGlyphImageSize.iWidth), aGlyphImageSize);
break;
case DirectGdi::EGraphicsRotation270:
glyphBoundingBox = TRect(TPoint(pos.iX, 1+pos.iY-aGlyphImageSize.iWidth), TSize(aGlyphImageSize.iHeight, aGlyphImageSize.iWidth));
break;
default:
glyphBoundingBox = TRect(pos, aGlyphImageSize);
break;
}
if (!glyphBoundingBox.Intersects(iTargetRegion.BoundingRect()) || !iRegionManager.Intersects(glyphBoundingBox))
{
return;
}
#ifdef DRAWGLYPH_BUFFERED
iDrawGlyphCommand[iDrawGlyphCount].pos = pos;
iDrawGlyphCommand[iDrawGlyphCount].aChar = aChar;
iDrawGlyphCommand[iDrawGlyphCount].aGlyphBitmapType = aGlyphBitmapType;
iDrawGlyphCommand[iDrawGlyphCount].aGlyphImageSize = aGlyphImageSize;
iDrawGlyphCommand[iDrawGlyphCount].aClipRect = clipRect;
iDrawGlyphCommand[iDrawGlyphCount].aRotation = aRotation;
iDrawGlyphCommand[iDrawGlyphCount].aGlyphImage = const_cast<TUint8*>(aGlyphImage);
++iDrawGlyphCount;
if (iDrawGlyphCount == KMaxGlyphs)
{
FlushDrawGlyphs();
iDrawGlyphCount = 0;
}
#else
VGImage foreground = VG_INVALID_HANDLE;
VGImage outline = VG_INVALID_HANDLE;
VGImage shadow = VG_INVALID_HANDLE;
TInt err = iFontGlyphImageStorage -> GlyphImage(iFontId, aChar, aGlyphBitmapType, aGlyphImage, aGlyphImageSize, &foreground, &shadow, &outline);
if(err != KErrNone)
{
iDriver.SetError(err);
if(err == KErrNoMemory)
{
if((foreground == VG_INVALID_HANDLE) ||
((aGlyphBitmapType == EFourColourBlendGlyphBitmap) && ((shadow == VG_INVALID_HANDLE) || (outline == VG_INVALID_HANDLE))))
{
return;
}
}
else
{
return;
}
}
RRegion oldClippingRegion;
oldClippingRegion.Copy(iRegionManager.ClippingRegion());
iRegionManager.ClipTo(clipRect);
// Load the matrix which converts Symbian coordinate system to OpenVG coordinate system
vgLoadMatrix(Identity());
if(aRotation == DirectGdi::EGraphicsRotation90)
{
vgTranslate(pos.iX+1, pos.iY);
vgRotate(90.0f);
}
else if(aRotation == DirectGdi::EGraphicsRotation270)
{
vgTranslate(pos.iX, pos.iY+1);
vgRotate(270.0f);
}
else
{
vgTranslate(pos.iX, pos.iY);
}
switch(aGlyphBitmapType)
{
case EMonochromeGlyphBitmap:
case EAntiAliasedGlyphBitmap:
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(foreground);
break;
case EFourColourBlendGlyphBitmap:
{
SetVgPaintColor(iTextBrush, iBrushColor);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(foreground);
SetVgPaintColor(iTextBrush, iTextShadowColor);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(shadow);
SetVgPaintColor(iTextBrush, iPenColor);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(outline);
break;
}
}
// Restore the clipping manager to its previous state.
iRegionManager.SetClippingRegion(oldClippingRegion);
oldClippingRegion.Close();
#endif
}
/**
@see MDirectGdiEngine::EndDrawGlyph()
Undoes any OpenVG or engine state changes made in BeginDrawGlyph().
*/
void CVgEngine::EndDrawGlyph()
{
#ifdef DRAWGLYPH_BUFFERED
FlushDrawGlyphs();
iDrawGlyphCount = 0;
#endif
vgLoadMatrix(Identity());
if (iOrigin != TPoint(0,0))
{
vgTranslate(iOrigin.iX, iOrigin.iY);
}
vgSetPaint(iBrush, VG_FILL_PATH);
vgSeti(VG_IMAGE_MODE, VG_DRAW_IMAGE_NORMAL);
}
#ifdef DRAWGLYPH_BUFFERED
void CVgEngine::FlushDrawGlyphs()
{
VGImage foreground[KMaxGlyphs];
Mem::FillZ(foreground, iDrawGlyphCount*sizeof(VGImage));
VGImage outline[KMaxGlyphs];
VGImage shadow[KMaxGlyphs];
for (TInt glyph = 0; glyph < iDrawGlyphCount; ++glyph)
{
if (foreground[glyph] == 0)
{
TSize aGlyphImageSize = iDrawGlyphCommand[glyph].aGlyphImageSize;
TChar aChar = iDrawGlyphCommand[glyph].aChar;
TGlyphBitmapType aGlyphBitmapType = iDrawGlyphCommand[glyph].aGlyphBitmapType;
TUint8* aGlyphImage = iDrawGlyphCommand[glyph].aGlyphImage;
VGImage foreground1 = VG_INVALID_HANDLE;
VGImage outline1 = VG_INVALID_HANDLE;
VGImage shadow1 = VG_INVALID_HANDLE;
TInt err = iFontGlyphImageStorage -> GlyphImage(iFontId, aChar, aGlyphBitmapType, aGlyphImage, aGlyphImageSize, &foreground1, &shadow1, &outline1);
if(err != KErrNone)
{
iDriver.SetError(err);
if(err == KErrNoMemory)
{
if((foreground1 == VG_INVALID_HANDLE) ||
((aGlyphBitmapType == EFourColourBlendGlyphBitmap) && ((shadow1 == VG_INVALID_HANDLE) || (outline1 == VG_INVALID_HANDLE))))
{
return;
}
}
else
{
return;
}
}
foreground[glyph] = foreground1;
outline[glyph] = outline1;
shadow[glyph] = shadow1;
for (TInt nextGlyph = glyph+1; nextGlyph < iDrawGlyphCount; nextGlyph++)
{
if (foreground[nextGlyph] == 0)
{
if (iDrawGlyphCommand[glyph].SameGlyph(iDrawGlyphCommand[nextGlyph]))
{
foreground[nextGlyph] = foreground[glyph];
outline[nextGlyph] = outline[glyph];
shadow[nextGlyph] = shadow[glyph];
}
}
}
}
}
RRegion oldClippingRegion;
oldClippingRegion.Copy(iRegionManager.ClippingRegion());
vgLoadMatrix(Identity());
TPoint lastPos;
for (TInt glyph = 0; glyph < iDrawGlyphCount; ++glyph)
{
TGlyphBitmapType aGlyphBitmapType = iDrawGlyphCommand[glyph].aGlyphBitmapType;
TRect aClipRect = iDrawGlyphCommand[glyph].aClipRect;
DirectGdi::TGraphicsRotation aRotation = iDrawGlyphCommand[glyph].aRotation;
TPoint aPos = iDrawGlyphCommand[glyph].pos;
iRegionManager.ClipTo(aClipRect);
// Load the matrix which converts Symbian coordinate system to OpenVG coordinate system
if(aRotation == DirectGdi::EGraphicsRotation90)
{
vgTranslate(aPos.iX+1, aPos.iY);
vgRotate(90.0f);
}
else if(aRotation == DirectGdi::EGraphicsRotation270)
{
vgTranslate(aPos.iX, aPos.iY+1);
vgRotate(270.0f);
}
else
{
//vgTranslate(aPos.iX, aPos.iY);
vgTranslate(aPos.iX-lastPos.iX, aPos.iY - lastPos.iY);
lastPos = aPos;
}
switch(aGlyphBitmapType)
{
case EMonochromeGlyphBitmap:
case EAntiAliasedGlyphBitmap:
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(foreground[glyph]);
break;
case EFourColourBlendGlyphBitmap:
{
SetVgPaintColor(iTextBrush, iBrushColor);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(foreground[glyph]);
SetVgPaintColor(iTextBrush, iTextShadowColor);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(shadow[glyph]);
SetVgPaintColor(iTextBrush, iPenColor);
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(outline[glyph]);
break;
}
}
// Restore the clipping manager to its previous state.
iRegionManager.SetClippingRegion(oldClippingRegion);
}
oldClippingRegion.Close();
}
#endif
/**
@see MDirectGdiEngine::CopyRect()
*/
void CVgEngine::CopyRect(const TPoint& aOffset, const TRect& aRect)
{
MakeEngineCurrent();
// Transformations, masking and blending are not applied.
// So need to convert to VG coordinate system.
// i.e. Need Bottom-Left coord of aRect in VG's coordinates.
// Also need to allow for drawing engine coordinate system (iOrigin)
const TPoint sourcePoint = ConvertToVgCoords(TPoint(aRect.iTl.iX, aRect.iBr.iY) + iOrigin);
// Scissoring is applied to destination, but does not affect reading of pixels.
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgCopyPixels(sourcePoint.iX + aOffset.iX, sourcePoint.iY - aOffset.iY, // destination point
sourcePoint.iX, sourcePoint.iY, // source point
aRect.Width(), aRect.Height()); // size of rect to copy
}
/**
@see MDirectGdiEngine::ExternalizeL()
@see InternalizeL()
*/
void CVgEngine::ExternalizeL(RWriteStream& aWriteStream)
{
aWriteStream.WriteUint32L(iPaintMode);
aWriteStream << iLinePos;
}
/**
@see MDirectGdiEngine::InternalizeL()
@see ExternalizeL()
*/
void CVgEngine::InternalizeL(RReadStream& aReadStream)
{
iPaintMode = aReadStream.ReadUint32L();
aReadStream >> iLinePos;
}
/**
@see MDirectGdiEngine::GetInterface()
*/
TInt CVgEngine::GetInterface(TUid aInterfaceId, TAny*& aInterface)
{
aInterface = NULL;
TInt err = KErrNone;
switch (aInterfaceId.iUid)
{
case KDirectGdiGetGlyphStorageUid:
{
aInterface = static_cast<MFontGlyphImageStorage*> (iDriver.FontGlyphImageStorage());
break;
}
case KDirectGdiVgImageCacheUid:
{
aInterface = static_cast<MVgImageCache*> (iDriver.VgImageCache());
break;
}
case KDrawDeviceOriginInterfaceID:
{
aInterface = static_cast<MDrawDeviceOrigin*>(this);
break;
}
default:
err = KErrNotSupported;
break;
}
return err;
}
/**
Converts a point from the Symbian OS graphics coordinate system to the OpenVG coordinate system.
The Symbian OS coordinate system's x-axis increases positively from the origin rightwards.
The y-axis increases positively from the origin downwards.
The OpenVG coordinate system's x-axis increases positively from the origin rightwards.
The y-axis increases positively from the origin upwards.
Therefore a point (X,Y) in the Symbian OS coordinate system would be equivalent to a point
(X',Y') in the OpenVG coordinate system by the following transformations:
X' = X
Y' = (Height of rendering target) - Y
@param aPoint A point specified in the Symbian OS graphics coordinate system.
@return The point specified in the OpenVG-specific coordinate system.
*/
const TPoint CVgEngine::ConvertToVgCoords(const TPoint& aPoint)
{
MakeEngineCurrent();
TInt targetHeight = iSize.iHeight;
return TPoint(aPoint.iX, targetHeight - aPoint.iY);
}
/**
Converts the position of a rectangle from the Symbian OS graphics coordinate system to the
OpenVG coordinate system.
The Symbian OS coordinate system's x-axis increases positively from the origin rightwards.
The y-axis increases positively from the origin downwards.
A rectangle's position is specified by the top-left coordinate.
The OpenVG coordinate system's x-axis increases positively from the origin rightwards.
The y-axis increases positively from the origin upwards.
A rectangle's position is specified by the bottom-left coordinate.
A point (X,Y) in the Symbian OS coordinate system would be equivalent to point
(X',Y') in the OpenVG coordinate system by the following transformations:
X' = X
Y' = (Height of rendering target) - Y
@param aRect A rectangle whose position is to be converted for use in OpenVG.
@return The bottom-left point of the rectangle, specified in the OpenVG specific coordinate system.
*/
const TPoint CVgEngine::ConvertToVgCoords(const TRect& aRect)
{
MakeEngineCurrent();
TInt targetHeight = iSize.iHeight;
return TPoint(aRect.iTl.iX, targetHeight - aRect.iBr.iY);
}
/**
Resets the user-to-surface OpenVG matrices to the default system matrix.
*/
void CVgEngine::ResetVgMatrix()
{
ResetVgMatrix(VG_MATRIX_PATH_USER_TO_SURFACE);
ResetVgMatrix(VG_MATRIX_IMAGE_USER_TO_SURFACE);
}
/**
Resets an OpenVG matrix to the default system matrix. Current origin offset applies.
@param aMatrixMode The OpenVG matrix being reset.
*/
void CVgEngine::ResetVgMatrix(const VGMatrixMode aMatrixMode)
{
vgSeti(VG_MATRIX_MODE, aMatrixMode);
// Load matrix which converts Symbian coordinate system to VG coordinate system
vgLoadMatrix(Identity());
// Add in translation for drawing engine coordinate system
vgTranslate(iOrigin.iX, iOrigin.iY);
}
/**
If aPath is a null handle, a VGPath is created with enough space to hold aExpectedCommandCount commands.
If the path is already a handle to a valid path, the path is simply cleared instead of recreated.
Also allocates memory for two arrays; one to hold all commands we will add to the newly
created path, and one to hold all coordinates we will add to the newly created path.
Commands and coordinates are not added to the path until FinishPath() is called for this path.
Commands and coordinates are saved for adding to the path when FinishPath() is
called by calling AppendPathCommand(). Any failures are reported to the driver before returning.
@param aPath A handle to the path being cleared/created.
@param aExpectedCommandCount The number of commands the path is expected to hold. If this size
is underestimated, the arrays that hold the current commands and coordinates will grow
as additional items are added beyond the original estimated size. Once a path is finished, the current
command and coordinate counters are reset, but the memory remains to cut down on memory allocations.
@return EFalse if memory allocation or path creation fails, ETrue otherwise.
@see CVgEngine::AppendPathCommand(VGubyte)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat, VGfloat)
@see CVgEngine::PrepareForPathCommand()
@see CVgEngine::FinishPath()
@see CVgEngine::AllocPathCommands()
@see CVgEngine::AllocPathCoords()
*/
TBool CVgEngine::PreparePath(VGPath& aPath, TInt aExpectedCommandCount)
{
TInt err = KErrNone;
err = AllocPathCommands(aExpectedCommandCount);
if(KErrNone == err)
{
err = AllocPathCoords(aExpectedCommandCount*2); // guess at the number of coords needed, this will be reallocated if it is not big enough
}
if(KErrNone == err)
{
if (aPath != VG_INVALID_HANDLE)
vgClearPath(aPath, VG_PATH_CAPABILITY_APPEND_TO);
else
{
aPath = vgCreatePath(VG_PATH_FORMAT_STANDARD,
VG_PATH_DATATYPE_F,
1.0f, //scale
0.0f, //bias
aExpectedCommandCount,
aExpectedCommandCount*2, //expected coord count
VG_PATH_CAPABILITY_APPEND_TO);
if (aPath == VG_INVALID_HANDLE)
err = KErrNoMemory;
}
}
if (KErrNone != err)
{
iDriver.SetError(err);
return EFalse;
}
return ETrue;
}
/**
Adds a path command to the currently saved array of path commands. Any error that occurs is stored
with the driver.
@param aCommand The VGPathCommand to add to the current path, e.g VG_CLOSE_PATH.
@see CVgEngine::CreatePath()
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat, VGfloat)
@see CVgEngine::PrepareForPathCommand()
@see CVgEngine::FinishPath()
@see CVgEngine::AllocPathCommands()
@see CVgEngine::AllocPathCoords()
*/
void CVgEngine::AppendPathCommand(VGubyte aCommand)
{
TInt err = PrepareForPathCommand(1, 0);
if (KErrNone == err)
{
iPathCommands[iPathCommandCount++] = aCommand;
}
else
{
iDriver.SetError(err);
}
}
/**
Adds a path command and a single coordinate to the currently saved array of path commands.
Any error that occurs is stored with the driver.
@param aCommand The VGPathCommand to add to the current path, e.g VG_HLINE_TO_ABS.
@param aCoord The coordinate to add to the current path, e.g. 10.f.
@see CVgEngine::CreatePath()
@see CVgEngine::AppendPathCommand(VGubyte)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat, VGfloat)
@see CVgEngine::PrepareForPathCommand()
@see CVgEngine::FinishPath()
@see CVgEngine::AllocPathCommands()
@see CVgEngine::AllocPathCoords()
*/
void CVgEngine::AppendPathCommand(VGubyte aCommand, VGfloat aCoord)
{
TInt err = PrepareForPathCommand(1, 1);
if (KErrNone == err)
{
iPathCommands[iPathCommandCount++] = aCommand;
iPathCoords[iPathCoordCount++] = aCoord;
}
else
{
iDriver.SetError(err);
}
}
/**
Adds a path command and two coordinates to the currently saved array of path commands.
Any error that occurs is stored with the driver.
@param aCommand The VGPathCommand to add to the current path, e.g VG_MOVE_TO_ABS.
@param aCoord1 The coordinate to add to the current path, e.g. 10.f.
@param aCoord2 The coordinate to add to the current path, e.g. 10.f.
@see CVgEngine::CreatePath()
@see CVgEngine::AppendPathCommand(VGubyte)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat)
@see CVgEngine::PrepareForPathCommand()
@see CVgEngine::FinishPath()
@see CVgEngine::AllocPathCommands()
@see CVgEngine::AllocPathCoords()
*/
void CVgEngine::AppendPathCommand(VGubyte aCommand, VGfloat aCoord1, VGfloat aCoord2)
{
TInt err = PrepareForPathCommand(1, 2);
if (KErrNone == err)
{
iPathCommands[iPathCommandCount++] = aCommand;
iPathCoords[iPathCoordCount++] = aCoord1;
iPathCoords[iPathCoordCount++] = aCoord2;
}
else
{
iDriver.SetError(err);
}
}
/**
Allocates memory to store the passed number of commands and coordinates in the saved command and
coordinate arrays. Any error that occurs is stored with the driver.
@param aCommandCount The number of new commands expected to be added to the current command array.
@param aCoordCount The number of new commands expected to be added to the current coordinate array.
@return KErrNoMemory if memory allocation fails, KErrNone otherwise.
@see CVgEngine::CreatePath()
@see CVgEngine::AppendPathCommand(VGubyte)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat, VGfloat)
@see CVgEngine::FinishPath()
@see CVgEngine::AllocPathCommands()
@see CVgEngine::AllocPathCoords()
*/
TInt CVgEngine::PrepareForPathCommand(TInt aCommandCount, TInt aCoordCount)
{
// Do we need to add space for the new commands and coords?
TInt err = AllocPathCommands(iPathCommandCount+aCommandCount);
if (err == KErrNone)
{
err = AllocPathCoords(iPathCoordCount+aCoordCount);
}
if (err != KErrNone)
{
iDriver.SetError(err);
}
return err;
}
/**
Adds the contents of the saved command and coordinate arrays to the passed VGPath ready for drawing.
Clears the counts of saved commands and coordinates.
@param aPath The path that the current commands and coordinates are to be added to.
@see CVgEngine::CreatePath()
@see CVgEngine::AppendPathCommand(VGubyte)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat, VGfloat)
@see CVgEngine::PrepareForPathCommand()
@see CVgEngine::AllocPathCommands()
@see CVgEngine::AllocPathCoords()
*/
void CVgEngine::FinishPath(VGPath aPath)
{
vgAppendPathData(aPath, iPathCommandCount, iPathCommands, iPathCoords);
iPathCommandCount = 0;
iPathCoordCount = 0;
}
/**
Allocates enough memory to hold aCommandCount path commands, unless the command array already holds
enough memory.
@param aCommandCount The number of commands to allocate space for in the command array.
@return KErrNoMemory if memory allocation fails, KErrNone otherwise.
@see CVgEngine::CreatePath(TInt)
@see CVgEngine::AppendPathCommand(VGubyte)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat, VGfloat)
@see CVgEngine::PrepareForPathCommand(TInt, TInt)
@see CVgEngine::FinishPath(VGPath)
@see CVgEngine::AllocPathCoords(TInt)
*/
TInt CVgEngine::AllocPathCommands(TInt aCommandCount)
{
TInt err = KErrNone;
if (iCurrentMaxCommands < aCommandCount)
{
VGubyte* oldPathCommands = iPathCommands;
iPathCommands = static_cast<VGubyte*>(User::ReAlloc(iPathCommands, sizeof(VGubyte)*aCommandCount));
if (iPathCommands)
{
iCurrentMaxCommands = aCommandCount;
}
else
{
delete oldPathCommands;
iCurrentMaxCommands = 0;
}
}
if(!iPathCommands)
err = KErrNoMemory;
return err;
}
/**
Allocates enough memory to hold aCoordCount path coordinates, unless the coordinate array already holds
enough memory.
@param aCoordCount The number of coordinates to allocate space for in the coordinate array.
@return KErrNoMemory if memory allocation fails, KErrNone otherwise.
@see CVgEngine::CreatePath()
@see CVgEngine::AppendPathCommand(VGubyte)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat)
@see CVgEngine::AppendPathCommand(VGubyte, VGfloat, VGfloat)
@see CVgEngine::PrepareForPathCommand()
@see CVgEngine::FinishPath()
@see CVgEngine::AllocPathCommands()
*/
TInt CVgEngine::AllocPathCoords(TInt aCoordCount)
{
TInt err = KErrNone;
if (iCurrentMaxCoords < aCoordCount)
{
VGfloat* oldPathCoords = iPathCoords;
iPathCoords = static_cast<VGfloat*>(User::ReAlloc(iPathCoords, sizeof(VGfloat)*aCoordCount));
if (iPathCoords)
{
iCurrentMaxCoords = aCoordCount;
}
else
{
delete oldPathCoords;
iCurrentMaxCoords = 0;
}
}
if(!iPathCoords)
err = KErrNoMemory;
return err;
}
/**
Helper method for creating a VGImage from a CFbsBitmap. A temporary VGImageFormat-compatible
copy of the image may be created, if the source bitmap pixel format is not directly supported
by OpenVG. If there is not enough memory available, the error state on the driver is set to KErrNoMemory.
It sets the error in the driver if out-of-memory occurs when creating a VGImage. OpenVG will set its
own internal error if creation of VGImage fails.
@param aSource The source bitmap.
@param aFlipY If 'ETrue' then inverts the image in the y axis.
@param aOrigin An offset from the top-left of the image in which to take the first pixel of the image.
@return Returns a valid VGImage created using the passed CFbsBitmap. If unable to
create a valid VGImage then VG_INVALID_HANDLE.
*/
VGImage CVgEngine::CreateSourceVGImage(const CFbsBitmap& aSource, TBool aFlipY, const TPoint& aOrigin)
{
TDisplayMode srcDisplayMode = aSource.DisplayMode();
TDisplayMode vgCompatibleDisplayMode = ClosestVgCompatibleDisplayMode(srcDisplayMode);
VGImageFormat imageFormat = MapToVgDisplayMode(vgCompatibleDisplayMode);
const TSize sourceSize = aSource.SizeInPixels();
VGImage image = DoVgCreateImage(imageFormat, sourceSize.iWidth, sourceSize.iHeight, VG_IMAGE_QUALITY_NONANTIALIASED);
if (image != VG_INVALID_HANDLE)
{
const TInt pixmapStride = CFbsBitmap::ScanLineLength(sourceSize.iWidth, vgCompatibleDisplayMode);
// Conversion is performed if changing display mode (pixel format), source is compressed, or want to flip the y orientation.
if ((vgCompatibleDisplayMode != srcDisplayMode) || (aSource.Header().iCompression != ENoBitmapCompression) || aFlipY)
{
// May be worth using a static memory buffer for smaller scan-lines, to avoid overhead of alloc.
TAny* data = User::Alloc(pixmapStride);
if (data)
{
// Allocate memory and transform source into target format.
TPoint sourcePoint (0, aFlipY ? (sourceSize.iHeight - 1) : 0);
TPtr8 targetPoint ((TUint8*)data, pixmapStride, pixmapStride);
TInt adj = aFlipY ? -1 : 1;
if (aOrigin == TPoint(0,0)) // Not shifted
{
for (TInt targetY = 0; targetY < sourceSize.iHeight; targetY++, sourcePoint.iY += adj)
{
aSource.GetScanLine(targetPoint, sourcePoint, sourceSize.iWidth, vgCompatibleDisplayMode);
vgImageSubData(image, data, pixmapStride, imageFormat, 0, targetY, sourceSize.iWidth, 1);
}
}
else if (aOrigin.iX == 0) // Only shifted in Y.
{
for (TInt targetY = 0x00; targetY < sourceSize.iHeight; targetY++, sourcePoint.iY += adj)
{
aSource.GetScanLine(targetPoint, sourcePoint, sourceSize.iWidth, vgCompatibleDisplayMode);
vgImageSubData(image, data, pixmapStride, imageFormat, 0, targetY + aOrigin.iY, sourceSize.iWidth, 1);
vgImageSubData(image, data, pixmapStride, imageFormat, 0, targetY + aOrigin.iY - sourceSize.iHeight, sourceSize.iWidth, 1);
}
}
else if (aOrigin.iY == 0) // Only shifted in X.
{
for (TInt targetY = 0x00; targetY < sourceSize.iHeight; targetY++, sourcePoint.iY += adj)
{
aSource.GetScanLine(targetPoint, sourcePoint, sourceSize.iWidth, vgCompatibleDisplayMode);
vgImageSubData(image, data, pixmapStride, imageFormat, -aOrigin.iX, targetY, sourceSize.iWidth, 1);
vgImageSubData(image, data, pixmapStride, imageFormat, -aOrigin.iX + sourceSize.iWidth, targetY, sourceSize.iWidth, 1);
}
}
else // Shifted in both X and Y.
{
for (TInt targetY = 0; targetY < sourceSize.iHeight; targetY++, sourcePoint.iY += adj)
{
aSource.GetScanLine(targetPoint, sourcePoint, sourceSize.iWidth, vgCompatibleDisplayMode);
vgImageSubData(image, data, pixmapStride, imageFormat, -aOrigin.iX, targetY + aOrigin.iY, sourceSize.iWidth, 1);
vgImageSubData(image, data, pixmapStride, imageFormat, -aOrigin.iX, targetY + aOrigin.iY - sourceSize.iHeight, sourceSize.iWidth, 1);
vgImageSubData(image, data, pixmapStride, imageFormat, -aOrigin.iX + sourceSize.iWidth, targetY + aOrigin.iY, sourceSize.iWidth, 1);
vgImageSubData(image, data, pixmapStride, imageFormat, -aOrigin.iX + sourceSize.iWidth, targetY + aOrigin.iY - sourceSize.iHeight, sourceSize.iWidth, 1);
}
}
User::Free(data);
}
else
{
iDriver.SetError(KErrNoMemory);
vgDestroyImage(image);
return VG_INVALID_HANDLE;
}
}
else
{
aSource.BeginDataAccess();
const TInt sourceDataStride = aSource.DataStride();
if (aOrigin == TPoint(0,0)) // Not shifted
{
vgImageSubData(image, aSource.DataAddress(), sourceDataStride, imageFormat, 0, 0, sourceSize.iWidth, sourceSize.iHeight);
}
else
{
TUint32* dataAddress = aSource.DataAddress();
if (aOrigin.iX == 0) // Only shifted in Y.
{
vgImageSubData(image, dataAddress, sourceDataStride, imageFormat, 0, aOrigin.iY, sourceSize.iWidth, sourceSize.iHeight);
vgImageSubData(image, dataAddress, sourceDataStride, imageFormat, 0, aOrigin.iY - sourceSize.iHeight, sourceSize.iWidth, sourceSize.iHeight);
}
else if (aOrigin.iY == 0) // Only shifted in X.
{
vgImageSubData(image, dataAddress, sourceDataStride, imageFormat, -aOrigin.iX, 0, sourceSize.iWidth, sourceSize.iHeight);
vgImageSubData(image, dataAddress, sourceDataStride, imageFormat, -aOrigin.iX + sourceSize.iWidth, 0, sourceSize.iWidth, sourceSize.iHeight);
}
else // Shifted in both X and Y.
{
vgImageSubData(image, dataAddress, sourceDataStride, imageFormat, -aOrigin.iX, aOrigin.iY, sourceSize.iWidth, sourceSize.iHeight);
vgImageSubData(image, dataAddress, sourceDataStride, imageFormat, -aOrigin.iX, aOrigin.iY - sourceSize.iHeight, sourceSize.iWidth, sourceSize.iHeight);
vgImageSubData(image, dataAddress, sourceDataStride, imageFormat, -aOrigin.iX + sourceSize.iWidth, aOrigin.iY, sourceSize.iWidth, sourceSize.iHeight);
vgImageSubData(image, dataAddress, sourceDataStride, imageFormat, -aOrigin.iX + sourceSize.iWidth, aOrigin.iY - sourceSize.iHeight, sourceSize.iWidth, sourceSize.iHeight);
}
}
aSource.EndDataAccess(ETrue);
}
}
return image;
}
/**
Creates an image which represents a standard brush pattern (e.g. EVerticalHatchBrush), by
drawing the pattern using the current pen and brush colour to an image.
@param aPatternSize The dimensions in pixels of the image to create.
@param aBrushPattern An array of 1s and 0s representing the pattern to create, the length
of this array is aSize.iWidth*aSize.iHeight.
@return KErrNone if the brush is created successfully, KErrNoMemory if we fail to create
the brush image, KErrArgument if aBrushPattern is invalid.
*/
TInt CVgEngine::CreateStandardBrush(TSize& aPatternSize, VGbyte* aBrushPattern)
{
if (aBrushPattern == NULL)
return KErrArgument;
TInt err = KErrNone;
MakeEngineCurrent();
// The image format to use as the standard brush. If this changes, it also needs changing in
// GetCurrentBrush().
const VGImageFormat dataFormat = VG_sARGB_8888;
// We want to create the brush to the nearest power of 4 size.
TSize brushSize((aPatternSize.iWidth+3)&~3, (aPatternSize.iHeight+3)&~3);
// Allocate some memory to write the brush pattern to.
TUint32* data = new TUint32[brushSize.iWidth*brushSize.iHeight];
if (data == NULL)
err = KErrNoMemory;
if (err == KErrNone)
{
if (iBrushPatternStandardRegion != VG_INVALID_HANDLE)
{
vgDestroyImage(iBrushPatternStandardRegion);
iBrushPatternStandardRegion = VG_INVALID_HANDLE;
}
// Check the size of the existing standard brush as it can't be re-used if the
// new brush to be created is a different size.
if (iBrushPatternStandard != VG_INVALID_HANDLE)
{
if (iBrushPatternStandardSize != brushSize)
{
vgDestroyImage(iBrushPatternStandard);
iBrushPatternStandard = VG_INVALID_HANDLE;
}
}
if (iBrushPatternStandard == VG_INVALID_HANDLE)
{
// Create an image to draw the brush pattern onto, this will be our offscreen buffer
iBrushPatternStandardSize = brushSize;
iBrushPatternStandard = DoVgCreateImage(dataFormat, brushSize.iWidth, brushSize.iHeight, VG_IMAGE_QUALITY_NONANTIALIASED);
if (iBrushPatternStandard == VG_INVALID_HANDLE)
err = KErrNoMemory;
}
}
if (err == KErrNone)
{
// Set the colour used for the pen. If not fully opaque and not in WriteAlpha mode,
// blend the pen colour with the brush colour.
TRgb penColor = iPenColor;
if ((iPenColor.Alpha() != 255) && (iDrawMode != DirectGdi::EDrawModeWriteAlpha))
{
penColor.SetInternal(
PMA2NonPMAPixel(
PMAPixelBlend(
NonPMA2PMAPixel(iBrushColor.Internal()),
NonPMA2PMAPixel(iPenColor.Internal()))));
}
// Draw the pattern on to the brush, pixel by pixel.
for (TInt j = 0; j < aPatternSize.iHeight; ++j)
{
for (TInt i = 0; i < aPatternSize.iWidth; ++i)
{
if (aBrushPattern[(j*aPatternSize.iWidth)+i])
{
data[(j*brushSize.iWidth)+i] = penColor._Color16MA();
}
else
{
data[(j*brushSize.iWidth)+i] = iBrushColor._Color16MA();
}
}
}
// Copy the pattern to the VGImage so we can set it as the current brush
vgImageSubData(iBrushPatternStandard, // the image to copy to
data, // the source data
brushSize.iWidth*4, // the stride of the source data
dataFormat, // the format of the source data
0, // x
0, // y
brushSize.iWidth, // width
brushSize.iHeight); // height
iBrushPatternStandardSize = brushSize;
// We only want to use the region of the brush we just created that is the size of the pattern
iBrushPatternStandardRegion = vgChildImage(iBrushPatternStandard,
0,
0,
aPatternSize.iWidth,
aPatternSize.iHeight);
iBrushPatternStandardRegionSize = aPatternSize;
}
// Clean up
delete [] data;
return err;
}
/**
Internal function to return the currently active brush and its properties.
@param aBrush On success, holds the current VGImage brush being used.
@param aSize On success, holds the dimensions of VGImage being used as the brush.
@param aFormat On success, holds the VGImageFormat of the brush.
@return ETrue if a brush is currently being used, EFalse otherwise.
*/
TBool CVgEngine::GetCurrentBrushPattern(VGImage& aBrush, TSize& aSize, VGImageFormat& aFormat) const
{
TBool success = ETrue;
if (iBrushStyle == DirectGdi::EPatternedBrush)
{
aBrush = iBrushPatternUser;
aSize = iBrushPatternUserSize;
aFormat = static_cast<VGImageFormat>(vgGetParameteri(aBrush, VG_IMAGE_FORMAT));
}
else if (iBrushStyle > DirectGdi::EPatternedBrush)
{
aBrush = iBrushPatternStandardRegion;
aSize = iBrushPatternStandardRegionSize;
// Currently we only ever use VG_sARGB_8888 for the standard brush format.
aFormat = VG_sARGB_8888;
}
else
success = EFalse;
return success;
}
/**
Copies the current brush pattern (if a brush pattern is set) into iBrushPatternNonZeroOrigin.
This function should only be used if the current brush origin is not (0,0). When copying the
current brush pattern, it is shifted to take into account the non-zero origin. This shifted brush
pattern should be used for all brush operations while a non-zero origin is set.
@return KErrNone if successful, KErrNotFound if the brush pattern could not be copied.
*/
TInt CVgEngine::CopyCurrentBrushPatternForNonZeroOrigin()
{
MakeEngineCurrent();
TInt ret = KErrNotFound;
VGImage brush = VG_INVALID_HANDLE;
TSize brushSize;
VGImageFormat imageFormat;
if (GetCurrentBrushPattern(brush, brushSize, imageFormat))
{
const TInt width = brushSize.iWidth;
const TInt height = brushSize.iHeight;
if ((width != 0) && (height != 0))
{
if (iBrushPatternNonZeroOrigin != VG_INVALID_HANDLE)
{
vgDestroyImage(iBrushPatternNonZeroOrigin);
iBrushPatternNonZeroOrigin = VG_INVALID_HANDLE;
}
// Create the brush we are going to copy the current brush into
iBrushPatternNonZeroOrigin = DoVgCreateImage(imageFormat,
width,
height,
VG_IMAGE_QUALITY_FASTER);
if (iBrushPatternNonZeroOrigin != VG_INVALID_HANDLE)
{
TInt offsetX = width - (iBrushOrigin.iX % width);
TInt offsetY = height - (iBrushOrigin.iY % height);
// Top left to bottom right
if (offsetX != 0 && offsetY != 0) // check the width and height we are copying are not 0
vgCopyImage(iBrushPatternNonZeroOrigin, width-offsetX, height-offsetY, brush, 0, 0, offsetX, offsetY, VG_FALSE);
// Top right to bottom left
if ((width-offsetX) != 0 && offsetY != 0)
vgCopyImage(iBrushPatternNonZeroOrigin, 0, height-offsetY, brush, offsetX, 0, width-offsetX, offsetY, VG_FALSE);
// Bottom left to top right
if (offsetX != 0 && (height-offsetY) != 0)
vgCopyImage(iBrushPatternNonZeroOrigin, width-offsetX, 0, brush, 0, offsetY, offsetX, height-offsetY, VG_FALSE);
// Bottom right to top left
if ((width-offsetX) != 0 && (height-offsetY) != 0)
vgCopyImage(iBrushPatternNonZeroOrigin, 0, 0, brush, offsetX, offsetY, width-offsetX, height-offsetY, VG_FALSE);
// Paint with the new non-zero origin brush
vgPaintPattern(iBrush, iBrushPatternNonZeroOrigin);
ret = KErrNone;
}
else
{
ret = KErrNoMemory;
}
}
}
return ret;
}
/**
Calculates the angle in degrees formed anti-clockwise between vector V1 and V2, where V1 is a
horizontal vector to the right from aOriginX, and V2 is the vector (aPointX-aOriginX, aPointY-aOriginY).
@param aOriginX The x coordinate of a point which represents the origin of our calculation.
@param aOriginY The y coordinate of a point which represents the origin of our calculation.
@param aPointX The x coordinate of the point which defines the angle with the origin's x-axis.
@param aPointY The y coordinate of the point which defines the angle with the origin's y-axis.
@param aWidth Width of the rectangle which defines where to draw the ellipse.
@param aHeight Height of the rectangle which defines where to draw the ellipse.
@panic DGDIAdapter 1006, if either width or height are less than 1 (debug-only).
@return The angle in degrees between the vectors V1 and V2 described above.
*/
TReal CVgEngine::GetAngleFromXAxisAnticlockwise(const TReal aOriginX, const TReal aOriginY, const TReal aPointX, const TReal aPointY, const TReal aWidth, const TReal aHeight)
{
GRAPHICS_ASSERT_DEBUG((aWidth > 0) && (aHeight > 0), EDirectGdiPanicInvalidParameter);
// The angle is calculated from the radius line that joins the point to the origin.
// The point initially provided defines a line relative to the ellipse.
// But the VG spec states that the required angle is that for a perfect circle
// before that circle is scaled by the bounding rect into an ellipse.
// Therefore, downscale the position of the point relative to the origin, by the
// relative dimensions of the width/height of the ellipse to make it relative to
// the underlying circle. Then use the resulting circle radius line to calculate the angle.
TReal angle = 0.0f;
TReal pointX = aPointX-aOriginX;
TReal pointY = aPointY-aOriginY;
const TReal scalingFactor = (aWidth / aHeight);
pointY *= scalingFactor;
Math::ATan(angle, pointY, pointX);
// Adjust the angle for Q2 and Q3
if (pointY < 0)
angle = (KPi*2)+angle;
return angle*KRadToDeg;
}
/**
Method for drawing arcs or pies. An arc is a segment of an ellipse which is defined by a given rectangle.
The arc is drawn anti-clockwise from the arc start point to the arc end point. The arc start point is the
intersection between vectors from the centre of the ellipse to the given start position and the ellipse.
The arc end point is defined in the same way.
@param aRect The rectangle which defines where to draw the ellipse.
@param aStart Position to be used in defining the arc start point.
@param aEnd Position to be used in defining the arc end point.
@param aArcType The type of arc to draw; an arc, a chord or a pie.
@post Request to draw an arc has been accepted. There is no guarantee that the request
has been processed when the method returns.
@see CVgEngine::DrawPie()
@see CVgEngine::DrawArc()
*/
void CVgEngine::DoDrawArc(const TRect& aRect, const TPoint& aStart, const TPoint& aEnd, VGUArcType aArcType)
{
MakeEngineCurrent();
// Only draw if we are not painting with a NULL pen and a NULL brush
if (iPaintMode == 0)
return;
// If the pen width and height are the same then draw as normal. If they are different but we should be filling
// this shape we need to draw the filled area only as normal (not the outline). The outline of the shape is drawn
// in the block of code below to allow the effect of a different width and height pen to be applied.
if ((iPenSize.iWidth == iPenSize.iHeight) || ((iPaintMode & VG_FILL_PATH) && (aArcType == VGU_ARC_PIE)))
{
TReal originX = aRect.iTl.iX + (static_cast<TReal>(aRect.Width())*0.5);
TReal originY = aRect.iTl.iY + (static_cast<TReal>(aRect.Height())*0.5);
TReal startAngle = GetAngleFromXAxisAnticlockwise(originX, originY, aStart.iX, aStart.iY, aRect.Width(), aRect.Height());
TReal endAngle = GetAngleFromXAxisAnticlockwise(originX, originY, aEnd.iX, aEnd.iY, aRect.Width(), aRect.Height());
TReal extent = endAngle - startAngle;
// The extent defines what direction the arc is drawn in, so make sure we always draw
// anti-clockwise (-ve extent)
if (extent > 0.0f)
extent -= 360.0f;
// If the start and end points are the same, make sure we draw arc all the way round the ellipse
if ((aStart == aEnd) || (extent > -0.0001f))
extent = -360.0f;
// Before any vgu command, call SetError() as this stores the current vg error state (if any) in the
// driver. Some implementations of vgu clears error state so we'd lose error codes otherwise.
iDriver.SetError(KErrNone);
if (PreparePath(iVgPath, 5)) // N.B. 5 is just an initial hint as to how large the path may be, not its final size
{
VGUErrorCode err = vguArc(iVgPath,
originX + 0.5f,
originY + 0.5f,
aRect.Width(),
aRect.Height(),
startAngle,
extent,
aArcType);
if (err == VGU_NO_ERROR)
{
VGbitfield paintMode = iPaintMode;
if(aArcType == VGU_ARC_OPEN)
{
paintMode &= ~VG_FILL_PATH;
}
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, (iPenSize.iWidth == iPenSize.iHeight) ? paintMode : VG_FILL_PATH);
}
else
{
SetVguError(err);
}
}
}
if ((iPenSize.iWidth != iPenSize.iHeight)
&& (iPaintMode & VG_STROKE_PATH)
&& (iPenSize.iWidth != 0) && (iPenSize.iHeight != 0))
{
// Setting a pen with different width and height is not available on OpenVG, so we need to scale
// the coordinates we are drawing and apply a scaling matrix that scales by the width and height
// of the pen to get the effect of a pen width different width and height.
TSize penSize = iPenSize;
SetPenSize(TSize(1, 1));
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
vgScale(penSize.iWidth, penSize.iHeight);
VGfloat scaleX = 1.0f/(float)penSize.iWidth;
VGfloat scaleY = 1.0f/(float)penSize.iHeight;
TReal originX = aRect.iTl.iX + (static_cast<TReal>(aRect.Width())*0.5);
TReal originY = aRect.iTl.iY + (static_cast<TReal>(aRect.Height())*0.5);
TReal startAngle = GetAngleFromXAxisAnticlockwise(originX, originY, aStart.iX, aStart.iY, aRect.Width(), aRect.Height());
TReal endAngle = GetAngleFromXAxisAnticlockwise(originX, originY, aEnd.iX, aEnd.iY, aRect.Width(), aRect.Height());
TReal extent = endAngle - startAngle;
// The extent defines what direction the arc is drawn in, so make sure we always draw
// anti-clockwise (-ve extent)
if (extent > 0.0f)
extent -= 360.0f;
// If the start and end points are the same, make sure we draw arc all the way round the ellipse
if ((aStart == aEnd) || (extent > -0.0001f))
extent = -360.0f;
// Before any vgu command, call SetError() as this stores the current vg error state (if any) in the
// driver. Some implementations of vgu clears error state so we'd lose error codes otherwise.
iDriver.SetError(KErrNone);
if (PreparePath(iVgPath, 5))
{
VGUErrorCode err = vguArc(iVgPath,
(originX + 0.5f) * scaleX,
(originY + 0.5f) * scaleY,
(float)aRect.Width() * scaleX,
(float)aRect.Height() * scaleY,
startAngle,
extent,
aArcType);
if (err == VGU_NO_ERROR)
{
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawPath(iVgPath, VG_STROKE_PATH);
}
else
{
SetVguError(err);
}
}
ResetVgMatrix(VG_MATRIX_PATH_USER_TO_SURFACE);
SetPenSize(penSize);
}
}
/**
Helper function for drawing a source as a CDirectGdiImageSource.
@see DrawResource(const TRect&, const RDirectGdiDrawableSource&, DirectGdi::TGraphicsRotation).
*/
void CVgEngine::DoDrawResource(const TRect& aDestRect,
CDirectGdiImageSourceImpl* aSource,
DirectGdi::TGraphicsRotation aRotation)
{
TSize size = aSource->Size();
TRect srcRect(0, 0, size.iWidth, size.iHeight);
DoDrawResource(aDestRect, aSource, srcRect, aRotation);
}
/**
Helper function for drawing a source as a CDirectGdiImageSource.
@see DrawResource(const TRect&, const RDirectGdiDrawableSource&, const TRect&, DirectGdi::TGraphicsRotation).
*/
void CVgEngine::DoDrawResource(const TRect& aDestRect,
CDirectGdiImageSourceImpl* aSource,
const TRect& aSourceRect,
DirectGdi::TGraphicsRotation aRotation)
{
// If the source rect is smaller than the actual source size then we need to create a child VGImage to draw
VGImage vgImage = aSource->VgImage();
TSize size = aSource->Size();
if ((aSourceRect.Width() < size.iWidth) || (aSourceRect.Height() < size.iHeight))
{
vgImage = vgChildImage(vgImage, aSourceRect.iTl.iX, aSourceRect.iTl.iY, aSourceRect.Width(), aSourceRect.Height());
}
if (vgImage != VG_INVALID_HANDLE)
{
vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
TRect destRect(aDestRect);
TRect sourceRect(aSourceRect);
if (aRotation == DirectGdi::EGraphicsRotationNone ||
aRotation == DirectGdi::EGraphicsRotation180)
{
// Pixel-data in EGLImages appears to be upside down due to the Y-inversion
// effect of the Identity matrix. Therefore must undo the Y-inversion here
// and adjust destination rect accordingly.
const TInt destRectHeight = aDestRect.Height();
destRect.iTl.iY = (iSize.iHeight - aDestRect.iTl.iY - iOrigin.iY) - destRectHeight;
destRect.iTl.iX += iOrigin.iX;
destRect.iBr.iX += iOrigin.iX;
destRect.iBr.iY = destRect.iTl.iY + destRectHeight;
sourceRect.iBr.iY = aSource->Size().iHeight - sourceRect.iTl.iY;
sourceRect.iTl.iY = sourceRect.iBr.iY - aSourceRect.Height();
vgLoadIdentity();
}
else
{
// But if rotation is 90 or 270 degrees, only need to mirror in the X-axis.
vgScale(-1, 1);
}
VGfloat xScale = 1.f;
VGfloat yScale = 1.f;
VGint posX = destRect.iTl.iX;
VGint posY = destRect.iTl.iY;
switch (aRotation)
{
case DirectGdi::EGraphicsRotation90:
xScale = ((VGfloat)destRect.Width()/(VGfloat)sourceRect.Height());
yScale = ((VGfloat)destRect.Height()/(VGfloat)sourceRect.Width());
vgTranslate(-posX, posY);
vgScale(xScale, yScale);
vgRotate(90.0f);
break;
case DirectGdi::EGraphicsRotation180:
xScale = ((VGfloat)destRect.Width()/(VGfloat)sourceRect.Width());
yScale = ((VGfloat)destRect.Height()/(VGfloat)sourceRect.Height());
vgTranslate(posX+destRect.Width(), posY+destRect.Height());
vgScale(xScale, yScale);
vgRotate(180.0f);
break;
case DirectGdi::EGraphicsRotation270:
xScale = ((VGfloat)destRect.Width()/(VGfloat)sourceRect.Height());
yScale = ((VGfloat)destRect.Height()/(VGfloat)sourceRect.Width());
vgTranslate(-posX-destRect.Width(), (posY+destRect.Height()));
vgScale(xScale, yScale);
vgRotate(270.0f);
break;
default: // DirectGdi::EGraphicsRotationNone
xScale = ((VGfloat)destRect.Width()/(VGfloat)sourceRect.Width());
yScale = ((VGfloat)destRect.Height()/(VGfloat)sourceRect.Height());
vgTranslate(posX, posY);
vgScale(xScale, yScale);
break;
}
for (iRegionManager.Begin(); iRegionManager.ApplyClipRegion(); iRegionManager.Next())
vgDrawImage(vgImage);
if (vgImage != aSource->VgImage())
{
// Created a child VGImage, so destroy after use.
vgDestroyImage(vgImage);
}
ResetVgMatrix(VG_MATRIX_IMAGE_USER_TO_SURFACE);
}
}
/**
Maps a VGU error code to a Symbian OS error code, and sets the driver's error state. If the
error is unrecognised, the error is set to KErrGeneral.
@param aErr The return value (error state) from a VGU command.
@post If empty, the driver's error state is updated to the mapped error code.
*/
void CVgEngine::SetVguError(VGUErrorCode aErr)
{
switch(aErr)
{
case VGU_NO_ERROR:
break;
case VGU_OUT_OF_MEMORY_ERROR:
iDriver.SetError(KErrNoMemory);
break;
case VGU_BAD_HANDLE_ERROR:
iDriver.SetError(KErrBadHandle);
break;
case VGU_ILLEGAL_ARGUMENT_ERROR:
iDriver.SetError(KErrArgument);
break;
case VGU_PATH_CAPABILITY_ERROR:
iDriver.SetError(KErrNotSupported);
break;
default:
iDriver.SetError(KErrGeneral);
break;
}
}
/**
Helper method for creating a VGImage. This method clears the VG image cache in an attempt to
reclaim some memory if creation of a VGImage fails due to no memory being available,
it then tries to create the image again. If image creation fails again it then clears the glyph
cache and tries to create the image again. If image creation still fails, OpenVG is forced to
complete all currently outstanding drawing requests, so that any OpenVG objects marked for deletion,
such as VGImages that are currently waiting to be drawn, are freed. This is an attempt to make sure
that images are still displayed in low memory conditions. Use this method instead of calling
vgCreateImage() directly. Clearing the VG image cache in this way will have a negative effect on
performance regarding speed, but it is more important that an attempt is made to draw something
when memory is low.
@param aFormat The pixel format of the image to be created
@param aWidth The width of the image to be created
@param aHeight The height of the image to be created
@param aAllowedQuality One of the VGImageQuality flags
@return A VGImage handle if the image was created successfully, VG_INVALID_HANDLE otherwise
*/
VGImage CVgEngine::DoVgCreateImage(VGImageFormat aFormat, VGint aWidth, VGint aHeight, VGbitfield aAllowedQuality)
{
const TInt oldVgError = CDirectGdiDriverImpl::GetVgError();
VGImage imageRet = vgCreateImage(aFormat, aWidth, aHeight, aAllowedQuality);
if (imageRet == VG_INVALID_HANDLE)
{
// If the new error is anything other than KErrNoMemory, there is nothing that can be done.
TInt newVgError = CDirectGdiDriverImpl::GetVgError();
if (newVgError != KErrNoMemory)
{
iDriver.SetError(oldVgError != KErrNone ? oldVgError : newVgError);
return imageRet;
}
// From here on, we are assuming any failure to create the image is due to OOM.
if (iDriver.VgImageCache())
{
// Delete all the images that are currently in the cache then try and create the image again
iDriver.VgImageCache()->ResetCache();
imageRet = vgCreateImage(aFormat, aWidth, aHeight, aAllowedQuality);
}
if ((imageRet == VG_INVALID_HANDLE) && iFontGlyphImageStorage)
{
// Clear the glyph cache as well then try and create the image again
iFontGlyphImageStorage->CleanGlyphImageCache();
imageRet = vgCreateImage(aFormat, aWidth, aHeight, aAllowedQuality);
}
if ((imageRet == VG_INVALID_HANDLE))
{
// Finally, force completion of any outstanding drawing, may free any VGImages marked for deletion.
// Empty the current OpenVG error state before calling Finish(), as Finish() may call SetError(),
// and could prematurely set the driver error state to KErrNoMemory.
//coverity[check_return]
//coverity[unchecked_value]
vgGetError();
iDriver.Finish();
imageRet = vgCreateImage(aFormat, aWidth, aHeight, aAllowedQuality);
}
// If the above worked, empty any OpenVG error state set by any failed attempts to create the image.
if (imageRet != VG_INVALID_HANDLE)
{
//coverity[check_return]
//coverity[unchecked_value]
vgGetError();
}
}
// Reset the error code to the original VG error code. If oldVgError is KErrNone,
// SetError() will use the current OpenVG error state.
iDriver.SetError(oldVgError);
return imageRet;
}
/**
Helper method for setting the colour property of a VGPaint object from a TRgb structure.
@param aPaint The VGPaint object to change the colour property of.
@param aColor The colour to set the paint to.
*/
void CVgEngine::SetVgPaintColor(VGPaint& aPaint, const TRgb& aColor)
{
// Need to swap from internal ARGB to RGBA for OpenVG.
const TUint argb = aColor.Internal();
const VGuint rgba = ((argb & 0xFFFFFF) << 8) + ((argb & 0xFF000000) >> 24);
vgSetColor(aPaint, rgba);
}