tpl.js首页、文档和下载 - JavaScript 模板引擎 - 开源中国社区

mikel阅读(879)

来源: tpl.js首页、文档和下载 – JavaScript 模板引擎 – 开源中国社区

一个轻量的(仅1kb),极速的JavaScript 模板引擎。

支持预编译,支持嵌入原生JavaScript语法,忽略不存在的变量,可直接在AMD、CMD以及全局环境中使用。

使用方法:

1.引入tpl.js

1
<script type="text/javascript" src="path/to/tpl.js"></script>

或者在Sea.js和Node.js中:

1
var tpl = require('path/to/tpl');

2.HTML模板

1
2
3
4
5
6
7
8
9
10
11
12
<div id="view"></div>
<script type="text/template" id="demo">
    <p>Hi, I'm <%=author%><%=anything%></p>
    <p>I can write:
    <%
        for(var i=0; i<languages.length; i++) {
            echo( (i===0?"":", ") + languages[i] );
        }
    %>
    </p>
</script>

3.执行渲染

1
console.log( tpl('#demo', {author: "Jony", languages:["javascript", "php", "shell"]}) );

结果为:

1
2
<p>Hi, I'm Jony</p>
<p>I can write: javascript, php, shell</p>

 

在线例子:http://niceue.github.io/tpl.js/

在线文档:https://github.com/niceue/tpl.js

Chrome 中的 JavaScript 断点设置和调试技巧

mikel阅读(1252)

你是怎么调试 JavaScript 程序的?最原始的方法是用 alert() 在页面上打印内容,稍微改进一点的方法是用 console.log() 在 JavaScript 控制台上输出内容。嗯~,用这两种土办法确实解决了很多小型 JavaScript 脚本的调试问题。不过放着 Chrome 中功能越发强大的开发者工具不用实在太可惜了。本文主要介绍其中的 JavaScript 断点设置和调试功能,也就是其中的 Sources Panel(以前叫 Scripts)。如果你精通 Eclipse 中的各种 Java 调试技巧,那么这里的概念都是类似。写作本文时使用的 Chrome 版本为 25.0.1364.172。

基本环境
Chrome 断点设置 - hanguokai - 韩国恺的博客
Sources Panel 的左边是内容源,包括页面中的各种资源。其中,又分 Sources 和 Content scripts。Sources 就是页面本身包含的各种资源,它是按照页面中出现的域来组织的,这是我们要关注的。异步加载的 js 文件,在加载后也会出现在这里的。Content scripts 是 Chrome 的一种扩展程序,它是按照扩展的ID来组织的,这类扩展实际也是嵌入在页面中的资源,它们也可以读写DOM。编写、调试这类扩展的开发者才要关心它们,如果你的浏览器没安装任何扩展,那么 Content scripts 就看不到任何东西。
Sources Panel 的中间主区域用于展示左边资源文件的内容。
Sources Panel 的右边是调试功能区,最上面的一排按钮分别是暂停/继续、单步执行、单步跳入、单步跳出、禁用/启用所有断点。下面是各种具体的功能区。稍后介绍。
注意,左右两边的区域默认可能收缩在两侧没有显示出来,点击两侧的伸缩按钮 Chrome 断点设置 - hanguokai - 韩国恺的博客Chrome 断点设置 - hanguokai - 韩国恺的博客展示出来。左边区域展示出来时默认是自动收缩状态,点击旁边的 pin 按钮 Chrome 断点设置 - hanguokai - 韩国恺的博客 就不会缩回去了。
最下面还有一些功能按钮很有用。
在源代码上设置断点
通 过左边的内容源,打开对应的 JavaScript 文件,鼠标点击文件的行号就可以设置和删除断点。添加的每个断点都会出现在右侧调试区的 Breakpoints 列表中,点击列表中断点就会定位到内容区的断点上。如果你有多个文件、多个断点的话,利用 Breakpoints 列表中的断点快速定位非常方便。
Chrome 中的 JavaScript 断点设置和调试 - hanguokai - 韩国恺的博客
对于每个已添加的断点都有两种状态:激活和禁用。刚添加的断点都是激活状态,禁用状态就是保留断点但临时取消该断点功能。在 Breakpoints 列表中每个断点前面都有一个复选框,取消选中就将禁用该断点。断点位置的右键菜单中也可以禁用断点。也可以在右侧功能区上面Chrome 断点设置 - hanguokai - 韩国恺的博客按钮临时禁用所有已添加的断点,再点一下恢复原状态。
条件断点:在断点位置的右键菜单中选择“Edit Breakpoint…”可以设置触发断点的条件,就是写一个表达式,表达式为 true 时才触发断点。
查看断点的环境
调用栈(Call Stack):在断点停下来时,右侧调试区的 Call Stack 会显示当前断点所处的方法调用栈,比如有一个函数 g() 其中又调用了函数 f() ,而我在 f() 中设置了一个断点,那么我在 console 中执行函数 g() 的时候会触发断点,其调用栈显示如下:
Chrome 断点设置 - hanguokai - 韩国恺的博客

最上面是 f(),然后是 g()。调用栈中的每一层叫做一个 frame,点击每个 frame 可以跳到该 frame 的调用点上。

Chrome 断点设置 - hanguokai - 韩国恺的博客
此外,还可以在 frame 上用右键菜单重新开始执行当前 frame,也就是从该 frame 的开始处执行。比如在函数 f() 的 frame 上 Restart Frame, 断点就会跳到 f() 的开头重新执行,context 中的变量值也会还原。这样结合变量修改和编辑代码等功能,就可以在当前 frame 中反复进行调试,而不用刷新页面重新触发断点了。
查看变量
Chrome 断点设置 - hanguokai - 韩国恺的博客

Call Stack 列表的下方是 Scope Variables 列表,在这里可以查看此时局部变量和全局变量的值。

执行选择的代码
Chrome 断点设置 - hanguokai - 韩国恺的博客

在断点调试时,可以用鼠标选择想要查看的变量或表达式,然后右键菜单执行“Evaluate in Console”,就可以看到该表达式的当前的值了。

中断下次要执行的 JavaScript 语句
右侧调试区的上面的“中断/继续”按钮还有一个功能,在没有触发断点时,点一下这个按钮就会进入“准备”中断的状态,页面下一次执行 JavaScript 语句时会自动中断,比如触发了一个点击动作时会执行的代码。
临时修改 JavaScript 代码
通 常我们在调试代码时习惯:修改代码→刷新页面→重新检查,这么一个循环。但其实 Chrome 中可以临时修改 JS 文件中的内容,保存(Ctrl+S)就可以立即生效,结合 Console 等功能就可以立即重新调试了。但注意这个修改是临时的,刷新页面修改就没了。
异常时断点
在界面下方能看到Chrome 断点设置 - hanguokai - 韩国恺的博客按钮,它是设置程序运行时遇到异常时是否中断的开关。点击该按钮会在3种状态间切换:
  1. Chrome 断点设置 - hanguokai - 韩国恺的博客默认遇到异常不中断
  2. Chrome 断点设置 - hanguokai - 韩国恺的博客遇到所有异常都会中断,包括已捕获的情况
  3. Chrome 断点设置 - hanguokai - 韩国恺的博客仅在出现未捕获的异常时才中断
主要解释一下状态2和状态3的区别

try{
throw ‘a exception’;
}catch(e){
console.log(e);
}

上面 try 里面的代码会遇到异常,但是后面的 catch 代码能够捕获该异常。如果是所有异常都中断,那么代码执行到会产生异常的 throw 语句时就会自动中断;而如果是仅遇到未捕获异常才中断,那么这里就不会中断。一般我们会更关心遇到未捕获异常的情况。

在 DOM 元素上设置断点

有时候我们需要监听某个 DOM 被修改情况,而不关心是哪行代码做的修改(也可能有多处都会对其做修改)。那么我们可以直接在 DOM 上设置断点。
Chrome 断点设置 - hanguokai - 韩国恺的博客

如图所见,在元素审查的 Elements Panel 中在某个元素上右键菜单里可以设置三种不同情况的断点:

  1. 子节点修改
  2. 自身属性修改
  3. 自身节点被删除
选中之后,Sources Panel 中右侧的 DOM Breakpoints 列表中就会出现该 DOM 断点。一旦执行到要对该 DOM 做相应修改时,代码就会在那里停下来,如下图所示。
Chrome 断点设置 - hanguokai - 韩国恺的博客
XHR 断点
右侧调试区有一个 XHR Breakpoints,点击+ 并输入 URL 包含的字符串即可监听该 URL 的 Ajax 请求,输入内容就相当于 URL 的过滤器。如果什么都不填,那么就监听所有 XHR 请求。一旦 XHR 调用触发时就会在 request.send() 的地方中断。
按事件类型触发断点
右侧调试区的 Event Listener 列表,这里列出了各种可能的事件类型。勾选对应的事件类型,当触发了该类型的事件的 JavaScript 代码时就会自动中断。
调试快捷键
Chrome 断点设置 - hanguokai - 韩国恺的博客

所有开发工具中的快捷键都可以在界面右下角的设置中查到。断点调试时一般用的是 F8、F10、F11或 Shitf+F11,但在 Mac OS 上 F10 等功能键可能与系统默认的快捷键冲突。没关系,它们分别可以用 Cmd+/ 、Cmd+’、Cmd+; 、Shift+Cmd+; 代替。

//@ sourceURL 给 eval 出来的代码命名
有时候一些非常动态的代码是以字符串的形式通过 eval() 函数在当前 Javascript context 中创建出来,而不是作为一个独立的 js 文件加载的。这样你在左边的内容区就找不到这个文件,因此很难调试。其实我们只要在 eval 创建的代码末尾添加一行 “//@ sourceURL=name“ 就可以给这段代码命名(浏览器会特殊对待这种特殊形式的注释),这样它就会出现在左侧的内容区了,就好像你加载了一个指定名字的 js 文件一样,可以设置断点和调试了。下图中,我们通过 eval 执行了一段代码,并利用 sourceURL 将它命名为 dynamicScript.js ,执行后左侧内容区就出现了这个“文件”,而它的内容就是 eval 的中的内容。
Chrome 断点设置 - hanguokai - 韩国恺的博客
还可以看看这个给动态编译出来的 CoffeeScript 代码命名的示例

var coffee = CoffeeScript.compile(code.value)+ “//@ sourceURL=” + (evalName.value || “Coffeeeeeeee!”);
eval(coffee);

实际上,//@ sourceURL 不仅仅可以用在 eval 的代码中,任何 js 文件、甚至是 Javascript Console 输入的代码都可以用,效果一样!
格式化代码(Pretty Print)
Chrome 断点设置 - hanguokai - 韩国恺的博客 按钮用于把杂乱的代码重新格式化为漂亮的代码,比如一些已被压缩的 js 文件基本没法看、更没法调试。点一下格式化,再点一下就取消格式化。
Chrome 断点设置 - hanguokai - 韩国恺的博客

美化前

Chrome 断点设置 - hanguokai - 韩国恺的博客

美化后

参考资料:

angular模板加载 ----ng-template - 作业部落 Cmd Markdown 编辑阅读器

mikel阅读(1183)

Cmd Markdown 编辑阅读器,支持实时同步预览,区分写作和阅读模式,支持在线存储,分享文稿网址。

来源: angular模板加载 —-ng-template – 作业部落 Cmd Markdown 编辑阅读器

Angularjs作为mvc(或者说mvvm)框架,同样具备模板这一基本概念。

NG加载模板的顺序为 内存加载—AJAX加载。

内存加载

如果之前使用过Bootstrap 插件的ng版,即angular-ui,就会了解到这种方式的具体应用。模板本质上是字符串,把字符串直接写入内存,加载时直接从内存获取,速度会更快,有两种方式显式启用内存加载。

  • 通过使用$templateCache service来实现
  1. angular.module('myApp', [])
  2. .controller('myCtrl', ['$scope','$templateCache', function($scope,$templateCache){
  3. var tmp = '<h4>lovestory</h4>'
  4. + '<p>这是直接调用$templateCache服务获取模板文件的方式</p>'
  5. + '<a href="http://www.baidu.com">服务启用templateCache方式</a>';
  6. $templateCache.put('lovestory.html',tmp);
  7. }])

$templateCache服务put方法负责向内存写入模板内容。

  • 通过script标签引入
  1. <script type="text/ng-template" id="lovestory.html">
  2. <h4>lovestory</h4>
  3. <p>这是script标签获取模板文件的方式</p>
  4. <a href="http://www.baidu.com">标签启用templateCache方式</a>
  5. </script>

这里需要注意,type="text/ng-template"是指明这是ng模板,id属性是指实际使用模板时的一个引用,标签之间的内容才是实际的模板内容。而且,需要注意,id绝对不是URL,这个script标签绝对不会发出HTTP请求,具体讨论见最后。
实际应用模板时候,使用ID属性,即可从内存中获取对应数据。

  1. <div ng-include="'lovestory.html'" class="well"></div>

使用ng-include的时候,应该注意,id相当于一个字符串,不是ng-expression,所以不要忘了加单引号。

AJAX加载

上述的内存加载,相当于一个预定义模板,定义在client-side,不会与服务器有任何交互,适合变化频率低的模板。

当NG在内存中找不到对应模板时,就会启用AJAX请求,去拉取对应模板。假设项目入口文件地址为http://127.0.0.1/index.html;

  1. <div ng-include="'lovestory.html'" class="well"></div>

在指令中同样可以使用,templateUrl对应值

  1. angular.module('myApp', [])
  2. .directive('templateDemo', ['$log', function($log){
  3. return {
  4. restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
  5. templateUrl: 'butterfly.html',
  6. replace: true,
  7. link: function($scope, iElm, iAttrs, controller) {}
  8. }
  9. }])

内存中没有对应模板时,AJAX请求地址为http://127.0.0.1/lovestory.html, 请求成功后将对应内容写入$templateCache,在页面不进行刷新,不手动删除的情况下,写入的内容不会丢失。而且,务必记住,AJAX是有缓存控制的。。。

内存模板优点

在雅虎前端优化34条里,有一条是“合并压缩文件”。

合并压缩文件可以减小HTTP请求量,又可以减小网络传输量,对于路径依赖并不严重的JS,CSS文件完全是必备,因为各JS,CSS文件开发时分割为不同的文件,实现各自的功能需求,上线时合并即可,但是,HTML文件可以压缩,但是无法合并,因为路径依赖严重。

以我为学习Angularjs而做的个人博客练习 路由为例:

  1. angular.module('administratorApp',[])
  2. .config(function ($routeProvider,$locationProvider) {
  3. $locationProvider.html5Mode(false);
  4. $routeProvider
  5. .when('/manage', {
  6. templateUrl: 'views/manage.html',
  7. controller: 'ManageCtrl'
  8. })
  9. .when('/diary/:key', {
  10. templateUrl: 'views/diaryDetail.html',
  11. controller: 'DiaryDetailCtrl',
  12. })
  13. .when('/diary', {
  14. templateUrl: 'views/diaryList.html',
  15. controller: 'DiaryListCtrl'
  16. })
  17. .when('/publish/:key', {
  18. templateUrl: 'views/update.html',
  19. controller: 'UpdateCtrl'
  20. })
  21. .when('/publish', {
  22. templateUrl: 'views/publish.html',
  23. controller: 'PublishCtrl'
  24. })
  25. .when('/record', {
  26. templateUrl: 'views/record.html',
  27. controller: 'RecordCtrl'
  28. })
  29. .otherwise({
  30. redirectTo: '/diary'
  31. });
  32. });

六个控制器需要六个模板,六次HTTP请求加载数据量并不大的模板资源浪费严重。NG的优化方案是,通过虚拟路径取代实体路径,去除掉server-side的路径依赖。
好处就是,一个JS文件一次HTTP请求,而不是六次。坏处就是内存压力变大,PC上无所谓,开发web app(mobile)就需要注意几点。

  • 移动端内存太脆,尽量不要使用上述所说的预定义模板,因为模板会全部加载到内存中
  • AJAX请求完毕,会自动把结果放入cache里,所以需要手动控制.模板与控制器存在对应关系,可以在控制器内部加上如下代码
  1. $scope.$on('$locationChangeStart',function(){
  2. $templateCache.remove('****.html');
  3. })
  • $routeProvider的template,templateUrl可以是函数,通过函数返回值可以控制模板加载。
  • PS::本人并未涉及到移动端开发,所以此处为思考所得,而且随着手机硬件性能提升,内存不再是个困扰。

$templateCache 方法

$templateCache基于cacheFactory而来,接口保持一致,可以认为
$templateCache = $cacheFactory(‘template’);

方法 功能
put 向内存写入模板内容
get 从内存获取模板内容
remove 传入key值,删除对应模板内容
removeAll 删除所有模板内容
destroy 解除key-value对应关系,但不释放内存
info 模板缓存对象的信息

Grunt与ID属性误解

  1. module.exports = function(grunt){
  2. grunt.initConfig({
  3. html2js : {
  4. simple : {
  5. options : {
  6. base : '',
  7. module : 'templateStore'
  8. },
  9. files : [{
  10. src : ['views/*.html'],
  11. dest : 'build/scripts/templateStore.js'
  12. }]
  13. }
  14. }
  15. });
  16. grunt.loadNpmTasks('grunt-html2js');
  17. grunt.registerTask('default',['html2js']);
  18. }

这是我目前使用Grunt–html2js的配置方案,目的是将views文件夹下的所有模板文件全部放入templateStore模块中,各模板对应ID即为路径,生成的部分代码如下:

  1. angular.module("views/diaryList.html", []).run(["$templateCache", function($templateCache) {
  2. $templateCache.put("views/diaryList.html", '*******'
  3. }]);

这部分代码等效于

  1. <script type="text/ng-template" id="views/diaryList.html">
  2. ***********
  3. </script>

现在应该明白,id只是个标示,不是URL。。。。。。

AJAX缓存

涉及到部分HTTP Header 和 XHR2 的相关内容,将作为单独篇章出现。

信息交流

如有不妥之处,QQ : 491229492与我联系,会尽快修正。

如果同为Angularjs的爱好者,QQ群: Angularjs中文社区。

Angularjs标签模板加载原理 - 怀疑真爱的流浪者jason - SegmentFault

mikel阅读(790)

来源: Angularjs标签模板加载原理 – 怀疑真爱的流浪者jason – SegmentFault

前言

Angularjs提供多种模板加载方案。

  • 最基础的为通过预先声明路径的方式,通过Ajax获取。
  • 使用诸如gulp-html2js构建工具,将HTML模板转化为js文件使用。
  • 使用script标签引入。

一般实际情况下,开发时使用第一种方式,部署时采取第二种方式,不会采用第三种方式。本文简要说明一下标签引入模板。Angularjs本身支持的标签typetext/ng-template,现在来支持另一种typetext/template

相关的一篇博文:https://www.zybuluo.com/bornkiller/note/6023

代码实现

从上一篇博文已经说明,$templateCache内的模板优先级最高,所以需要使用到。angularjs本身采取将script指令化的方式来实现。

var scriptDirective = ['$templateCache', function($templateCache) {
  return {
    restrict: 'E',
    terminal: true,
    compile: function(element, attr) {
      if (attr.type == 'text/ng-template') {
        var templateUrl = attr.id,
            text = element[0].text;
        $templateCache.put(templateUrl, text);
      }
    }
  };
}];

代码非常简单,判定类型—写入模板即可。封装后完全看不到内部实现,所以才会再用个人方式实现,用以理解。

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Inline Template</title>
    <script type="text/template" id="love">
        <h3>love is color blind</h3>
        <p>why so serious about the world, behind the darkness</p>
    </script>
    <script src="libs/angular.min.js"></script>
    <script src="libs/angular-sanitize.min.js"></script>
    <script src="js/template.js"></script>
</head>
<body ng-app="template">
    <article ng-controller="TemplateCtrl">
        <div ng-bind-html="story"></div>
    </article>
</body>
</html>
angular.module('template', ['ngSanitize'])
  .run(['$document', '$templateCache', function($document, $templateCache) {
    var scripts = Array.prototype.slice.call($document[0].scripts, 0);
    scripts.forEach(function(script) {
      if (script.type === 'text/template') {
        $templateCache.put(script.id, script.innerHTML);
      }
    });
  }])
  .controller('TemplateCtrl', ['$scope', '$templateCache', '$log', function($scope, $templateCache, $log) {
    $scope.story = $templateCache.get('love');
  }]);

代码非常简单,即通过document.scripts这样接近原始的方式来获取对应标签,然后将标签内部的内容写入$templateCache即可。

预览地址:http://120.24.59.102/inline.html

WeEngine源码分析(1) - 余璜的技术博客 - 博客频道 - CSDN.NET

mikel阅读(1162)

我以前一直自学linux,但是工作中一直没用过,进入诺西干了三年半,所有产品都是在linux上面运行和开发,终于有机会彻底接触linux了,以前对linux模糊的理解,在一个又一个项目中磨练,当入门Linux后发现,其实linux并不是很难,不过还是有点技巧,根据我的经验和知识总结下,供大家参考和学习.1.第一个误区是文件系统的认识,中国学计算机基本上都是从windows开始的,一到linux

来源: WeEngine源码分析(1) – 余璜的技术博客 – 博客频道 – CSDN.NET

这是关于WeEngine的第一篇文章。欲善其事,先利其器,这篇文章先不做源码分析,而是先把一些辅助的结构建立起来。

一,日志系统

  1. data/config.php
  2. $config[‘setting’][‘development’] = 1;

有了日志之后,调试将会事半功倍。打印日志的方法:

  1. source/modules/engine.php
  2. public static function logging($level = ‘info’, $message = ”)
  3. 其中level可以是任意字符串。推荐使用error,warn,info,trace几个字符表示不同级别。例如:
  4. WeUtility::logging(‘warn’, ‘Request Failed’);

日志输出样例:

  1. 2014-04-03 19:57:30 trace :
  2. ————
  3. Array:
  4. from : otP7TjgEgLrOKzp9TGuMx6x;
  5. to : gh_765067e402 ;
  6. time : 1396526250 ;
  7. type : CLICK ;
  8. event : CLICK ;
  9. tousername : gh_765067e402 ;
  10. fromusername : otP7TjgEgLrOKzp9TGuMx6x ;
  11. createtime : 1396526250 ;
  12. msgtype : event ;
  13. eventkey : huodongzhaoji ;
  14. 2014-04-03 19:57:30 params :
  15. ————
  16. Array:
  17. module : default ;
  18. rule : -1 ;
  19. 2014-04-03 19:57:30 response :
  20. ————
  21. Array:
  22. FromUserName : gh_765067e402 ;
  23. ToUserName : otP7TjgEgLrOKzp9TGuMx6x ;
  24. MsgType : news ;
  25. ArticleCount : 2 ;
  26. Articles : Array ;

调试时,可以用下面的命令持续观察日志最新输出:

  1. tailf data/logs/xxxxxxx.log

 

二,全局变量

  1. source/bootstarp.api.inc.php
  2. global $_W;

此乃WeEngine精华所在。下面分述各个字段。

  1. $_W[‘account’]
  1. $SQL = “SELECT * FROM ” . tablename(‘wechats’) . ” WHERE `hash`=:hash LIMIT 1″;
  2. $_W[‘account’] = pdo_fetch($SQL, array(‘:hash’ => $_GPC[‘hash’]));
  3. $_W[‘account’][‘default_message’] = iunserializer($_W[‘account’][‘default_message’]);
  4. $_W[‘account’][‘access_token’] = iunserializer($_W[‘account’][‘access_token’]);
  5. $_W[‘account’][‘payment’] = iunserializer($_W[‘account’][‘payment’]);

wechats表记录的是公众账号信息,如下图所示。WeEngine比较强大的地方之一是可以管理多个公众号。每个公众号都有一个唯一标示id:weid。下例中只存了一个公众号,weid编号为1.


从上面的代码中可知$_W[‘account’]中保存了公众号的所有信息。其中default_message、access_token、payment三个参数是以序列化的方式存入到wechats表中,所以读出的时候需要反序列化一下。
对于微信和易信分别使用了不同的序列化类,见source/class/weixin.account.class.php fetch_token():
  1. if (empty($this->account[‘key’]) || empty($this->account[‘secret’])) {
  2.     message(‘请填写公众号的appid及appsecret, (需要你的号码为微信服务号)!’, create_url(‘account/post’array(‘id’ => $this->account[‘weid’])), ‘error’);
  3. }
  4. $url = “https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->account[‘key’]}&secret={$this->account[‘secret’]}”;
  5. $content = ihttp_get($url);
  6. if(is_error($content)) {
  7.     message(‘获取微信公众号授权失败, 请稍后重试!错误详情: ‘ . $content[‘message’]);
  8. }
  9. $token = @json_decode($content[‘content’], true);
  10. if(empty($token) || !is_array($token) || empty($token[‘access_token’]) || empty($token[‘expires_in’])) {
  11.     message(‘获取微信公众号授权失败, 请稍后重试! 公众平台返回原始数据为: <br />’ . $content[‘meta’]);
  12. }
  13. $record = array();
  14. $record[‘token’] = $token[‘access_token’];
  15. $record[‘expire’] = TIMESTAMP + $token[‘expires_in’];
  16. $row = array();
  17. // 序列化token,并更新到数据库中
  18. $row[‘access_token’] = iserializer($record);
  19. pdo_update(‘wechats’$rowarray(‘weid’ => $this->account[‘weid’]));
  20. return $record[‘token’];
还有几个变量很重要,源码中会经常遇到,用到的时候再详细介绍。
  1. $_W[‘account’][‘modules’] = array();
  1. $_W[‘setting’] = (array)cache_load(“setting”);
  1. $_W[‘config’]

还有个特别常见的:$_W[‘fans’],详见source/bootstarp.mobile.inc.php。首先从cookie中读出 __msess变量,然后就可以计算出fans的唯一id了。浏览器关闭后这个cookie变量就会自动消失。即使取消了对微信号的关注,只要没有关闭过 微信,则一些需要权限的URL还是可以在一段时间内打开。

We7官方关于全局变量有更详细的描述,详见这里

参考链接

android webview设置自适应任意大小的pc网页 - 心有灵犀鬼才心 - 博客频道 - CSDN.NET

mikel阅读(2074)

1. 简介 直到4g时代,流量依然是宝贵的东西。而移动网络传输中,最占流量的一种载体:图片,成为了我们移动开发者不得不关注的一个问题。 我们关注的问题,无非是图片体积和质量如何达到一个比较和谐的平衡,希望得到质量不错的图片同时体积还不能太大。 走在时代前列的谷歌给出了一个不错的答案——WebP。 WebP是一种图片文件格式,在相同的压缩指标下,webp的有损压缩能比jpg小 25-

来源: android webview设置自适应任意大小的pc网页 – 心有灵犀鬼才心 – 博客频道 – CSDN.NET

  1.               WebSettings webSettings = view.getSettings();
  2. webSettings.setJavaScriptEnabled(true);
  3. // User settings
  4. webSettings.setJavaScriptEnabled(true);
  5. webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
  6. webSettings.setUseWideViewPort(true);//关键点
  7. webSettings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN);
  8. webSettings.setDisplayZoomControls(false);
  9. webSettings.setJavaScriptEnabled(true); // 设置支持javascript脚本
  10. webSettings.setAllowFileAccess(true); // 允许访问文件
  11. webSettings.setBuiltInZoomControls(true); // 设置显示缩放按钮
  12. webSettings.setSupportZoom(true); // 支持缩放
  13. webSettings.setLoadWithOverviewMode(true);
  14. DisplayMetrics metrics = new DisplayMetrics();
  15.   getWindowManager().getDefaultDisplay().getMetrics(metrics);
  16.   int mDensity = metrics.densityDpi;
  17.   Log.d(“maomao”, “densityDpi = ” + mDensity);
  18.   if (mDensity == 240) {
  19.    webSettings.setDefaultZoom(ZoomDensity.FAR);
  20.   } else if (mDensity == 160) {
  21.      webSettings.setDefaultZoom(ZoomDensity.MEDIUM);
  22.   } else if(mDensity == 120) {
  23.    webSettings.setDefaultZoom(ZoomDensity.CLOSE);
  24.   }else if(mDensity == DisplayMetrics.DENSITY_XHIGH){
  25.    webSettings.setDefaultZoom(ZoomDensity.FAR);
  26.   }else if (mDensity == DisplayMetrics.DENSITY_TV){
  27.    webSettings.setDefaultZoom(ZoomDensity.FAR);
  28.   }else{
  29.       webSettings.setDefaultZoom(ZoomDensity.MEDIUM);
  30.   }
  31. /**
  32.  * 用WebView显示图片,可使用这个参数 设置网页布局类型: 1、LayoutAlgorithm.NARROW_COLUMNS :
  33.  * 适应内容大小 2、LayoutAlgorithm.SINGLE_COLUMN:适应屏幕,内容将自动缩放
  34.  */
  35. webSettings.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS);

其中关键是以下设置属性

webSettings.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS);
webSettings.setUseWideViewPort(true);

这样的话如果你的PC网页里面没有设置 meta标签 viewport的缩放设置也没有关系。

常用的 viewport meta 如下:

1 <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />

 

1、width : 控制viewport的大小,可以指定一个值,如600, 或者特殊的值,如device-width为设备的宽度(单位为缩放为100%的CSS的像素)

2、height : 和width相对应,指定高度

3、initial-scale : 初始缩放比例,页面第一次加载时的缩放比例

4、maximum-scale : 允许用户缩放到的最大比例,范围从0到10.0

5、minimum-scale : 允许用户缩放到的最小比例,范围从0到10.0

6、user-scalable : 用户是否可以手动缩放,值可以是:①yes、 true允许用户缩放;②no、false不允许用户缩放

Android 各个版本WebView - typename 记录点滴 - 博客频道 - CSDN.NET

mikel阅读(1375)

1、 创建maven项目NewàProjectàMaven Projectà不勾选 Create a simple projectà选择maven-archetype-quickstartà填写Group ID,artifact id至此。Maven项目创建完成,目录结果如下。2、 加入spring 依赖打开pom.xml文件,最后一个标签

来源: Android 各个版本WebView – typename 记录点滴 – 博客频道 – CSDN.NET

转载请注明出处   http://blog.csdn.net/typename/ powered by miechal zhao : miechalzhao@gmail.com

前言:

根据Google公布的Android 各个系统版本市场占有率(Google Android dashboards), Android 4.0及其以上系统将近90%左右,发展趋势必将是未来市面上几乎是Android 4.0以上系统。本文主要关注Android 4.0及以上系统WebView的实现,从Android WebView实现的Framework层大致可以分为三段Android 4.0系列,Android 4.1—4.3系列,Android 4.4及其以上系列。

WebView差异

WebView是Android系统提供能显示网页的系统控件,它是一个特殊的View,同时它也是一个ViewGroup可以有很多其他子View。在Android 4.4以下(不包含4.4)系统WebView底层实现是采用WebKit(http://www.webkit.org/)内核,而在Android 4.4及其以上Google 采用了chromium(http://www.chromium.org/) 作为系统WebView的底层内核支持。在这一变化中Android 提供的WebView相关API并没有发生大变化,在4.4上也兼容低版本的API并且引进了少部分API。这里简单介绍下基于Chromium 的Webview和基于Webkit webview的差异,基于Chromium Webview提供更广的HTML5,CSS3,JavaScript支持,在目前最新Android 系统版本5.0上基于chromium 37,Webview提供绝大多数的HTML5特性支持。Webkit JavaScript引起采用WebCore JavaScript 在Android 4.4上换成了V8能直接提升JavaScript性能。另外Chromium 支持远程调试(Chrome DevTools)。

WebKit for WebView VS Chromium for WebView性能比对(测试环境 小米2. CM Browser. Android 4.1.1 VS 4.4.3)

Webkit for Webview Chromium for Webview 备注
HTML5 278 434 http://html5test.com/
远程调试 不支持 支持 Android 4.4及以上支持
内存占用 相差20-30M左右
WebAudio 不支持 支持 Android 5.0及以上支持
WebGL 不支持 支持 Android 5.0及以上支持
WebRTC 不支持 支持 Android 5.0及以上支持

 

Android 4.0 WebView结构

Android WebView API层主要提供给我们应用程序的接口,为了兼容向下版本Android在高版本中也是对这一层的API进行支持,因此如果底层发生变化,这些API接口 层也不会发生太大变化。Android 平台不仅提供应用层编程接口也提供native层编程。下面介绍上图中的三个部分:
1)Android Framework:Android WebView是个特殊控件实现的支持需要Framework的代码主要在./frameworks/base/core/java/android/webkit目录下,在Android 4.0实现主要是在WebViewCore.java,BrowserFrame.java等文件。
2) Android JNI:需要有Native代码支持,因此需要有JNI层实现,Android WebView 4.0的JNI层实现WebView相关代码在./external/webkit/Source/WebKit/android/jni/目录下,这一 层起到承上启下的作用,链接Framework层以及WebKit层的桥梁,比如相关的一些实现在 WebviewCore.cpp,WebCoreFrameBridge.cpp等。
3) WebKit: WebKit内核,其核心主要是解析W3C标准以及渲染排版网页,他是一个跨平台的内核引擎,那么需要支持各个平台,需要我们的平台实现层,在 Android 4.0系统这一部分相关代码主要在./external/webkit/Source/WebKit/android/WebCoreSupport/目 录下,比如FrameLoaderClientAndroid.cpp,ChromeClientAndroid.cpp,这一层负责WebCore与系 统平台的桥接,具体在不同平台会有不同的实现。实现网页的解析排版及渲染由WebCore来实现在Android 4.0源码当中代码位于./external/webkit/Source/WebCore/下,下面有WebCore实现的各个模块功能支持的相关代 码,比如页面视图部分在page目录的chrome.cpp,比如加载页面需要的资源的loader中得FrameLoader.cpp等,这里不在继续 深入详解,有兴趣的朋友可以下载Android 4.0源码阅读。

Android 4.1–4.3 WebView结构

Android 4.1–4.3版本WebView内核实现还是基于WebKit,但在WebView的Framework层发生了变化,引入了工厂模式,目地是为了将 内核与上层API接口分离开来,分离的意义不仅仅是抽象接口,更重要的是将来能替换内核部分的实现。 在4.1–4.3这一系列版本native结构基本与4.0版本相同,下图呈现新的变化:

Android 4.0–4.3 渲染

尽 管之前4.0,与4.1–4.3是在不同的结构系列,其两者之间的差异主要是集中的Framework上的变化,这种变化更多体现在Framework 层结构上的变化,WebKit内核极其在Android上的表现机制并没有发生很大变化,他们的渲染机制是相同的。下面介绍Android 4.0–4.3的渲染机制:

在Android 4.0上已经默认开启硬件加速,因此WebView的渲染默认是基于硬件渲染的,通过本人分析其在WebView被隐藏的那一帧是采用软件渲染,目的是减 少硬件占用,让其他UI能及时的响应。在硬件渲染情况下WebView通过onDraw方法传递Canvas 并将其转行为HardwareCanvas ,并生成native的 DrawGLFunction指针,通知native做渲染。在软件模式下,WebView通过传递的Canvas 通知内核webkitDraw将内核的一帧生成picture传输到Canvas中,执行Canvas draw bitmap。

Android 4.4 WebView结构

在Android 4.4系统上 Google已经将系统默认的Webkit内核替换成自己的开源项目chromium,通过之前的版本分析,我们可以看到Android 对WebView的Framework 结构进行调整使其更抽象,更重要的目的还是集成自己的开源chromium。下面我们来看看WebView的结构发生了什么样的变化:
目录:
./frameworks/base/core/java/android/webkit
./frameworks/webview/chromium/java/com/android/webview/chromium
./external/chromium_org/android_webview
./external/chromium_org/content
为 了将chromium项目集成到Android 中,chromium项目抽象出Android webview这一层,之前的接口抽离这时候已经变得很明显,Android Webview基于chromium content API这一层,第三方浏览器厂商也可以采用这种方式,目前所了解的厂商有Opera使用这种方式。Android 4.4WebView的渲染核心目前也没有发生太大变化,还是基于WebView的Canvas,将Chromium composit 结构绘制到WebView Canvas上。接入chromium内核,WebView浏览性能大幅度提升,但是和chrome for Android还是有些不同,主要体现在一下几点:
1. chrome浏览器是多进程架构,Chromium for Android Webview 是单进程架构。
2. chrome浏览器 内存占用比 Android WebView大的多。
3. chrome支持更多的HTML5 feature。

Android WebView展望:

Chromium项目编译”android_webview_apk“ 目前实现是基于Android SurfaceView,其渲染性能高于Android WebView的Canvas,历史遗留问题以及Android 系统WebView的作用特点,这一块随着Chromium 和 Android项目的整合,相信值得大家期待将来的Android WebView 的渲染性能会再次大幅提升。

本博客会持续更新Android WebView后续版本的变化,敬请关注 谢谢!

Android 5.0 Lollipop WebView

Lollipop 版本中WebView的内核实现采用Chromium 37版本,这个版本带来更多的安全性和稳定性。这个版本解决Android 4.4版本网页当中请求访问打开本地文件选择器问题,引入新的回调接口,onShowFileChooser方法,需要此功能的可以在5.0上接上这个回 调接口,并实现功能。另外这个版本提供安全许可给用户选择,当网页需要访问特殊资源时,会通知我们的应用程序,请求允许,回调接口为 onPermissionRequest。之前我们也提到这个版本使得WebView默认支持WebAudio,WebGL,WebRTC等标准。
另 外Google Android 还将webview做为一个能动态更新的app,能不更新Android版本情况下,更新WebView内核。Android 5.0 Webview默认提供减少内存占用支持,并且智能选择需要绘制的HTML document部门来提供性能。 当然开发者可以在自己应用程序需要时关闭这个选项(enableSlowWholeDocumentDraw)。

参考资料:

https://source.android.com/ Android 4.0—5.0

http://developer.android.com/reference/android/webkit/WebView.html

mysql创建定时器(event),查看定时器,打开定时器,设置定时器时间_Raining_新浪博客

mikel阅读(1166)

mySQL创建定时器(event),查看定时器,打开定时器,设置定时器时间_Raining_新浪博客,Raining,

来源: mysql创建定时器(event),查看定时器,打开定时器,设置定时器时间_Raining_新浪博客

    由于项目需要创建定时器(evevt),所以就百度了一下,发现基本都是来源于一个模板,有些功能还不全,现在自己总结一下。

注:mySQL版本是从5.1开始才支持event的。如果你的版本低于5.1就先升级版本吧。

查看版本的方法有很多,这里只给大家提供一个    select version();

1.查看是否开启evevt与开启evevt。

1.1、MySQL evevt功能默认是关闭的,可以使用下面的语句来看evevt的状态,如果是OFF或者0,表示是关闭的。

              show VARIABLES LIKE '%sche%';
1.2、开启evevt功能
            SET GLOBAL event_scheduler = 1;
2.创建定时器的过程
2.1、创建测试表test
drop table if exists test;
create table test
(
id int(11) not null auto_increment primary key,
time datetime not null
) engine=innodb default charset=utf8;
2.2、创建evevt要调用的存储过程test_proce
delimiter //
drop procedure if exists test_proce//
create procedure test_proce()
begin
insert into test(time) values(now());
end//
delimiter ;
2.3、开启evevt(要使定时起作用,MySQL的常量GLOBAL event_scheduler必须为on或者是1)
执行show variables like 'event_scheduler';查看evevt是否开启;
若没开启执行set global event_scheduler='on';
2.4、创建事件test_event(其作用:每隔一秒自动调用test_proce()存储过程)
drop event if exists test_event;
create event test_event
on schedule every 1 second
on completion preserve disable
do call test_proce();
2.5、开启事件test_event
alter event test_event on completion preserve enable;
2.6、关闭事件test_event
alter event test_event on completion preserve disable;
2.7、查看表test
select * from test;
 
3.查看自己创建的event
如果要查看更加详细的信息,你需要root用户的授权,如果是你自己的数据库你可以用下面语句查看
select * from  mysql.event;
下面的我的evevt的查看结果
mysql创建定时器(event),查看定时器,打开定时器,设置定时器时间

4.event的时间设置

设置event很简单,但是麻烦的是如何设置执行的时间,网上找了一些,自己总结了一下。
先看语句,如下面这个

     CREATE EVENT test_event ON SCHEDULE EVERY 1 DAY STARTS '2012-09-24 00:00:00'
     ON COMPLETION PRESERVE ENABLE DO CALL test_procedure();
EVERY 后面的是时间间隔,可以选 1 second,3 minute,5 hour,9 day,1 month,1 quarter(季度),1 year 
从2013年1月13号0点开始,每天运行一次
ON SCHEDULE EVERY 1 DAY STARTS '2013-01-13 00:00:00'
从现在开始每隔九天定时执行
ON SCHEDULE EVERY 9 DAY STARTS NOW() 
每个月的一号凌晨1 点执行
on schedule every 1 month starts date_add(date_add(date_sub(curdate(),interval day(curdate())-1 day),interval 1 month),interval 1 hour);
每个季度一号的凌晨1点执行
on schedule every 1 quarter starts date_add(date_add(date(concat(year(curdate()),'-',elt(quarter(curdate()),1,4,7,10),'-',1)),interval 1 quarter),interval 1 hour);
每年1月1号凌晨1点执行
on schedule every 1 quarter starts date_add(date_add(date(concat(year(curdate()),'-',elt(quarter(curdate()),1,4,7,10),'-',1)),interval 1 quarter),interval 1 hour);
 
其他的 就大家自由组合了。。
 
 
附录
 
1>使用权限

单独使用event调用SQL语句时,查看和创建需要用户具有event权限,调用该SQL语句时,需要用户具有执行该SQL的权限。Event权限的设置保存在mysql.user表和mysql.db表的Event_priv字段中。

当event和procedure配合使用的时候,查看和创建存储过程需要用户具有create routine权限,调用存储过程执行时需要使用excute权限,存储过程调用具体的SQL语句时,需要用户具有执行该SQL的权限。

查看EVENT命令有如下几种:

(1)查询mysql.event表;

(2)通过SHOW EVENTS命令;

(3)通过SHOW FULL EVENTS命令;

(4)通过查询information_schema.events表

(5)SHOW CREATE EVENT。

总之,event的使用频率较低建议使用root用户进行创建和维护。

2>CREATE EVENT 的语法

CREATE EVENT

[IF NOT EXISTS] ————————————————*标注1

event_name —————————————————–*标注2

ON SCHEDULE schedule ——————————————-*标注3 

[ON COMPLETION [NOT] PRESERVE] ———————————*标注4

[ENABLE | DISABLE] ———————————————*标注5 

[COMMENT ‘comment’] ——————————————–*标注6 

DO sql_statement ;———————————————-*标注7

说明:

标注1:[IF NOT EXISTS]

      使用IF NOT EXISTS,只有在同名event不存在时才创建,否则忽略。建议不使用以保证event创建成功。

标注2:event_name

     名称最大长度可以是64个字节。名字必须是当前Dateabase中唯一的,同一个数据库不能有同名的event。

     使用event常见的工作是创建表、插入数据、删除数据、清空表、删除表。

     为了避免命名规范带来的不便,最好让事件名称具有描述整个事件的能力。建议命名规则如下为:

     动作名称_(INTO/FROM_)  表名_TIME,例如:

                                              1.每月创建(清空/删除)fans表: 

                                                                        create(truncate/drop)_table_fans_month;

                                              2.每天从fans表插入(删除)数据: 

                                                                        insert(delete)_into(from)_fans_day;

标注3:ON SCHEDULE

      ON SCHEDULE 计划任务,有两种设定计划任务的方式:

      1. AT 时间戳,用来完成单次的计划任务。

      2. EVERY 时间(单位)的数量时间单位[STARTS 时间戳] [ENDS时间戳],用来完成重复的计划任务。

      在两种计划任务中,时间戳可以是任意的TIMESTAMP 和DATETIME 数据类型,时间戳需要大于当前时间。

      在重复的计划任务中,时间(单位)的数量可以是任意非空(Not Null)的整数式.

      时间单位是关键词:YEAR,MONTH,DAY,HOUR,MINUTE 或者SECOND。

      提示: 其他的时间单位也是合法的如:

        QUARTER, WEEK,YEAR_MONTH,DAY_HOUR,DAY_MINUTE,DAY_SECOND,HOUR_MINUTE,HOUR_SECOND, MINUTE_SECOND

                 不建议使用上一行这些不标准的时间单位。

标注4: [ON COMPLETION [NOT] PRESERVE]

       ON COMPLETION参数表示”当这个事件不会再发生的时候”,即当单次计划任务执行完毕后或当重复性的计划任务执行到了ENDS阶段。而PRESERVE的作用是使事件在执行完毕后不会被Drop掉,建议使用该参数,以便于查看EVENT具体信息。

标注5:[ENABLE | DISABLE]

      参数Enable和Disable表示设定事件的状态。Enable表示系统将执行这个事件。Disable表示系统不执行该事件。

        可以用如下命令关闭或开启事件:

                                    ALTER EVENT event_name  ENABLE/DISABLE

标注6:[COMMENT ‘comment’]

       注释会出现在元数据中,它存储在information_schema表的COMMENT列,最大长度为64个字节。

       ‘comment’表示将注释内容放在单引号之间,建议使用注释以表达更全面的信息。

标注7: DO sql_statement

       DO sql_statement字段表示该event需要执行的SQL语句或存储过程。

       这里的SQL语句可以是复合语句,例如:

                                          BEGIN

                                          CREATE TABLE test1;//创建表(需要测试一下)

                                          DROP TABLE test2;//删除表

                                          CALL proc_test1();//调用存储过程

                                          END

使用BEGIN和END标识符将复合SQL语句按照执行顺序放在之间。当然SQL语句是有限制的,对它的限制跟函数Function和触发器Trigger 中对SQL语句的限制是一样的,如果你在函数Function 和触发器Trigger 中不能使用某些SQL,同样的在EVENT中也不能使用。明确的来说有下面几个:

                         LOCK TABLES

                         UNLOCK TABLES

                         CREATE EVEN

                         ALTER EVENT

                         LOAD DATA

3>执行逻辑

For (已建立事件each event that has been created)

If (事件的状态非DISABLE)

And (当前时间在ENDS时间之前)

And (当前时间在STARTS时间之后)

And (在上次执行后经过的时间)

And (没有被执行)

Then:

建立一个新的线程

传递事件的SQL语句给新的线程

(该线程在执行完毕后会自动关闭)

4>修改事件

使用ALTER EVENT 来修改事件,具体的ALTER语法如下,与创建事件的语法类似:

ALTER EVENT   event_name

ON SCHEDULE schedule

[RENAME TO new_event_name]

[ON COMPLETION [NOT] PRESERVE]

[ENABLE | DISABLE]

[COMMENT ‘comment’]

DO sql_statement;

5>删除事件

EVENT使用DROP EVENT语句来删除已经创建的事件,语法如下:

DROP EVENT  [IF EXISTS]  event_name;

但当一个事件正在运行中时,删除该事件不会导致事件停止,事件会执行到完毕为止。使用DROP USER和DROP DATABASE 语句同时会将包含其中的事件删除。

投影矩阵的推导(Deriving Projection Matrices) - 有你而精彩 - 博客频道 - CSDN.NET

mikel阅读(1145)

JBPM4.3总结五博客分类:Jbpm4.3JSPJBPMXML4 合同审批实例1.画流程代码:

来源: 投影矩阵的推导(Deriving Projection Matrices) – 有你而精彩 – 博客频道 – CSDN.NET

本文乃<投影矩阵的推导>译文,原文地址为:

http://www.codeguru.com/cpp/misc/misc/math/article.php/c10123__1/Deriving-Projection-Matrices.htm,由于本人能力有限,有译的不明白的地方大家可以参考原文,谢谢^-^!

译者: 流星上的潴

如需转载,请注明出处,感谢!

 

在3D图形程序的基本矩阵变换中,投影矩阵是其中比较复杂的。平移和缩放浏览一下就能理解,旋转矩阵只要掌握了三角函数知识也可以理解,但投影矩阵有点棘 手。如果你曾经看过投影矩阵,你会发现你的常识不足以告诉你它是怎么来的。而且,我在网上还未看到许多关于如何推导投影矩阵的教程资源。本文的话题就是如 何推导投影矩阵。

对于刚刚开始接触3D图形的人,我应该指出,理解投影矩阵如何推导可能是我们对于数学的好奇心,它不是必须的。你可以只用公式,并且如果你用像 Direct3D那样的图形API,你甚至都不需要使用公式,图形API会为你构建一个投影矩阵。所以,如果本文看起来有点难,不要害怕。只要你理解了投 影矩阵做了什么,你没必要在你不想的情况下关注它是怎么做的。本文是给那些想了解更多的程序员的。

        概述: 什么是投影?

计算机显示器是一个二维表面,所以如果你想显示三维图像,你需要一种方法把3D几何体转换成一种可作为二维图像渲染的形式。那也正是投影做的。拿一个简单 的例子来说,一种把3D对象投影到2D表面的方法是简单的把每个坐标点的z坐标丢弃。对立方体来说,看上去可能像图1:

图1: 通过丢弃Z坐标投影到XY平面

        当然,这过于简单,并且在大多数情况下不是特别有用。首先,根本不会投影到一个平面上;相反,投影公式将变换你的几何体到一个新的空间体中,称为规范视域 体(canonical view volume),规范视域体的精确坐标可能在不同的图形API之间互不相同,但作为讨论起见,把它认为是从(-1, -1, 0)延伸至(1, 1, 1)的盒子,这也是Direct3D中使用的。一旦所有顶点被映射到规范视域体,只有它们的x和y坐标被用于映射到屏幕上。这并不代表z坐标是无用的,它 通常被深度缓冲用于可见度测试。这就是为什么变换到一个新的空间体中,而不是投影到一个平面上。

注意,图1描述的是左手坐标系,摄像机俯视z轴正方向,y轴朝上并且x轴朝右。这是Direct3D中使用的坐标系,本文中我都将使用该坐标系。对于右手 坐标系系统来说,在计算方面没有明显差异,在规范视域体方面有一点区别,所以一切讨论仍将适用即使你的图形API使用与Direct3D不同的规定。

现在,可以进入实际的投影变换了。有许多投影方法,我将介绍最常见的2种:正交和透视。

 

        正交投影(Orthographic Projection)

正交投影,之所以这么称呼是因为所有的投影线都与最终的绘图表面垂直,是一种相对简单的投影技术。视域体,也就是包含所有你想显示的几何体的可视空间——是一个将被变换到规范视域体的轴对齐盒子,见图2:

图2: 正交投影

        正如你看见的,视域体由6个面定义:

因为视域体和规范视域体都是轴对齐盒子,这种类型的投影没有距离更正。最终的结果是,事实上,很像图1那样每个坐标点只是丢弃了z坐标。对象在3D空 间中的大小和在投影中的大小相同,即使一个对象比另一个对象距离摄像机远很多。在3D空间中平行的直线在最终的图像上也是平行的。使用这种类型的投影将出 现一些问题像第一人称射击游戏——试想一下在不知道任何东西有多远的情况下玩!但它也有它的用处。你可能在格子游戏中使用它,例如,特别是摄像机被绑定在 一个固定角度的一款格子游戏中,图3显示了1个简单的例子:

图3: 正交投影的一个简单例子

        所以,事不宜迟,现在开始弄清楚它是如何工作的。最简单的方法可能是3个坐标轴分开考虑,并且计算如何沿着每个坐标轴将点从视域体映射到规范视域体。从x轴开始,视域体中的点的x坐标范围在[l, r],想把它变换到范围在[-1, 1]:

现在,准备把范围缩小到我们期望的,各项减去l,这样,最左边的项变为0。另一种可能考虑的做法是平移范围使其以0为中心,而不是一端为0,但现在这种方式代数式更整洁,所以为了可读性起见我将以现在这种方式做:

现在,范围的一端是0,你可以缩小到期望的大小。你期望x值的范围是2个单位宽,从1到-1,所以把各项乘以2/(r-l)。注意r-l是视域体的宽度,因此始终是一个正数,所以不用担心不等号会改变方向:

下一步,各项减去1就产生了我们期望的范围[-1,1]:

基本代数允许我们将中间项写成一个单一的分数:

最后,把中间项分成两部分使它形如px+q的形式,我们需要把项组织成这种形式这样我们推导的公式就可以简单的转换成矩阵形式:

这个不等式的中间项告诉了我们把x转换到规范视域体的公式:

获取y的变换公式的步骤是完全一样的——只要用y替代x,用t替代r,用b替代l——所以这里不重复它们了,只是给出结果:

最后,需要推倒z的变换公式。z的推导有点不同,因为需要把z映射到范围[0, 1]而不是[-1, 1],但看上去很相似。z坐标最开始在范围[n,f]:

把各项减去n,这样的话范围的下限就变为了0:

现在剩余要做的就是除以f-n,这样就产生了最终的范围[0,1]。和前面相同,注意f-n是视域体的深度所以绝对不会为负:

最后,把它分成两部分使它形如px+q的形式:

这样便给出了z的变换公式

现在,可以准备写正交投影矩阵了。总结到目前为止的工作,推导了3个投影公式:

如果写成矩阵形式,就得到了:

就是这样!Direct3D提供了D3DXMatrixOrthoOffCenterLH()(what a mouthful!)方法构造一个和这个公式相同的正交投影矩阵;你可以在DirectX文档中找到。方法名中的”LH”代表了你正在使用左手坐标系。但 是,究竟”OffCenter”的意思是什么呢?

这一问题的答案引导你到一个正交投影矩阵的简化形式。考虑几点: 首先,在可见空间中,摄像机定位在原点并且沿着z轴方向观看。第二,你通常希望你的视野在左右方向上延伸的同样远,并且在z轴的上下方向上也延伸的同样 远。如果是这样的情况,那么z轴正好直接穿过你视域体的的中心,所以得到了r = -l并且t = -b。换句话说,你可以把r, l, t和b一起忘掉,简单的把视域体定义为1个宽度w和1个高度h,以及裁剪面f和n。如果你在正交投影矩阵中应用上面说的,那么你将得到这个相当简化的版 本:

这个公式是Direct3D中D3DXMatrixOrthoLH()方法的实现。你几乎可以一直使用这个矩阵替代上面那个你推导的更通用的”OffCenter”版本,除非你用投影做些奇怪的事情。

在完成这部分之前还有一点。它启发我们注意到这个矩阵可以用两个简单的变换串联替代:平移其次是缩放。如果你思考几何的话这对你是有意义的,因为所有 你在正交投影中做的就是从一个轴对齐盒子转向另一个轴对齐盒子;视域体不改变它的形状,只改变它的位置和大小。具体来说,有:

这种投影方式可能更直观一点因为它让你更容易想象发生了什么。首先,视域体沿着z轴平移使它的近平面和原点重合;然后,应用一个缩放把它缩小到规范视 域体大小。很容易理解吧,对不对?一个偏离中心(OffCenter)的正交投影矩阵也可以用一个变换和一个缩放代替,它和上面的结果很相似所以我在这里 不列出了。

上面就是正交投影,现在可以去接触一些更有挑战性的东西了。

 

        透视投影(Perspective Projection)

透视投影是稍复杂的一种投影方法,并且用的越来越平凡,因为它创造了距离感,因此会生成更逼真的图像。从几何上说,这种方法与正交投影不同的地方在于透视投影的视域体是一个平截头体——也就是,一个截断的金字塔,而不是一个轴对称盒子。见图4:

图4: 透视投影

        正如你所看见的,视域体的近平面从(l,b, n)延伸至(r, t, n)。远平面范围是从原点发射穿过近平面四个点的射线直至与平面z=f相交。由于视域体从原点进一步延伸,它变得越来越宽大;同时你将这个形状变换到规范 视域体盒子;视域体的远端比视域体的近端压缩的更厉害。因此,视域体远端的物体会变得更小,这就给了你距离感。

由于空间体形状的这种变换,透视投影不能像正交投影那样简单的表达为一个平移和一个缩放。你必须制定一些不同的东西。但是,这并不意味着你在正交投影 上做的工作是无用的。一个方便的解决数学问题的方法是把问题减少到你已经知道怎么解决的那一个。所以,这就是你在这里可以做的。上一次,你一次检查一个坐 标,但这次,你将把x和y坐标合起来一起做,然后再考虑z坐标。你对x和y的处理可以分2个步骤:

第1步: 给定视域体中的点(x,y, z),把它投影到近平面z=n。由于投影点在近平面上,所以它的x坐标范围在[l, r],y坐标范围在[b, t]。

第2步: 使用你在正交投影中学会推导的公式,把x坐标从[l, r]映射到[-1, 1],把y坐标范围从[b, t]映射到[-1, 1]。

听上去很棒吧?看一看图5:

图5: 使用相似三角形投影一个点到z=n平面

        在这个图中,你从点(x, y, z)到原点画了条直线,注意直线与z=n平面相交的那个点——用黑色标记的那个。通过这些点,你画了2条相对于z轴的垂线,突然你得到了一对相似三角形。 如果你能够回想起高中的几何知识,相似三角形是拥有相同形状但大小不一定相同的三角形。为了证明2个三角形是相似的,必须证明它们的同位角相等,在这里不 难做到。角1被两个三角形共享,显然它和自身相等。角2和角3是穿越两条平行线形成的同位角,所以它们是相等的。同时,直角当然是彼此相等的,所以两个三 角形是相似的。

对于相似三角形你应该感兴趣的是它们的每对对应边都是同比例的。你知道沿着z轴的边的长度,它们是n和z。那意味着其他对应边的比例也是n/z。所以,考虑下你知道了什么。根据勾股定理,从(x, y, z)相对于z轴做的垂线具有以下长度:

如果你知道了从你的投影点到z轴的垂线的长度,那么你就可以计算出该点的x和y坐标。长度怎么求?那太简单了!因为你有了相似三角形,所以长度就是简单的L乘以n/z:

因此,x坐标是x * n/z,y坐标是y * n/z。第一步做完了。

第二步只是简单的执行你上一部分做的同样的映射,所以是时候回顾下你在正交投影中学习到的推导公式了。回想下把x和y坐标映射到规范视域体,像这样:

现在你可以再次调用这些公式,除非你要考虑到投影;所以,把x用x * n/z代替,把y用y * n/z代替:

现在,通过乘以z:

这些结果有点奇怪。为了把这些等式写进矩阵,你需要把它们写成这种形式:

但很明显,现在还做不到,所以现在看起来进入了僵局。应该做什么呢?如果你能找到个办法获得z’z的公式就像x’z和y’z那样,你就可以写一个变换 矩阵把(x, y, z)映射到(x’z, y’z, z’z)。然后,你只需要把各部分除以点z,你就会得到你想要的(x’, y’, z’)。

因为你知道z到z’的转换不依赖于x和y,你知道你想要一个公式形如z’z= pz + q,p和q是常量。并且,你可以很容易的找到那些常量,因为你知道在两种特殊情况下如何得到z’: 因为你要把[n, f]映射到[0, 1],你知道当z=n时z’=0,和z=f时z’=1。当你把第一组值代入z’z = pz + q,你可以解得:

现在,把第二组值代入,得到:

把q的值代入等式,你可以很容易的解得p:

现在你有p的值了,并且刚刚你求得了q= –pn,所以你可以解得q:

最后,把p和q的表达式代入最原始的公式中,得:

你就快完成了,但是你处理这个问题的不寻常的性质需要你也处理齐次坐标w。通常情况下,只是简单的设置w’ = 1 ——你可能已经注意到在一个基本的变换下最后一行总是[0, 0, 0, 1]—但是现在你在为点(x’z, y’z, z’z, w’z)写一个变换。所以取而代之的,把w’ = 1写成w’z = z。因此最后用于透视投影的等式如下:

现在,当你把这个等式写成矩阵的形式,得到:

        当你把这个矩阵用于点(x, y, z,1),它将产生(x’z, y’z, z’z, w’z)。然后,你应用通常的步骤去除以齐次坐标,得到(x’, y’, z’, 1)。那就是透视投影。Direct3D的D3DXMatrixPerspectiveOffCenterLH()方法也实现了上述公式。正如正交投影, 如果你假设视域体是对称的并且中心是z轴(也就是r = -l,t = -b),你可以简单的用视域体的宽w和高h改写矩阵中的各项:

        Direct3D的D3DXMatrixPerspectiveLH()方法也生成这个矩阵。

最后,还有个经常用的上的透视投影的表示。在这种表示中,你根据摄像机的可视范围定义视域体,而不用去担心视域体的尺寸。此概念参阅图6:

图6: 视域体的高由垂直可视范围的角度a定义

        垂直可视范围的角度是a。这个角度被z轴一分为二,所以根据基本的三角函数,你可以写下面的方程,关联a和近平面n以及屏幕高度h:

这个表达式可以取代投影矩阵中的高度。此外,使用横纵比r代替宽度,r定义为显示区域的宽比高的横纵比。所以,得到:

因此,有了用垂直可视范围角度a和横纵比r构成的透视投影矩阵:

        在Direct3D中,你可以使用D3DXMatrixPerspectiveFovLH()方法得到这种形式的矩阵。这种形式特别有用,因为你可以 直接把r设置成渲染窗口的横纵比,并且可视范围角度为p / 4比较好。所以,你真正需要担心的事情只是定义视域体沿着z轴的范围。

 

        总结

这就是所有的你需要的投影变换背后的数学概念。还有一些其他的不太常用的投影方法,并且如果你使用右手坐标系或者一个不同的规范视域体就会和我们讨论 的有点不同,但是以本文的结论作为基础你应该很容易能够推导出那些公式。如果你想知道更多的关于投影或者其他变换的信息,看一看Tomas Moller和Eric Haines的Real-Time Rendering,或者James D. Foley, Andries van Dam, Steven K. Feiner和John F.Hughes的Computer Graphics: Principles and Practice;这两本是优秀的关于计算机图形的书。

如果你对本文有任何问题,或者需要指出任何需要更正的地方,你可以通过CodeGuru论坛联系我,我的名字是Smasher/Devourer。

Happy coding!

增强现实与透视投影

mikel阅读(1538)

手机的显示屏幕是二维平面,为了表现三维场景,我们不得不通过变换将三维场景投影到二维平面中。而 AR 的过程恰好反过来,我们通过算法从平面图中获取物料在三维场景中的位置并叠加信息,最终让我们感觉虚拟元素被附着在物料上。我们所要讨论的正是如何根据算 法的结果将信息与物料叠加。

注:本文以 iOS 为基础,其它平台可以通过类比实现

无论最终的实现方式如何,它们都有一个共同的基础那就是透视模型。
透视模型
这是简化的透视模型,其中有这么一些重要的元素。首先是世界坐标系,它决定了你从哪里观察世界。然后是视点,它相当于人的眼睛或者摄像机。最后是投影面,它是最终我们能够看到的画面。只要你知道了这些重要元素的信息,那么不论具体的模型和实现有何变化你都能应付自如。

OpenGLES 实现

由于跟踪算法本身给出的结果是用于 OpenGL 的,所以在 OpenGL 中我们只需要将

传入编写好的着色器就可以了。我们更关注 的意义,这有助于之后从这两个矩阵中获取有用的信息。
OpenGL透视模型
对于 ,绿色的区域包含旋转与缩放信息,红色区域表示沿 X、Y、Z 轴的位移,蓝色区域表示切变。
OpenGL透视模型
对于 ,变量与 OpenGL 中的变量相对应。图中的近截面就是我们简化模型中的投影面。所以通过

我们就能计算得到简化模型中摄像头离投影面的距离。

CALayer 实现

我们需要上下两层 CALayer 来实现三维效果,对下面一层设置投影参数(相当于设置摄像头到投影面的距离),对上面一层设置

,设置时要注意摄像头在世界坐标系中的位置。CALayer 模型的详细信息可以阅读参考资料。

HTML 实现

和 CALayer 的模型几乎一样,但更为清晰。css3 中加入了 perspective 与 transform 属性。按照我的理解 perspective 的值表示的就是视点到投影平面的距离,而 transform 则可以设置转移矩阵。

结果及样例工程


样例工程链接:含 CALayer 及 HTML 实现例子

参考资料

1. OpenGL 模型
2. iOS CALayer 模型
3. CSS 模型