在 Tizen 上自定义 2D 图形

在本文中,您将通过我们的 TizenPaint 示例应用程序学习如何使用 HTML5 画布 API 和面料画布 API 绘制基元,如矩形、圆形和三角形。 您还将学习如何更改基元的某些属性(如笔划的宽度和颜色)。 在这篇文章的最后一节,我们将介绍如何以序列化的字符串形式保存您的绘图。

了解 HTML5 画布 API 和 Fabric.js

在深入研究任何细节之前,让我们先来熟悉一下 HTML5 的画布和 Fabric.js JavaScript 框架。 HTML5 画布是一个 DOM 元素,您可以利用其动态绘制 2D/3D 图形。 这是您的网页或设备屏幕上一个定义的区域,在那里你可以绘制图形对象。 若要创建一个画布,可以将此代码添加到您的 HTML 文件:

"paint-canvas" width="680" height="1000">

  Fabric.js 框架是构建在画布元素之上的交互性对象。 它使您能更容易地在 HTML5 画布上工作。 您可以创建和填充由数百个或数千个简单路径组成的几何形状和复杂形状的对象。 有关如何使用 Fabric.js 库的详细信息,请访问 http://fabricjs.com/docs/

 

介绍 TizenPaint 示例应用程序

TizenPaint 示例应用程序演示如何使用 HTML5 画布 API,以及如何使用 Fabric.js 框架在 HTML5 画布上创建和使用 2D 图形。 这个示例应用程序的 UI 只包含一个页面元素。 示例应用程序启动后,你会看到一个空白的画布和一些按钮,如保存打开绘制等 您可以通过两种模式使用此示例:绘制和编辑;正如的页面底部的两个按钮所示。 默认模式是编辑模式。 在编辑模式下,您可以操作选定的对象、例如移动、调整大小或将其旋转、更改它们的颜色或线条宽度,甚至从三个可用的图像资源(保存在"图像"文件夹中)添加 SVG 文件。 您可以通过打开绘图模式和触摸屏幕绘制新对象。 您可以使用提供的工具绘制简单的二维形状,如线条、 正方形、 圆形、 三角形和多边形。 您可以将您的绘图保存在存储设备上。 示例应用程序使用 Tizen 上的完全视区 (720 x 1280),并包括 jQuery 1.8.0 库。该示例应用程序在 Tizen SDK 2.0.0a2 上通过测试。    

图:示例应用程序的屏幕快照

使用 frabric.js 框架在画布上绘图

您可以使用画布 API 直接绘制一个原始形状 —矩形。 如果您想要绘制其他形状,您必须创建一个或多个路径,然后在画布上绘制路径。 或者,你可以使用 fabric.js 框架更方便地绘制复杂的图形中。 你不需要定义路径的点并链接路径,而是可以将通过已经定义的属性将每个形状创建为一个对象。 本文将演示如何使用 fabric.js 框架和画布 API 创建形状。 若要使用 fabric.js 框架,您必须在 HTML 画布元素上创建 fabric.Canvas() 的实例。

var fabricCanvas = new fabric.Canvas('paint-canvas');

当调用 fabric.Canvas 构造函数时,fabric.js 框架将一个次要的画布元素(这是带有"上部-画布"类标识符的画布)添加到 DOM 中。

"paint-canvas" class="lower-canvas">
"upper-canvas">

当您单击"绘图"按钮时,它激活绘图模式,并使画布上的所有其它项目不可选择。 这是为了避免您在绘图时选择一些随机对象。

$('#add-figure').unbind().click(function() {
    /**
     * Draws line or adds figure on click
     */
    if ($('#shape-menu').hasClass('hidden')) {
        drawMode = true;
        if (currShape == 'pencil')
            fabricCanvas.isDrawingMode = true;
        else
            $('#stroke-menu').toggleClass('hidden', false);
        if (currShape == 'polygon')
            $('#vertex').toggleClass('hidden', false);
        if (!($('#colors-menu').hasClass('hidden'))) {
            $('#colors-menu').addClass('hidden');
            $('#choose-color').removeClass('selected');
        }
        $('#shape-menu').toggleClass('hidden', false);
        $('#edit-figure').removeClass('selected');
        $('#add-figure').addClass('selected');

        fabricCanvas.deactivateAll();
        for ( var i = 0; i < fabricCanvas.getObjects().length; i++)
            fabricCanvas.item(i).selectable = false;

        fabricCanvas.renderAll();

    } else {
        $('#shape-menu').toggleClass('hidden', true);
        $('#stroke-menu').toggleClass('hidden', true);
        $('#vertex').toggleClass('hidden', true);
    }
});

默认情况下,选定的工具是让您绘制简单线条的铅笔。 若要使用此工具,您只需要将 fabricCanvas.isDrawingMode 设置为 true。

使用画布 API 在画布上绘图

在画布上绘制的另一个选项是使用画布 API。  若要使用画布 API,您应该使用以下代码注册画布来处理两个事件:touchstart 和 touchmove :

canvas.addEventListener("touchstart", handleStart);
canvas.addEventListener("touchmove", handleMove);

传递给 addEventListener() 方法的第一个参数是事件的类型。 第二个参数是侦听器本身。 它在指定的类型的事件发生时会收到通知。 绘图本身其实也就是录制触摸位置而异。 您可以使用以下代码这么做:

function handleStart(evt) {
    var touches = evt.changedTouches[0];
    var x = touches.pageX - leftPos;
    var y = touches.pageY - topPos;

Evt.changedTouches[0] 属性返回代表用户在视口上第一个触摸点的对象。 Touches.pageX 和 touches.pageY 属性分别表示相对于视口最左上角的 x 和 y 坐标。 LeftPos 和 topPos 属性本别表示相对于画布最左上角的 x 和 y 坐标。 如果您从 touches.pageX 减去 leftPos(对 touches.pageY 和 topPos 也同样处理),您就获得画布上的点的 x(或 y)坐标。 在 'touchstart' 侦听器中,您必须通过调用 beginPath() 方法并使用 ctx.moveTo(x,y) 方法定义起始点以开始绘图。

    ctx.beginPath();
    ctx.moveTo(x,y);

在 'touchmove' 侦听器中,您必须使用 lineTo(x, y) 方法来定义行(或路径)的终点,并通过调用 stroke() 方法绘制线条笔画:

    ctx.lineTo(x, y);
    ctx.stroke();

 

绘制不同的形状

在本节中,您将学习如何使用 fabric.js API 和画布 API 来i绘制更复杂的形状。  我们从最简单的形状 — 矩形开始。  

矩形

使用 fabric.js API 绘制矩形是很简单的。 您可以使用以下代码创建一个矩形:

var rect = new fabric.Rect({
    width : 100,
    height : 100,
    left : x,
    top : y,
    selectable : false
});

Left 和 top 属性表示矩形的中心点;width 和 height 属性定义矩形的大小;而可选的属性表明该对象是否可选。 最后,你调用 add() 方法将矩形添加到画布上:

fabricCanvas.add(rect);

如果您想要更改矩形的颜色,您可以使用 Rect 对象的以下属性:

  • 'fill' — 如果你想要用给定的颜色填充该形状;
  • 'stroke' — 如果您想要将笔画的颜色设置为给定的颜色 ;
fill : 'rgb(34,177,76)',
stroke : 'rgba(0,0,0,0.8)',

如果您不指定颜色的 fill 或 stroke 属性,在默认情况下,它们为黑色。 如果您想要更改默认的颜色,您可以指定 W3C CSS3 颜色建议中定义的任何颜色。 不同于画布 API,在 fabric.js 中的每个形状都是一个对象。 如果您想要更改属性值,可以使用 set() 方法。

rect.set('left', 'x+50');

  在使用画布 API 绘制矩形时,您可以使用以下方法:

  • 用于填充fillRect(x,y,width,height)
  • 用于带 stroke 的空strokeRect(x,y,width,height)

X 和 y 是矩形左上角的坐标。 Width 和 height 定义矩形的大小。 请注意,当你使用画布 API绘制矩形时,您在使用 fabric.js API 时必须定义矩形的左上角,而不是中心点。 画布 API 有两个重要属性,我们可以用以将颜色应用于形状:fillStyle 和 strokeStyle。

fillStyle = color
strokeStyle = color

 

在画布上绘制圆形是非常简单的。 你可以用此方法创建一个圆形对象:

var circle = new fabric.Circle({
    radius : 50,
    left : x,
    top : y,
    opacity : 1,
    selectable : false
});

您需要初始化圆对象的属性,例如我们将圆的半径初始化为 50 像素,中心点为 (x,y),和不透明度为 1;并将圆形设为不可选。 还有使用画布 API arc() 来绘制的另一种方式。

ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)

Arc() 方法绘制假想圆的周长的一段。 该圆是由中心点 (x,y) 和半径参数定义的。 StartAngle 和 endAngle 参数以正 x 轴顺时针弧度定义。 最后一个参数定义了两个点之间 arc 路径的方向。 万一您需要绘制一个完整的圆,您可以定义 startAngle = 0 * Math.PI,和 endAngle = 2 * Math.PI。

http://www.html5canvastutorials.com/demos/tutorials/html5-canvas-arcs/html5-canvas-arcs-diagram.png

Figure: arc parameters

 

三角形

在 fabric.js 中使用以下代码绘制三角形很简单:

var triangle = new fabric.Triangle({
    left : x,
    top : y,
    width : 100,
    height : 100,
    opacity : 1,
    selectable : false
});

left 和 top 属性代表三角形中心位置;其他属性都很容易理解。 如果您想要使用画布 API 绘制三角形,此方法将比使用 fabric.js API 会更复杂些。 如我们之前所述,您只可以使用给定的画布方法绘制矩形形状。 对于任何其他形状,您必须使用 moveTo 和 lineTo 方法来创建路径。 下面的代码显示了如何绘制三角形:

ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x+50, y+100);
ctx.lineTo(x-50, y+100);
ctx.lineTo(x, y);

ctx.fill(); 	// for filled triangle
//ctx.stroke(); 	// for stroked triangle
ctx.closePath();

 

更改绘图属性

您将学习如何在画布上绘制一些基本的形状。 现在我们向您展示如何更改形状的属性,如填充的颜色和描边的宽度等。 我们再将 TizenPaint 示例作为例子。 当您在示例应用程序中单击"颜色"按钮时,它显示包含可用的颜色和线条宽度的菜单。 当你改变线条的宽度时,它以您传递的值调用 setStrokeWidth () 方法。 下面的代码演示了如何使用 fabric.js API 设置描边宽度。

function setStrokeWidth(value) {
    $('#line-width').text(value);
    fabricCanvas.freeDrawingLineWidth = parseInt($('#line-width').text(), 10);

    if (fabricCanvas.getActiveObject()) {
        if (fabricCanvas.getActiveObject().get('stroke')) {
            fabricCanvas.getActiveObject().set('strokeWidth', parseInt($('#line-width').text(), 10) || 1);
        }
        fabricCanvas.renderAll();
    }
}

如果在绘制任何形状之前您想要更改线条宽度,你可以使用 fabricCanvas.freeDrawingLineWidth 属性在绘图模式中设置线条宽度。 如果您想要更改所选形状的线条宽度,你可以使用 fabricCanvas.getActiveObject() 方法选择画布上的对象,并使用 get () 方法来选择所选的画布对象的描边属性。 最后,您可以用 set()method 方法中设置行的宽度值。 现在让我们看看如何更改图形对象的颜色。 我们在示例应用程序中执行了名为 setColors() 的方法。 当您选择一种颜色时,它将调用此方法。 SetColors() 方法不仅可以更改绘图的颜色,而且也更改任何活动画布上的 '填充' 或 '描边' 对象的颜色。 下面的代码显示了如何实现 setColors() 方法:

function setColors(color) {
    if (!(color.prop("checked"))) {
        actualColor.css('border', '1px solid black');
        actualColor.prop("checked", false);
        color.css('border', '3px solid red');
        color.prop("checked", true);
        actualColor = color;

        fabricCanvas.freeDrawingColor = color.css('background-color');

        if (fabricCanvas.getActiveObject())
            if (fabricCanvas.getActiveObject().get('active'))
                if (fabricCanvas.getActiveObject().get('stroke'))
                    fabricCanvas.getActiveObject().set('stroke', color.css('background-color'));
                else
                    fabricCanvas.getActiveObject().set('fill', color.css('background-color'));

        fabricCanvas.renderAll();
    }
}

在此方法中,您可以在绘图模式下使用 fabricCanvas.freeDrawingColor 属性设置颜色。 而且你需要调用 fabricCanvas.renderAll() 方法来渲染你在顶部画布(即 fabricCanvas)以及辅助容器画布(即 HTML5 canvas元素)上做的更改。 关于画布图层的详细信息,您可以从这里找到。 如果您想要清理 fabric 画布,可以使用 fabricCanvas.clear() 方法。  

使用编辑模式选项

在本节中,我们将解释如何在示例应用程序中使用编辑模式。 当您单击"编辑"按钮时,它调用 editModeOn() 方法,并打开编辑模式。 同时这种方法将停用绘图模式、 隐藏一些选项 (如果显示)并使画布上的所有项目可选。

function editModeOn() {
    if (drawMode) {
        drawMode = false;
        fabricCanvas.isDrawingMode = false;
        $('#shape-menu').toggleClass('hidden', true);
        $('#stroke-menu').toggleClass('hidden', true);
        $('#vertex').toggleClass('hidden', true);
        $('#add-figure').removeClass('selected');
        $('#edit-figure').addClass('selected');

        for ( var i = 0; i < fabricCanvas.getObjects().length; i++) {
            fabricCanvas.item(i).selectable = true;
        }

        fabricCanvas.renderAll();
    }
}

在编辑模式下,您可以选择形状及:

  • 通过拖放将其移动,
  • 点击环绕它的控件之一并拉伸/旋转以调整其大小/旋转。
  • 通过选择菜单 '颜色',更改颜色或线条宽度(如果是描边形状)。

保存和打开文件

Fabric.js 允许您序列化整个画布到 JSON 对象。 当您单击"保存"按钮时,它会调用调用称为 stringify() 的序列化函数:

var canvasSerialized = JSON.stringify(fabricCanvas);

成功地序列化画布后,您可以在设备文件系统中的 canvasSerialized 对象。 我们使用 Filesystem Tizen 设备 API 来保存该对象,这仅是文件的一个字符串而异。 下一节解释如何做到这一点。  

使用文件系统 Tizen 设备 API

Tizen Filesystem API 提供了对设备的文件系统的访问。 若要使用 Filesystem 对象的任何功能,您必须在 config.xml 文件中声明此功能 (http://tizen.org/api/filesystem)。 有关 Filesystem Tizen 设备 API 的详细信息,您可以从这里找到。  

解析目录

将任何东西保存至文件之前,您需要指定您要操作的文件的目录。 在此示例应用程序中,我们使用称作 ‘documents’ 的根文件夹来保存我们的所有文件。 在任何目录中操作之前,必须解析该目录。 解析操作决定用程序是否可以访问该目录。 如果允许进行访问,则提供一个表示该目录的对象。 在回调函数中返回名为 dir 的文件句柄。 可以使用它对此文件执行各种操作,例如创建或删除此文件。 Tizen.filesystem.resolve() 方法采取以下参数:

  • 位置 - 文件或目录的相对路径
  • 成功回调 - 操作成功时的调用
  • 错误回调 - 出现错误时的回调(可选)
  • 模式 - 在其中应用程序请求打开文件或目录(可选)的模式;可能的值为:表示该目录 "r"(读取),或 "rw" (读和写)

这是示例应用程序如何使用此方法:

tizen.filesystem.resolve("documents", function(dir) {
    gDocumentsDir = dir;
}, onError, "rw");

传递给成功回调的唯一参数是名为 dir 的文件句柄 文件句柄可以是文件或目录。 在此案例中,它表示一个目录。 在回调函数中,我们可以将文件句柄 dir 分配给我们自己称为 gDocumentsDir 的变量,它将在示例应用程序的其他部分使用。 由于我们需要保存并覆盖该文件,我们在此案例中将文件模式设为 'rw'。  

创建和写入文件

一旦您解析了目录,并获得对目录或文件的访问,您便可以创建和写入文件。 你可以使用文件对象提供的 createFile() 方法以给定的文件名称创建文件。 在我们的示例应用程序中,您在输入字段中输入文件名,并单击"保存"按钮以创建一个文件。

var filename = $('#file-name').val();

try {
    gDocumentsDir.createFile(filename);
} catch (exc) {
    $('#save-popup').toggleClass('hidden', false);
    return;
}

如果该文件已经存在,示例应用程序便显示一个弹出窗口,并允许您更改文件名或覆盖现有的文件。 这个函数在 writeToFile() 方法中实现,如下所示:

var filename = $('#file-name').val();

function writeToFile(filename) {
    var file;

    try {
        file = gDocumentsDir.resolve(filename);
    } catch (exc) {
        console.error('Could not resolve file: ' + exc.message);
        return;
    }

    try {
        file.openStream('w', writeToStream, onError);
    } catch (exc) {
        console.error('Could not write to file: ' + exc.message);
    }
}

写入现有的文件和创建一个新文件略有不同。 您应该使用文件对象提供的 resolve() 方法解析文件。 在我们的例子中,它是 gDocumentsDir 对象。 该方法将返回一个新的文件句柄,用来使用 openStream() 方法打开该文件。 在我们的例子中,我们想要在写模式下打开它,所以我们将 'w' 传递给此方法。 成功回调函数看起来如下所示:

function writeToStream(fileStream) {
    try {
        fileStream.write(canvasSerialized);
        fileStream.close();
    } catch (exc) {
        console.error(exc.message);
    }
}

  请记住我们想要将序列化的画布(即 JSON 字符串)保存到该文件。 所以我们将字符串对象 canvasSerialized 传递给 write() 方法。 文本写入到文件后,流已经使用 close() 方法关闭。 如果发生任何错误,将调用错误回调函数 onError():

function onError(err) {
    console.error("Error: " + err.message);
}

 

读取文件

示例应用程序允许您打开包含序列化画布的文件。 并且您可以导入画布并修改对象。 在本节中,我们向您展示如何读取该文件。 我们实现一个名为 displayFileContents() 的方法来处理打开文件事件。 当您单击"打开"按钮时,它会调用此方法。 下面的代码显示了它是如何实现的。

function displayFileContents() {
    var file;

    try {
        file = gDocumentsDir.resolve($('#file-name').val());
    } catch (exc) {
        console.error('resolve() exception: ' + exc.message);
        return;
    }

    try {
        file.openStream('r', readFromStream, onError);
    } catch (exc) {
        console.error('openStream() exception:' + exc.message);
    }
}

这与我们在前面所述的 writeToFile() 方法非常相似。 一旦您从 resolve() 方法收到文件句柄,您便可以从 File 对象调用 openStream() 方法,并按 'r' 以表明您要将文件在读取模式中打开。 您还需要一个成功回调函数和错误回调函数。 我们实施的成功回调函数称为 readFromStream()。 它从 openStream() 方法获取数据流:

function readFromStream(fileStream) {
    try {
        var contents = fileStream.read(fileStream.bytesAvailable);
        fileStream.close();

        fabricCanvas.loadFromJSON(contents);

        for ( var i = 0; i < fabricCanvas.getObjects().length; i++) {
            if (drawMode)
                fabricCanvas.item(i).set('selectable', false);
            else
                fabricCanvas.item(i).set('selectable', true);
        }
    } catch (exc) {
        console.error('File reading exception: ' + exc.message);
    }
}

若要确保您从数据流中读取有效的字节,您需要使用 bytesAvailable 属性。 它返回从数据流中可读取的字节的数目。 然后方法 read () 从给定的 FileStream 读取所有字符。 一旦你完成从流中读取,您需要通过调用 close () 方法来关闭流。 下一步是解析序列化的字符串,即画布对象的 JSON 对象。 Fabric.js 提供了一种称为 loadFromJSON() 的方法来做到这一点。 注意: 如果该示例应用程序是在 '绘制模式' 中,所有对象都为不可选。

SVG 文件

您还可以用 fabric.js 框架完成另一项操作。 您还可以在画布上插入 SVG 图像。 我们的示例应用程序中的 "SVG 文件" 按钮允许您这样做。 它用三个按钮(SVG1、SVG2、SVG3)打开菜单。 每个按钮都触发称为 loadSVGFromURL() 的函数:

var svgFileName = 'someFile.svg';

fabric.loadSVGFromURL('images/' + svgFileName + '.svg', function(objects, options) {
    var loadedObject = fabric.util.groupSVGElements(objects, options);

    loadedObject.set({
        left : x,
        top : y,
        angle : 0,
        padding : 0
    });
    loadedObject.setCoords();

    fabricCanvas.add(loadedObject);
});

svgFileName 是一个字符串,表示文件的名称。 LoadSVGFromURL() 方法的第一个参数是 SVG 文件的路径。 第二个参数是成功回调函数,检索从 SVG 文件导入的元素数组。 fabric.util.groupSVGElements 用于对 SVG 文件中的元素进行分组。 setCoords() 方法设置角位置的坐标。 最后,函数 fabricCanvas.add() 将 SVG 图像绘制在画布上。  

退出应用程序

可以终止示例应用程序。 若要提供此功能,您应该:

  • 在 config.xml 文件中添加 application.read 功能。
"http://tizen.org/api/application.read" required="true"/>
  • 将“退出”按钮添加到 index.html,例如用这种方式:
"submit" id="close" value="Close" class="top-menu-button"/>
  • 将 exit 方法与退出按钮绑定
$("#close").unbind().click(function(event) {
    tizen.application.exit();
});

总结

在本文中,您学习了如何使用画布 API 和 fabric.js 在画布上绘制简单的 2D 形状、如何使用 fabric.js 框架将您的画布作为文件保存和打开,以及最后如何使用 fabric.js 在画布上插入 SVG 图像。 正如您所看到的,在 Tizen 上执行这些功能并不是很难。 我们希望这篇文章将帮助您在 Tizen web 应用程序中创建一些简单的 2D 游戏和动画。

文件附件: