HTML5 Canvas ActiveX

CairoCanvasX is an HTML5 Canvas 2D Rendering Context for use with OLE/COM languages that does not require HTML5 run-time. The ActiveX uses Cairo 2D Graphics Library version 1.10.2 from GTK+ 2.24 win32 implementation. This is a production-grade stable component for commercial use that will add hardware accelerated vector graphics in your desktop applications and 2D games. Effort has been made to preserve HTML5 canvas API in order to offer smooth web to desktop porting and vice versa.

Key Features

Downloads

Download CairoCanvasX ActiveX version 1.0.500.1487 (12 Apr 2018)
MD5:    40a4f15f7fd3fdafe8769d4e5ca601b2
SHA-1:  cbd5ddb0486f65bc222c8af72030b18fa5a0792c

Complete installation package for Windows 7, 8, 10 (x86, works on x64 too).
Includes CairoCanvasX ActiveX, GTK+ 2.24.10-20120208 Cairo implementation and Visual Basic 6.0 SP6 Samples.


Cairo binaries bundle can be also downloaded separately from here:
https://ftp.gnome.org/Public/gnome/binaries/win32/gtk+/2.24/gtk+-bundle_2.24.10-20120208_win32.zip


Microsoft x86 C++ 2015 Runtime must be installed on your computer. Can be also downloaded separately from here:
Microsoft Visual C++ 2015 Redistributable Update 3 RC


License Agreement:
mobileFX Software End-User License Agreement for CairoCanvasX ActiveX Control


Cairo Graphics Library

Cairo Introduction

Cairo is an open source programming graphics library written in C, originally developed by Keith Packard, Carl Worth and Behdad Esfahbod on 2003, that provides a vector graphics-based, device-independent API for software developers. It provides primitives for two-dimensional drawing across a number of different back ends. Cairo uses hardware acceleration when available. There is a formal proposal to standardize a C++ 2D graphics API based on a mechanical transformation of Cairo.

https://cairographics.org/

Cairo Drawing Model

The Cairo drawing model relies on a three layer model. Any drawing process takes place in three steps:

  1. First a mask is created, which includes one or more vector primitives or forms, i.e., circles, squares, TrueType fonts, bézier curves, etc.
  2. Then source must be defined, which may be a color, a color gradient, a bitmap or some vector graphics, and from the painted parts of this source a die cut is made with the help of the above defined mask.
  3. Finally the result is transferred to the destination or surface, which is provided by the back-end for the output.

This constitutes a fundamentally different approach from Scalable Vector Graphics, which directly specifies the color of shapes with Cascading Style Sheets. Whereas cairo would create a mask of a shape, then make a source for it, and then transfer them onto the surface, an SVG file would simply specify the shape with a style attribute. That said, the models are not incompatible; many SVG renderers use cairo for heavylifting.

Compatibility

Even though the control is promoted and supported only for Visual Basic 6.0 SP6, it is compatible with the following x86 programming languages and Integrated Development Environments (IDE):

Release Notes

12 Apr 2018 - Download CairoCanvasX ActiveX version 1.0.500.1487

Quick Installation

Download the setup package and install it in a place like C:\Program Files (x86)\CairoCanvasX and you are ready to start using the component.

Licensing

Evaluation

You are welcome to evaluate CairoCanvasX for as long as you want and feel free to contact us with your questions and your suggestions!  During evaluation, when opening a URL and at random intervals CairoCanvasX displays a message box informing you that it is a commercial product and that it requires a License, but it wont prevent you from using all its features.

License

In a nutshell, a single License allows you to develop an unlimited number of applications linked with CairoCanvasX ActiveX control and distribute them freely to an unlimited number of your customers. License Agreement: mobileFX Software End-User License Agreement for CairoCanvasX ActiveX

Edition Commercial Use Price
CairoCanvasX HTML5 2D Context ActiveX (x86) Yes £ 149

Distribution

If you distribute a LICENSED CairoCanvasX ActiveX along with your applications, don't forget to include MS C++ 2015 x86 redistributables. To register the ActiveX simply use: regsvr32 CairoCanvasX.ocx. Obviously you are prohibited from distributing an unlicensed copy of CairoCanvasX.

Getting Started

Commercial License Activation

First you must activate CairoCanvasX ActiveX by providing your License Key. If you are using early binding, a good place to do that is on Form_Load event. If you are using late binding you are advised to activate the control straight after its creation. This step applies only to license owners. During trial evaluation of CairoCanvasX you may skip it.

ScaleMode & AutoRedraw IMPORTANT NOTE

CairoCanvasX is a memory drawing surface; all drawing operations take place in memory and once you are ready you must copy canvas pixels to a Device Context (hDC) using canvas BitBlt method. Before you call BitBlt on a surface such as a Form or PictureBox, you need to set their ScaleMode to Pixels and AutoRedraw to true. If you desire to keep AutoRedraw false, you need to explicitly call Form or PictureBox Refresh() method after BitBlt.

HTML5 Canvas 2D Rendering Context API

CairoCanvasX wraps cairo graphics library through an HTML5 Canvas 2D Rendering Context. Added in HTML5, the HTML <canvas> element can be used to draw graphics via scripting. For example, it can be used to draw graphs, make photo compositions and create animations and simple 2D games.

For detailed HTML5 2D Rendering Context documentation please visit: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D

The element was originally introduced by Apple for the OS X Dashboard and Safari. Firefox gained support for <canvas> starting with version 1.5 and Internet Explorer supports <canvas> from version 9 on-wards; Google Chrome and Opera 9 also support <canvas>. The key concept on CairoCanvasX was to be able to port code from the web and vice-versa by offering the same functionality and seamless integration.

VERY IMPORTANT NOTE
CairoCanvasX is a memory drawing surface. You must create a CairoCanvas object by code and get its CairoContext2D interface. The CairoContext2D is used for drawing rectangles, text, images and other objects onto the canvas element. It provides the 2D rendering context for the drawing surface of a canvas element. To get an object of this interface, call getContext() on a canvas element, supplying "2d" as the argument. Please note that you need to release and re-obtain a new context every time you resize the Canvas. Typically you do that by handling the host's Resize event.

Drawing Shapes

Drawing Rectangles

Unlike SVG, canvas only supports one primitive shape: rectangles. All other shapes must be created by combining one or more paths, lists of points connected by lines. Luckily, we have an assortment of path drawing functions which make it possible to compose very complex shapes. There are three functions that draw rectangles on the canvas:

Each of these three functions takes the same parameters. x and y specify the position on the canvas (relative to the origin) of the top-left corner of the rectangle. width and height provide the rectangle's size.

Drawing Lines

For drawing straight lines, use the lineTo() method.

This method takes two arguments, x and y, which are the coordinates of the line's end point. The starting point is dependent on previously drawn paths, where the end point of the previous path is the starting point for the following, etc. The starting point can also be changed by using the moveTo() method.

Drawing Paths

The only other primitive shapes are paths. A path is a list of points, connected by segments of lines that can be of different shapes, curved or not, of different width and of different color. A path, or even a subpath, can be closed. To make shapes using paths takes some extra steps:

  1. First, you create the path.
  2. Then you use drawing commands to draw into the path.
  3. Once the path has been created, you can stroke or fill the path to render it.

Here are the functions used to perform these steps:

The first step to create a path is to call the beginPath(). Internally, paths are stored as a list of sub-paths (lines, arcs, etc) which together form a shape. Every time this method is called, the list is reset and we can start drawing new shapes.

When the current path is empty, such as immediately after calling beginPath(), or on a newly created canvas, the first path construction command is always treated as a moveTo(), regardless of what it actually is. For that reason, you will almost always want to specifically set your starting position after resetting a path.

The second step is calling the methods that actually specify the paths to be drawn.

The third, and an optional step, is to call closePath(). This method tries to close the shape by drawing a straight line from the current point to the start. If the shape has already been closed or there's only one point in the list, this function does nothing.

For example, the code for drawing a triangle would look something like this:

Moving the Pen

One very useful function, which doesn't actually draw anything but becomes part of the path list described above, is the moveTo() function. You can probably best think of this as lifting a pen or pencil from one spot on a piece of paper and placing it on the next.

When the canvas is initialized or beginPath() is called, you typically will want to use the moveTo() function to place the starting point somewhere else. We could also use moveTo() to draw unconnected paths. Take a look at the smiley face below.

To try this for yourself, you can use the code snippet below.



Drawing Arcs

To draw arcs or circles, we use the arc() or arcTo() methods.

Let's have a more detailed look at the arc method, which takes six parameters: x and y are the coordinates of the center of the circle on which the arc should be drawn. radius is self-explanatory. The startAngle and endAngle parameters define the start and end points of the arc in radians, along the curve of the circle. These are measured from the x axis. The anticlockwise parameter is a Boolean value which, when true, draws the arc anticlockwise; otherwise, the arc is drawn clockwise.

Angles in the arc function are measured in radians, not degrees.
To convert degrees to radians you can use the following expression: radians = (PI/180)*degrees.

The following example is a little more complex and it draws different arcs all with different angles and fills. The two for loops are for looping through the rows and columns of arcs. For each arc, we start a new path by calling beginPath(). In the code, each of the parameters for the arc is in a variable for clarity, but you wouldn't necessarily do that in real life.

The x and y coordinates should be clear enough. radius and startAngle are fixed. The endAngle starts at 180 degrees (half a circle) in the first column and is increased by steps of 90 degrees, culminating in a complete circle in the last column.

The statement for the clockwise parameter results in the first and third row being drawn as clockwise arcs and the second and fourth row as counterclockwise arcs. Finally, the if statement makes the top half stroked arcs and the bottom half filled arcs.



Drawing Bezier and Quadratic Curves

The next type of paths available are Bézier curves, available in both cubic and quadratic varieties. These are generally used to draw complex organic shapes.

The difference between these can best be described using the image on the right. A quadratic Bézier curve has a start and an end point (blue dots) and just one control point (indicated by the red dot) while a cubic Bézier curve uses two control points.

The x and y parameters in both of these methods are the coordinates of the end point. cp1x and cp1y are the coordinates of the first control point, and cp2x and cp2y are the coordinates of the second control point.

Using quadratic and cubic Bézier curves can be quite challenging, because unlike vector drawing software like Adobe Illustrator, we don't have direct visual feedback as to what we're doing. This makes it pretty hard to draw complex shapes. In the following example, we'll be drawing some simple organic shapes, but if you have the time and, most of all, the patience, much more complex shapes can be created.

There's nothing very difficult in these examples. In both cases we see a succession of curves being drawn which finally result in a complete shape.

Making Combinations

So far, each example on this page has used only one type of path function per shape. However, there's no limitation to the number or types of paths you can use to create a shape. So in this final example, let's combine all of the path functions to make a set of very famous game characters.



CairoCanvasX API

import "oaidl.idl";
import "ocidl.idl";

[
	uuid(F8A9D31E-CEBD-416A-9782-3DD95D507FCF),
	version(1.0),
	helpstring("mobileFX Cairo Canvas ActiveX Control"),
]
library CairoCanvasXLib
{
	importlib("stdole2.tlb");

	interface ICairoCanvas;

	typedef enum CANVAS_QUALITY
	{
		CANVAS_QUALITY_FAST,
		CANVAS_QUALITY_GOOD,
		CANVAS_QUALITY_BEST,
		CANVAS_QUALITY_NEAREST,
		CANVAS_QUALITY_BILINEAR,
		CANVAS_QUALITY_GAUSSIAN
	} CANVAS_QUALITY;

	// ==================================================================================================================================
	//	    ____      __            ____                    
	//	   /  _/___  / /____  _____/ __/___ _________  _____
	//	   / // __ \/ __/ _ \/ ___/ /_/ __ `/ ___/ _ \/ ___/
	//	 _/ // / / / /_/  __/ /  / __/ /_/ / /__/  __(__  ) 
	//	/___/_/ /_/\__/\___/_/  /_/  \__,_/\___/\___/____/  
	//	                                                    
	// ==================================================================================================================================

	////////////////////////////////////////////////////////////////////
	[
		object,
		uuid(8E599F28-E8DD-4497-B1D7-DF0056824FD3),
		dual,
		nonextensible,
		pointer_default(unique)
	]
	interface ICairoGradient : IDispatch
	{
		HRESULT addColorStop([in] double p, [in]  BSTR color);
	};

	////////////////////////////////////////////////////////////////////
	[
		object,
		uuid(23800E22-3073-4B94-84AD-C9D28B9F172D),
		dual,
		nonextensible,
		pointer_default(unique)
	]
	interface ICairoTextMetrics : IDispatch
	{
		[propget] HRESULT Width([out, retval] LONG* pVal);
		[propget] HRESULT Height([out, retval] LONG* pVal);
	};

	////////////////////////////////////////////////////////////////////
	[
		object,
		uuid(BB7FC266-EE1B-4561-9E45-94CAE231E869),
		dual,
		nonextensible,
		pointer_default(unique)
	]
	interface ICairoImage : IDispatch
	{
		HRESULT LoadFromFile([in] BSTR FileName);
		HRESULT LoadFromBuffer(VARIANT Buffer);
		[propget] HRESULT Width([out, retval] LONG* pVal);
		[propget] HRESULT Height([out, retval] LONG* pVal);
	};

	////////////////////////////////////////////////////////////////////
	[
		object,
		uuid(388B23E0-2A67-4C2C-A309-E0F054446BDB),
		dual,
		nonextensible,
		pointer_default(unique)
	]
	interface ICairoImageData : IDispatch
	{
		[propget] HRESULT Width([out, retval] LONG* pVal);
		[propget] HRESULT Height([out, retval] LONG* pVal);
	};

	////////////////////////////////////////////////////////////////////
	[
		object,
		uuid(45F84B30-2EC5-4628-ADF2-DFFDCA5FB578),
		dual,
		nonextensible,
		pointer_default(unique)
	]
	interface ICairoContext2D : IDispatch
	{
		// State 
		HRESULT save();
		HRESULT restore();

		// Transformations 
		HRESULT rotate([in] double Angle);
		HRESULT scale([in] double sx, [in] double sy);
		HRESULT translate([in] double tx, [in] double ty);
		HRESULT transform([in] double  xx, [in] double  yx, [in] double  xy, [in] double  yy, [in] double  x0, [in] double  y0);
		HRESULT setTransform([in] double  xx, [in] double  yx, [in] double  xy, [in] double  yy, [in] double  x0, [in] double  y0);
		HRESULT resetTransform();

		// Compositing 		
		[propget] HRESULT globalAlpha([out, retval] double* pVal);
		[propput] HRESULT globalAlpha([in] double newVal);
		[propget] HRESULT globalCompositeOperation([out, retval] BSTR* pVal);
		[propput] HRESULT globalCompositeOperation([in] BSTR newVal);
		[propget] HRESULT globalQuality([out, retval] CANVAS_QUALITY* pVal);
		[propput] HRESULT globalQuality([in] CANVAS_QUALITY newVal);
		

		// Colors and Styles 
		[propput] HRESULT strokeStyle([in] VARIANT newVal);
		[propput] HRESULT fillStyle([in] VARIANT newVal);

		// Gradients 
		HRESULT createLinearGradient([in] double x0, [in] double y0, [in] double x1, [in] double y1, [out, retval] ICairoGradient* *pVal);
		HRESULT createRadialGradient([in] double x0, [in] double y0, [in] double r0, [in] double x1, [in] double y1, [in] double r1, [out, retval] ICairoGradient* *pVal);

		// Shadows 
		[propget] HRESULT shadowBlur([out, retval] LONG* pVal);
		[propput] HRESULT shadowBlur([in] LONG newVal);
		[propget] HRESULT shadowColor([out, retval] BSTR* pVal);
		[propput] HRESULT shadowColor([in] BSTR newVal);
		[propget] HRESULT shadowOffsetX([out, retval] double* pVal);
		[propput] HRESULT shadowOffsetX([in] double newVal);
		[propget] HRESULT shadowOffsetY([out, retval] double* pVal);
		[propput] HRESULT shadowOffsetY([in] double newVal);

		// Rects 
		HRESULT clearRect([in] double X, [in] double Y, [in] double Width, [in] double Height);
		HRESULT fillRect([in] double X, [in] double Y, [in] double Width, [in] double Height);
		HRESULT strokeRect([in] double X, [in] double Y, [in] double Width, [in] double Height);

		// Paths 
		HRESULT beginPath();
		HRESULT closePath();
		HRESULT moveTo([in] double X, [in] double Y);
		HRESULT lineTo([in] double X, [in] double Y);
		HRESULT bezierCurveTo([in] double x1, [in] double y1, [in] double x2, [in] double y2, [in] double x3, [in] double y3);
		HRESULT quadraticCurveTo([in] double x1, [in] double y1, [in] double x2, [in] double y2);
		HRESULT arc([in] double xc, [in] double yc, [in] double radius, [in] double angle1, [in] double angle2, [in] VARIANT_BOOL anticlockwise);
		HRESULT arcTo([in] double x0, [in] double y0, [in] double x1, [in] double y1, [in] double radius);
		HRESULT rect([in] double X, [in] double Y, [in] double Width, [in] double Height);

		// Drawing Paths 
		HRESULT fill();
		HRESULT stroke();
		HRESULT clip();
		HRESULT isPointInPath([in] double X, [in] double Y, [out, retval] VARIANT_BOOL* pVal);

		// Text 
		HRESULT fillText(BSTR Text, [in] double X, [in] double Y);
		HRESULT strokeText(BSTR Text, [in] double X, [in] double Y);
		HRESULT measureText(BSTR Text, [out, retval] ICairoTextMetrics* *pVal);

		// Font	
		[propput] HRESULT font(BSTR newVal);
		[propput] HRESULT textAlign([in] BSTR newVal);
		[propput] HRESULT textBaseLine([in] BSTR newVal);

		// Drawing Images 
		HRESULT drawImage([in] ICairoImage* Image, [in] double sx, [in] double sy, [in] double sw, [in] double sh, [in] double dx, [in] double dy, [in] double dw, [in] double dh);
		HRESULT drawImageCanvas([in] ICairoCanvas* Canvas, [in] double sx, [in] double sy, [in] double sw, [in] double sh, [in] double dx, [in] double dy, [in] double dw, [in] double dh);

		// Pixel Manipulation 
		HRESULT createImageData([in] LONG w, [in] LONG h, [out, retval] ICairoImageData* *pVal);
		HRESULT putImageData([in] ICairoImageData *data, [in] LONG dx, [in] LONG dy, [in] LONG sx, [in] LONG sy, [in] LONG sw, [in] LONG sh);
		HRESULT getImageData([in] LONG sx, [in] LONG sy, [in] LONG sw, [in] LONG sh, [out, retval] ICairoImageData* *pVal);

		// Line caps/joins 
		[propget] HRESULT lineWidth([out, retval] double* pVal);
		[propput] HRESULT lineWidth([in] double newVal);
		[propget] HRESULT lineCap([out, retval] BSTR* pVal);
		[propput] HRESULT lineCap([in] BSTR newVal);
		[propget] HRESULT lineJoin([out, retval] BSTR* pVal);
		[propput] HRESULT lineJoin([in] BSTR newVal);
		[propget] HRESULT miterLimit([out, retval] double* pVal);
		[propput] HRESULT miterLimit([in] double newVal);

		// Dashed Lines	
		HRESULT setLineDash(VARIANT Dash);
		HRESULT getLineDash([out, retval] VARIANT* Dash);
		[propget] HRESULT lineDashOffset([out, retval] double* pVal);
		[propput] HRESULT lineDashOffset([in] double newVal);
	};

	////////////////////////////////////////////////////////////////////
	[
		object,
		uuid(6F639DD6-6B85-42A7-BCF8-FF3FBA67827F),
		dual,
		nonextensible,
		pointer_default(unique)
	]
	interface ICairoCanvas : IDispatch
	{
		HRESULT Activate([in] BSTR YourEmail, [in] BSTR YourActivationKey);

		[propget] HRESULT Width([out, retval] LONG* pVal);
		[propput] HRESULT Width([in] LONG newVal);
		[propget] HRESULT Height([out, retval] LONG* pVal);
		[propput] HRESULT Height([in] LONG newVal);
		HRESULT getContext(BSTR CtxType, [out, retval] ICairoContext2D* *pVal);
		HRESULT toDataURL(BSTR mime, [out, retval] BSTR* pVal);
		HRESULT toPNG24([out, retval] VARIANT* pVal);

		HRESULT BitBlt([in] LONG hDC);
	};

	// ==================================================================================================================================
	//	   ______      ________                         
	//	  / ____/___  / ____/ /___ ______________  _____
	//	 / /   / __ \/ /   / / __ `/ ___/ ___/ _ \/ ___/
	//	/ /___/ /_/ / /___/ / /_/ (__  |__  )  __(__  ) 
	//	\____/\____/\____/_/\__,_/____/____/\___/____/  
	//	                                                
	// ==================================================================================================================================

	////////////////////////////////////////////////////////////////////
	[
		uuid(BD27E6C5-64CE-4D78-B7A8-AAB355C9D400)		
	]
	coclass CairoCanvas
	{
		[default] interface ICairoCanvas;
	};

	////////////////////////////////////////////////////////////////////
	[
		noncreatable,
		uuid(F1DC8457-1C31-465D-B238-C6C439E0FD8C)
	]
	coclass CairoContext2D
	{
		[default] interface ICairoContext2D;
	};

	////////////////////////////////////////////////////////////////////
	[
		uuid(00DC2B54-2792-404B-88E6-198745D55B4F)		
	]
	coclass CairoImage
	{
		[default] interface ICairoImage;
	};

	////////////////////////////////////////////////////////////////////
	[
		noncreatable, 
		uuid(67CEFDC2-A1A7-4162-BF2A-3F486DD0CB9A)
	]
	coclass CairoImageData
	{
		[default] interface ICairoImageData;
	};

	////////////////////////////////////////////////////////////////////
	[
		noncreatable, 
		uuid(E1E85EF2-AA14-42A1-ABE7-EFB4C6184100)
	]
	coclass CairoTextMetrics
	{
		[default] interface ICairoTextMetrics;
	};

	////////////////////////////////////////////////////////////////////
	[
		noncreatable, 
		uuid(1226C296-C200-4DB3-9753-4D2557A20F32)
	]
	coclass CairoGradient
	{
		[default] interface ICairoGradient;
	};
};