易栈网-膘叔

浏览模式: 标准 | 列表 | 图文
Javascript的变量与delete操作符

Javascript的变量与delete操作符

刚刚看到一篇好文(原文链接), 对Javascript中的delete操作符分析得很透彻。在这里简单地介绍一下内容。

虽然是一个小小的delete操作符,其行为却异常复杂。


Javascript的变量

实际上Javascript中,变量 = 对象属性,这是因为 Javascript 在执行脚本之前 会创建一个Global对象,所有的全局变量都是这个Global对象的属性, 执行函数时也会创建一个Activation对象,所有的局部变量都是这个Activation对象的属性。 如下例:

var global = 42;
this.global; // 42, 可以通过this来访问Global对象

this.global2 = 12;
global2; // 12

function foo() {
var local = 36;
// 不过无法直接访问Activation,
// 因此无法通过 foo.local 的方式来访问local变量
}

delete操作符删除的对象

C++中也有delete操作符,它删除的是指针所指向的对象。例如:

class Object {
public:
Object *x;
}
Object o;
o.x = new Object();
delete o.x; // 上一行new的Object对象将被释放
但Javascript的delete与C++不同,它不会删除o.x指向的对象,而是删除o.x属性本身。
// Javascript
var o = {};
o.x = new Object();
delete o.x; // 上一行new的Object对象依然存在
o.x; // undefined,o的名为x的属性被删除了
var o = {};
var a = { x: 10 };
o.a = a;
delete o.a; // o.a属性被删除
o.a; // undefined
a.x; // 10, 因为{ x: 10 } 对象依然被 a 引用,所以不会被回收
在实际的Javascript中,delete o.x之后,Object对象会由于失去了引用而被垃圾回收,所以delete o.x也就“相当于”删除了o.x所指向的对象,但这个动作并不是ECMAScript标准, 也就是说,即使某个实现完全不删除Object对象,也不算是违反ECMAScript标准。“删除属性而不是删除对象”这一点,可以通过以下的代码来确认。另外,delete o.x 也可以写作 delete o["x"],两者效果相同。

对变量执行delete的情况

由于变量也是 Global 或者是 Activation 对象的属性,所以对变量的delete操作也是同样的结果。

var global = 42;
delete global; // 删除Global.global

function foo() {
var local = 36;
delete local; // 删除Activation.local
}

能删除的属性和不能删除的属性

并不是所有的属性都能被delete。例如,prototype中声明的属性就无法被delete:

function C() { this.x = 42; }
C.prototype.x = 12;

var o = new C();
o.x; // 42, 构造函数中定义的o.x

delete o.x;
o.x; // 12, prototype中定义的o.x,即使再次执行delete o.x也不会被删除
对象的预定义属性也无法删除。 可以认为这类属性带有DontDelete的特性。
var re = /abc/i;
delete re.ignoreCase;
re.ignoreCase; // true, ignoreCase无法删除

能删除的变量和不能删除的变量

通过var声明的变量和通过function声明的函数拥有DontDelete特性,无法被删除。

var x = 36;
delete x;
x; // 36, x没有被删除

y = 12;
delete y;
y; // undefined

function foo() { return 42; }
delete foo;
foo(); // 42
但是有一点例外,就是通过 eval 执行的代码中,通过var声明的变量虽然与正常的var声明变量 同属于Global对象,但它们不具有DontDelete特性,能被删除。
eval("var x = 36;");
x; // 42
delete x;
x; // undefined

但是这也有一点例外,eval的代码中的函数内通过var定义的变量具有DontDelete,不能被删除。

eval("(function() { var x = 42; delete x; return x; })();");
// 返回 42

delete的返回值

delete是普通运算符,会返回true或false。规则为:当被delete的对象的属性存在并且拥有DontDelete时 返回false,否则返回true。 这里的一个特点就是,对象属性不存在时也返回true,所以返回值并非完全等同于删除成功与否。

function C() { this.x = 42; }
C.prototype.y = 12;
var o = new C();

delete o.x; // true
o.x; // undefined
"x" in o; // false
// o.x存在并且没有DontDelete,返回true

delete o.y; // true
o.y; // 12
// o自身没有o.y属性,所以返回true
// 从这里也可以看到prototype链的存在,对象自身属性和prototype属性是不同的

delete o; // false
// Global.o拥有DontDelete特性所以返回false

delete undefinedProperty; // true
// Global没有名为undefinedProperty的属性因此返回true

delete 42; // true
// 42不是属性所以返回true。有的实现会抛出异常(违反ECMAScript标准)

var x = 24;
delete x++; // true
x; // 25
// 被删除的是x++的返回值(24),不是属性,所以返回true
MYSQL索引之小小分析

MYSQL索引之小小分析

MYSQL中,如果表是MYISAM,除了主键索引外,基本上常用的就是默认的索引,而unique index应该是比较少用到的。外键索引在MYSQL4.0以下直接忽略。

查了一下资料,好象默认索引应该是称之为B*Tree索引

B*Tree索引是最常见的索引结构,默认建立的索引就是这种类型的索引。B*Tree索引在检索高基数数据列(高基数数据列是指该列有很多不同的值)时提供了最好的性能。当取出的行数占总行数比例较小时B-Tree索引比全表检索提供了更有效的方法。但当检查的范围超过表的10%时就不能提高取回数据的性能。B-Tree索引是基于二叉树的,由分支块(branch block)和叶块(leaf block)组成。在树结构中,位于最底层底块被称为叶块,包含每个被索引列的值和行所对应的rowid。在叶节点的上面是分支块,用来导航结构,包含了索引列(关键字)范围和另一索引块的地址,如图26-1所示。

假设我们要找索引中值为80的行,从索引树的最上层入口开始,定位到大于等于50,然后往左找,找到第2个分支块,定位为75-100,最后再定位到叶块上,找到80所对应的rowid,然后根据rowid去读取数据块获取数据。如果查询条件是范围选择的,比如where column >20 and column <80,那么会先定位到第一个包含20的叶块,然后横向查找其他的叶块,直到找到包含80的块为止,不用每次都从入口进去再重新定位。
平时在MYSQL的查询中,我们也是建议,筛选条件高的、返回结果集比较整齐的,尽量放在条件的第一个,依稀记得WHERE条件中,如果是AND那么是从前往后比较,如果是OR条件,那么是从后往前比较。(记不清了。。。)

刚才又看到文章,说在使用B*Tree索引的时候千万要注意:Btree索引不存储NULL值,而在order by desc中,NULL值总是最大的,sql语句通过索引无法判断表是否存在NULL,执行计划还是走全表扫描[详见:http://rdc.taobao.com/blog/dba/html/67_oracle_fenye.html](当然,这也只是一个参考)

如果是确实这样,那么如果该字段可能会需要用到排序的时候,请尽量不要使用default NULL,而且在处理where的时候,NULL是不会被显示的。除非是条件是is NULL。

刚才测试了一下,设定了某字段允许NULL,然后插入数据,并为该字段做了索引,结果在where fields < 100 的时候,使用了该索引(当然NULL值是不显示的),但我取where fields > 0的时候,扫描方式立刻变为了全表,而且没有使用fields索引(同样不显示含 NULL值的行),where fields > 1 的时候,同样扫描全表。直到我设为fields > 2的时候,又开始使用了索引,看来,MYSQL对于索引字段做查询条件的时候还是需要斟酌的,条件不能随便乱加。

其实在程序中,如果使用INT字段(数值型字段),请尽量不要使用NULL,否则确实有可能会出现值查不到的情况。

然而,在我现在的项目中却有这种情况,要求我们把该字段设为NULL,明天我要再检查一下代码,以防止有漏掉的数据。

不看不知道一看吓一跳呀

突破网页的限制

突破网页的限制

很多时候,WEB开发人员为了偷懒,对于数据输入的长度仅在前台作了限制,而在POST提交页面并未进行判断,这时候很有可能会通过其他方法进行攻击(虽然,插入数据库后,可能会被自动截断,但。。。改改总是挺好玩的)

如:提交页面

<input type="text" name="msg_title" value="" id="msg_title" maxlength="3">

这样的INPUT框里面,你就只能输入3个字符了,怎么办?当然有办法了,在浏览器的地址栏里键入如下代码:

javascript:document.getElementById('msg_title').value='123456';void(0);

现在再看一下,是不是input框里面有6个字符了???
黑黑。。。

浅谈TP的COOKIE类

浅谈TP的COOKIE类

TP的COOKIE类是被封装好的,里面包含了一些常见的操作,set,get,clear等等,其实TP的cookie类与其他的COOKIE有着明显的不同,那就是TP的COOKIE ID是唯一的(不好意思,在我写完这段的时候,为了防止写错,我又重新打开了cookie.class.php,却突然发现,现在的cookie功能与以前的不一样了。)

以前的set函数是类似于这样:

$_COOKIE[C('COOKIE_ID')][C('COOKIE_PREFIX').$name] = $value ;
如今的又恢复成:
$_COOKIE[C('COOKIE_PREFIX').$name] = $value;

既然如此,我还是按最新的稍作讲解吧。。。

Cookie类,固名思意,就是把对COOKIE的操作进行了封装,通过定义一个COOKIE名的前缀,以防与其他COOKIE产生冲突,然后加上COOKIE变量的名称,就可以进行赋值、取值等。

Cookie类所有的操作都采用了静态方法,即在实际应用中,只要加载了Cookie类,就可以随时使用了:

<?
//假设cookie 前缀为 'neatcn_'
Cookie::get('test');
//return $_COOKIE['neatcn_test'];

Cookie::set('test','123456');
//return setcookie('neatcn_test','123456',xxx,xxx,xxx);
//xxx是默认定义的常量,如默认COOKIE的过期时间,作用域等
对于这些封装,可以方便使用,同样需要注意的是,在Cookie操作时,请不要输出header,否则可能会出现header already send的warning,同时无法写cookie,这点请需要注意。

另外一个需要注意的是,cookie在不同的浏览器下面有着不同的限制,最常见的就是cookie的长度,请尽量不要超过4K的字节。

参考资料:

* 一个 Cookie 档案最多只能包含 300 个 Cookies。(这只适用于 Netscape,因为它把所有的 Cookie 都放在一个档案。)
* 每一个 Cookie 的大小不得超过 4KB。
* 每一个 URL 路径,最多只能设定 20 个 Cookie。
防盗链的几种常见方法

防盗链的几种常见方法

身为一个网站管理人员,防盗链这个任务实在是任重而道远呀。不说别的,如果不控制,每天的流量可能就会刷刷刷的往上涨,可实际情况呢?根本没有人来浏览。这还不是最重要的,最重要的是这些流量占用了大量带宽,影响了其他人的正常浏览。长此以往,来浏览的人就更少了,再最后就造成恶性循环,网速越慢,来看的人越少,来看的人越少,被下载的机会就越多,也就导致打开网页更慢。

作为一个非下载性网站,防盗连实在是有一些必要,这里我也不谈很多,毕竟防盗链也是一门很高深的技术,我只能随便谈谈。

技巧没有多少,稍谈一点
1、利用SESSION机制,判断该用户的session存在多久?如果是刚生成的,则不让下载
2、COOKIE机制,如果是正常浏览网页,那么生成一个COOKIE,然后才允许下载
3、检查客户机的agent,不过这个很不准确,大多数下载工具都会伪造一个头
4、referer,这个嘛。。。黑黑,表面上有用,其实也没啥用,很明显,下载工具也一样会伪造
5、即时readfile下载,这个会有一点问题,如果东西不大,那是无所谓,如果东西很大,那问题就来了,不能断点续传,也不能使用下载工具。一旦下载失败就不得不重来
6、输入密码。。。象某些网站的提取码之类的
7、用组件,IIS上有现成的防盗链组件,直接可以使用,限制好特定的文件后缀名就行了
8、验证码,这个和输入密码下载有点类似,不过现在很多程序都会自动识别验证码,但下载工具好象还没有这个功能,可以尝试
9、通过邮件机制发送,而不是直接给出下载链接(但很有可能没有办法在网上显示相关内容)
10、无耻的生成BT种子,你们想怎么玩就怎么玩吧。
11、定期按规则更改文件名,或者是链接。。。但不知道那些下载工具是否会自动判断文件的MD5?或者在下载前先判断文件MD5?应该不可能,所以还是值得尝试一下,写个函数,对于下载链接的URL可以定时更换。
12、更改资源内容(这个是看别人的网站而来,意思是,如果提供了MP3下载,那么可以尝试定期不定期的修改MP3的TAG,如果是压缩文件,那么他们都有一个备注区,定期通过程序修改这些位置,可以造成文件大小不一样,对于下载软件来说可能就会被当成不同的文件)
对于11、12好象都有一个问题,主要是指迅雷,如果资源被收录了,但实际被删除了,可能会在一段时间内会被迅雷狂访问,变相的DDOS,曾经我有一台服务器就遇到过这种情况,在我之前,服务器的IP是做电影下载的,后来不搞了,我使用了这个IP,天天流量满,后来一查,发现所有的链接都是下载电影的,虽然没有资源了,但就是拼命的、不停的在连接,造成流量狂涨。

上面这些方法除了用组件比较完全和相对会比较成功一点之外,其他的都难说啊。。。

随便谈谈而己,也不奢求一定能够有用

惊心动魄的SQL BUG

惊心动魄的SQL BUG

刚才打开google reader的时候,突然看到一篇文章,让俺大吃一惊呀。详细内容如下:

请谨慎注意这一微软SQLBug
刚来博客园,有什么不对,需要改进的地方,欢迎各位同道指出来,谢谢。
今天在使用数据库时出现意外操作,将一张表的数据删除了。仔细查看SQL语句,发现问题。将问题类推到Northwind中。我们使用两个表,Employees和Products
SQL语句如下:
select * from Products where CategoryID in (select CategoryID from Employees)
不看表结构是看不出什么问题的,不清楚的看一下表结构。执行上面的SQL语句,你将能看到所有的产品记录。
当你执行上面语句的"select CategoryID from Employees",将会提示一个错误提示:
消息 207,级别 16,状态 3,第 1 行
列名 'CategoryID' 无效。
表Employees中根本就没有CategoryID列,而微软忽略了括号中的错误,执行前面的语句。如果是应用在删除,更新,那后果将是不可现象的。我曾为此付出了代价。
这个应该就是微软SQL的BUG吧,我们需要特别注意。
在SQL2000企业管理器,SQL2000的查询分析器,SQL2005的Management Studio Express中均有此Bug。

然后有人回复为:

我在Sql2000上测试,确实有这个问题。
有意思的问题,在我的management studio里面查询,也会有这个问题,要好好地研究一下。
Microsoft SQL Server Management Studio 9.00.1399.00
有楼主说的问题!
确实应该注意一下。
看了表结构才知道,Employees 表里面没有 CategoryID字段。为什么不报错呢。错误被吃掉了。

select * from Products where CategoryID in (select CategoryID from Employees)

select * from Products where CategoryID in (select EmployeeID from Employees)

我比较了一下这两个语句的执行计划,不完全一样。建议lz分析一下执行计划。
因为我还不能完全看懂执行计划。只能看出来两个执行计划是不一样的。呵呵。
在MYSQL里没有试会不会遇到这种情况,我用navicat试是没有这种情况的。不知道其他的会不会有。

联想起以前写CRUD之类的东西时候,在过滤条件时,自动将空值直接unset掉,然后再拼成SQL,后来被BOBBY严厉批评了一下,确实,查询的时候感觉不出有什么问题,可如果是在UPDATE或者是DELETE的时候呢?本来是有条件的,可突然被unset掉成为没有条件了,那岂不是很恐怖?

借这个机会再次提醒自己,细心、周全、小心

对膘叔的文章感兴趣的朋友可以使用JS调用功能

对膘叔的文章感兴趣的朋友可以使用JS调用功能

<script type="text/javascript" charset="utf-8" src="http://www.neatcn.com/js.php?view=article&cids=1&titlenum=10&titlelimit=50&newwindow=1&cname=0&author=0&orderby=dateline&dateline=0&articleinfo=0"></script>

一一分析路径。。。
view=article&cids=1&titlenum=10&titlelimit=50&newwindow=1&cname=0&author=0&orderby=dateline&dateline=0&articleinfo=0

以上代码表示:

view = article 调用文章
cids=1 分类ID为1,如果没有cids,则代表调用所有分类
titlenum=10 显示为10条
titlelimit=50 标题截取字数为50
newwindow=1 表示是否开新窗:1为开新窗0为不开
cname=0 表示显示文章所在分类:1为显示,0为不显示
author=0 是否显示作者姓名,1为显示,0为不显示
orderby=dateline 表示文章按什么类型排序>> dateline:文章发表的时间 ,views:浏览次数最多的文章,comments:评论次数最多的文章
dateline=0 是否显示文章发表时间:1为显示,0为不显示
articleinfo=0 是否显示文章数据包括浏览次数和评论次数,1为显示,0为不显示

黑黑,尝试一下吧。,。。

强烈推荐google reader

强烈推荐google reader

之所以会突然间提起这个也是有历史原因的,以前收藏别人的网站(指RSS),都是用的greatnews,而且也用了有两年之久,之后也使用过很多客户端软件,什么周伯通啦之类的,包括一些特定的客户端也用了不少,象foxmail,maxthon等,但最后还是坚持 使用了GreatNews。

可是,为什么我现在又推荐google reader了呢?这当然也有原因的,最初greatnews的时候并没有想过我会有两台电脑。也没有想过会有三台,四台这样的情况,因此一个greatnews我就完全足够了。然而却往往事与愿违,在单位的时候、在家的时候、出门在外的时候都有可能会关注这些收藏的网址,怎么办?客户端软件的局限性就出来了。

如果我每台机器都装一个,那么势必我每条新闻都要确认多次我是否读过,这样的人生活着还有什么意义?于是想到了在线阅读。。。在线阅读,当然要首选网络最快的了,哪个网站网络最快?毫无疑问,google打倒了几乎所有的对手。需要其他选择吗?需要理由吗?那就再加一个理由,几乎所有的feed都支持google,其他一些在线阅读软件他敢这么保证吗?

而且,使用google reader我也不需要再重新注册了,因为我本来就有gmail。功能也不比网上那些在线阅读网站差,再配合上firefox的google reader监视插件,相当于我装了一个C/s客户端。这些都不是主要的,主要的是,我在一个地方mark feed is read之后,使用其他电脑的时候,我可以直接跳过这些,如果实在重要的,我还可以加星、加到我的共享,同时将我的共享网址发给别人的时候,他们也可以和我一样分享我认为有意义的东西,岂不美哉?

故此,推荐google reader,认为枪文也好,放屁也罢,这是我的选择,你可以不赞同也可以反对,但我不会改变,除非你有更好的理由来说服我。Over,黑黑

用TIMESTAMP类型取代INT和DATETIME[转]

用TIMESTAMP类型取代INT和DATETIME[转]

引言:在以前,我总是习惯用 INT UNSIGNED 来存储一个转换成Unix时间戳的时间值,认为这样做从索引,比较等角度来讲,都会比较高效。现在我们来对比下 TIMESTAMP 和 INT UNSIGNED 以及 DATETIME 这3种类型到底谁更好。

1. 准备

创建一个测试表:

mysql> CREATE TABLE `t` (
`d1` int(10) unsigned NOT NULL default '0',
`d2` timestamp NOT NULL default CURRENT_TIMESTAMP,
`d3` datetime NOT NULL,
KEY `d2` (`d2`),
KEY `d1` (`d1`),
KEY `d3` (`d3`)
);

然后创建一个存储过程填充数据:

mysql> DELIMITER //
CREATE PROCEDURE INS_T()
BEGIN
SET @i=1;
WHILE 0<1
DO
SET @i=@i+1;
INSERT INTO i VALUES (1199116800+@i, FROM_UNIXTIME(1199116800+@i), FROM_UNIXTIME(1199116800+@i));
END WHILE;
END;//
DELIMITER ;

时间戳 1199116800 表示 2008-01-01 这个时间点。然后运行存储过程,大概填充几十万条记录后,中止执行,因为上面的存储过程是个死循环,所以需要人工中止。
来看看到底有多少条记录了,以及索引情况:

mysql> select count(*) from t;
+----------+
| count(*) |
+----------+
| 924707 |
+----------+
mysql> analyze table t;
+--------+---------+----------+-----------------------------+
| Table | Op | Msg_type | Msg_text |
+--------+---------+----------+-----------------------------+
| test.t | analyze | status | Table is already up to date |
+--------+---------+----------+-----------------------------+
mysql> show index from t;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| t | 1 | d2 | 1 | d2 | A | 924707 | NULL | NULL | | BTREE | |
| t | 1 | d1 | 1 | d1 | A | 924707 | NULL | NULL | | BTREE | |
| t | 1 | d3 | 1 | d3 | A | 924707 | NULL | NULL | | BTREE | |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

2. 对比

2.1 只检索一条记录

mysql> explain select * from t where d1 = 1199579155;
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| 1 | SIMPLE | t | ref | d1 | d1 | 4 | const | 1 | |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
mysql> explain select * from t where d2 = '2008-01-06 08:25:55';
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| 1 | SIMPLE | t | ref | d2 | d2 | 4 | const | 1 | |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
mysql> explain select * from t where d3 = '2008-01-06 08:25:55';
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| 1 | SIMPLE | t | ref | d3 | d3 | 8 | const | 1 | |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+

2.2 范围检索

mysql> explain select * from t where d1 >= 1199894400;
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | t | range | d1 | d1 | 4 | NULL | 121961 | Using where |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
mysql> explain select * from t where d2 >= from_unixtime(1199894400);
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | t | range | d2 | d2 | 4 | NULL | 121961 | Using where |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
mysql> explain select * from t where d3 >= from_unixtime(1199894400);
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | t | range | d3 | d3 | 8 | NULL | 120625 | Using where |
+----+-------------+-------+-------+---------------+------+---------+------+--------+-------------+

小结:从上面的2次对比中可以看到,对 d1 或 d2 字段检索时的索引长度都是 4,因为 TIMESTAMP 实际上是 4字节 的 INT 值。因此,实际应用中,基本上完全可以采用 TIMESTAMP 来代替另外2种类型了,并且 TIMESTAMP 还能支持自动更新成当前最新时间,何乐而不为呢?

--END--

依稀记得,timestamp类型好象是从MYSQL4.1以后才支持的吧,很多时间为了兼容都采用了无符号的INT类型,效率相差也并不是很大,如果是直接在高版本上操作,还是和作者学习一下吧。黑黑

Choosing watermark color based on the background luminosity[转]

原文网址:http://valokuva.org/?p=59

原文内容:

Usually images are watermarked using a predefined color (let's say for example "white"). How well does this actually work when you're doing the watermark on a light or even a white image? The answer is simple: not very well.

This example illustrates how to use ImagickPixelIterator to get the average luminosity of the background and chosing text color according to it. I tested this algorithm very briefly and the results seemed positive. There's four example images posted at the end of this post to show why background matters when doing a watermark.

代码:

<?php

/* Allowed images */
$allowed = array( "strawberry.png", "Imagepng" );
$img = ( isset( $_GET['img'] ) && in_array( $_GET['img'], $allowed ) )
? $_GET['img'] : 'strawberry.png';

/* Read the image in. This image will be watermarked. */
$image = new Imagick( $img );
$image->setImageFormat( "png" );

/* The text to write on the mark */
$text = "www.valokuva.org";

/* This object will hold the font properties */
$draw = new ImagickDraw();

/* Setting gravity to the center changes the origo
where annotation coordinates are relative to */
$draw->setGravity( Imagick::GRAVITY_CENTER );

/* Use a custom truetype font */
$draw->setFont( "./WCManoNegraBta.ttf" );

/* Set the font size */
$draw->setFontSize( 26 );

/* Create a new imagick object */
$im = new imagick();

/* Get the text properties */
$properties = $im->queryFontMetrics( $draw, $text );

/* Region size for the watermark.
Add some extra space on the sides */
$watermark['w'] = intval( $properties["textWidth"] + 5 );
$watermark['h'] = intval( $properties["textHeight"] + 5 );

/* Create a canvas using the font properties.
Add some extra space on width and height */
$im->newImage( $watermark['w'], $watermark['h'],
new ImagickPixel( "transparent" ) );

/* Get a region pixel iterator to get the pixels in the watermark area */
$it = $image->getPixelRegionIterator( 0, 0, $watermark['w'], $watermark['h'] );

$luminosity = 0;
$i = 0;

/* Loop trough rows */
while( $row = $it->getNextIteratorRow() )
{
/* Loop trough each column on the row */
foreach ( $row as $pixel )
{
/* Get HSL values */
$hsl = $pixel->getHSL();
$luminosity += $hsl['luminosity'];
$i++;
}
}

/* If we are closer to white, then use black font and
the other way around */
$textColor = ( ( $luminosity / $i )> 5 ) ?
new ImagickPixel( "black" ) :
new ImagickPixel( "white" );

/* Use the same color for the shadow */
$draw->setFillColor( $textColor );

/* Use png format */
$im->setImageFormat( "png" );

/* Annotate some text on the image */
$im->annotateImage( $draw, 0, 0, 0, $text );

/* Clone the canvas to create the shadow */
$watermark = $im->clone();

/* Set the image bg color to black. (The color of the shadow) */
$watermark->setImageBackgroundColor( $textColor );

/* Create the shadow (You can tweak the parameters
to produce "different" kind of shadows */
$watermark->shadowImage( 80, 2, 2, 2 );

/* Composite the text on the background */
$watermark->compositeImage( $im, Imagick::COMPOSITE_OVER, 0, 0 );

/* Composite the watermark on the image to the top left corner */
$image->compositeImage( $watermark, Imagick::COMPOSITE_OVER, 0, 0 );

/* Display the results */
header( "Content-Type: image/png" );
echo $image;

?>
效果图如下:

Here is strawberry.png with black watermark (this was chosen by the script):

f1305dd65c2e771f2d432979e7e050e9

And here is strawberry.png with white watermark:

da4932e64da4c76dd447c8790868514a

Image32.png with white watermark (this was chosen by the script):

jqueryapi_chm.chm

Image32.png with black watermark:

cascading-style-sheet-2.0-中文手册.chm

P.S. I opened a new page for the Windows Imagick builds. Seems like my dynamic builds work pretty well, so I will keep providing them thru this site.

--END--

看这个明显可以看出效果比GD好很多,而且代码也没有那么复杂。如果服务器上装了imagemagick扩展,还是用这些比较方便啊