diff -r 000000000000 -r 5d03bc08d59c graphicsdeviceinterface/directgdiadaptation/hwsrc/vgengine.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graphicsdeviceinterface/directgdiadaptation/hwsrc/vgengine.cpp Tue Feb 02 01:47:50 2010 +0200 @@ -0,0 +1,3820 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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& 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& 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& 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& 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(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(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 (iDriver.FontGlyphImageStorage()); + break; + } + case KDirectGdiVgImageCacheUid: + { + aInterface = static_cast (iDriver.VgImageCache()); + break; + } + case KDrawDeviceOriginInterfaceID: + { + aInterface = static_cast(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(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(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(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(aRect.Width())*0.5); + TReal originY = aRect.iTl.iY + (static_cast(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(aRect.Width())*0.5); + TReal originY = aRect.iTl.iY + (static_cast(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); + }