查看规则

iptables -L INPUT --line-numbers
iptables -L -n --line-number
iptables -D INPUT 2

开放指定的端口

iptables -A INPUT -p tcp --dport 80 -j ACCEPT

禁止指定端口

iptables -A INPUT -p tcp --dport 80 -j DROP

拒绝所有端口

iptables -A INPUT -j DROP

限制单个ip的所有端口访问

iptables -I INPUT -s 211.1.0.1 -j DROP

封IP段所有端口访问

iptables -I INPUT -s 211.1.0.0/16 -j DROP
iptables -I INPUT -s 211.2.0.0/16 -j DROP
iptables -I INPUT -s 211.3.0.0/16 -j DROP

封整个段所有端口访问

iptables -I INPUT -s 211.0.0.0/8 -j DROP

限制9889端口 tcp访问

iptables -I INPUT -p tcp --dport 9889 -j DROP

允许211.1.0.1 tcp访问9889端口

iptables -I INPUT -s 211.1.0.1 -p tcp --dport 9889 -j ACCEPT

限制211.1.0.1 tcp访问9889端口

iptables -I INPUT -s 211.1.0.1 -p tcp --dport 9889 -j DROP

限制211.1.0.1访问80端口的并发数不超过10

iptables -I INPUT -p tcp --dport 80 -s 211.1.0.1 -m connlimit --connlimit-above 10 -j REJECT

解除所有

iptables -L INPUT

解除单个(iptables -L --line-numbers 查看id)

iptables -D INPUT $ID

1、^: 匹配字符串的开始位置;
 
2、 $:匹配字符串的结束位置;
 
3、.*:   .匹配任意字符,*匹配数量0到正无穷;
 
4、\. 斜杠用来转义,\.匹配 .    特殊使用方法,记住记性了;
 
5、(值1|值2|值3|值4):或匹配模式,例:(jpg|gif|png|bmp)匹配jpg或gif或png或bmp
 
6、i不区分大小写
 

一.正则表达式匹配,其中:
* ~ 为区分大小写匹配
* ~* 为不区分大小写匹配
* !~和!~*分别为区分大小写不匹配及不区分大小写不匹配
二.文件及目录匹配,其中:
* -f和!-f用来判断是否存在文件
* -d和!-d用来判断是否存在目录
* -e和!-e用来判断是否存在文件或目录
* -x和!-x用来判断文件是否可执行
三.rewrite指令的最后一项参数为flag标记,flag标记有:
1.last    相当于apache里面的[L]标记,表示rewrite。
2.break本条规则匹配完成后,终止匹配,不再匹配后面的规则。
3.redirect  返回302临时重定向,浏览器地址会显示跳转后的URL地址。
4.permanent  返回301永久重定向,浏览器地址会显示跳转后的URL地址。


使用last和break实现URI重写,浏览器地址栏不变。而且两者有细微差别,使用alias指令必须用last标记;使用proxy_pass指令时,需要使用break标记。Last标记在本条rewrite规则执行完毕后,会对其所在server{......}标签重新发起请求,而break标记则在本条规则匹配完成后,终止匹配。
例如:如果我们将类似URL/photo/123456 重定向到/path/to/photo/12/1234/123456.png
rewrite "/photo/([0-9]{2})([0-9]{2})([0-9]{2})"/path/to/photo/$1/$1$2/$1$2$3.png ;


四.NginxRewrite 规则相关指令


1.break指令
使用环境:server,location,if;
该指令的作用是完成当前的规则集,不再处理rewrite指令。


2.if指令
使用环境:server,location
该指令用于检查一个条件是否符合,如果条件符合,则执行大括号内的语句。If指令不支持嵌套,不支持多个条件&&和||处理。


3.return指令
语法:returncode ;
使用环境:server,location,if;
该指令用于结束规则的执行并返回状态码给客户端。
示例:如果访问的URL以".sh"或".bash"结尾,则返回403状态码
location ~ .*\.(sh|bash)?$
{
return 403;
}


4.rewrite 指令
语法:rewriteregex replacement flag
使用环境:server,location,if
该指令根据表达式来重定向URI,或者修改字符串。指令根据配置文件中的顺序来执行。注意重写表达式只对相对路径有效。如果你想配对主机名,你应该使用if语句,示例如下:
if( $host ~* www\.(.*) )
{
set $host_without_www $1;
rewrite ^(.*)$  http://$host_without_www$1permanent;
}


5.Set指令
语法:setvariable value ; 默认值:none; 使用环境:server,location,if;
该指令用于定义一个变量,并给变量赋值。变量的值可以为文本、变量以及文本变量的联合。
示例:set$varname "hello world";


6.Uninitialized_variable_warn指令
语法:uninitialized_variable_warnon|off
使用环境:http,server,location,if
该指令用于开启和关闭未初始化变量的警告信息,默认值为开启。

 


五.Nginx的Rewrite规则编写实例
1.当访问的文件和目录不存在时,重定向到某个php文件
if( !-e $request_filename )
{
rewrite ^/(.*)$ index.php last;
}


2.目录对换 /123456/xxxx  ====>  /xxxx?id=123456
rewrite ^/(\d+)/(.+)/  /$2?id=$1 last;


3.如果客户端使用的是IE浏览器,则重定向到/ie目录下
if( $http_user_agent  ~ MSIE)
{
rewrite ^(.*)$ /ie/$1 break;
}


4.禁止访问多个目录
location ~ ^/(cron|templates)/
{
deny all;
break;
}


5.禁止访问以/data开头的文件
location ~ ^/data
{
deny all;
}


6.禁止访问以.sh,.flv,.mp3为文件后缀名的文件
location ~ .*\.(sh|flv|mp3)$
{
return 403;
}


7.设置某些类型文件的浏览器缓存时间
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~ .*\.(js|css)$
{
expires 1h;
}


8.给favicon.ico和robots.txt设置过期时间;
这里为favicon.ico为99天,robots.txt为7天并不记录404错误日志
location ~(favicon.ico) {
log_not_found off;
expires 99d;
break;
}
location ~(robots.txt) {
log_not_found off;
expires 7d;
break;
}


9.设定某个文件的过期时间;这里为600秒,并不记录访问日志
location ^~ /html/scripts/loadhead_1.js {
access_log  off;
root /opt/lampp/htdocs/web;
expires 600;
break;
}


10.文件反盗链并设置过期时间
这里的return412 为自定义的http状态码,默认为403,方便找出正确的盗链的请求
“rewrite ^/ http://img.linuxidc.net/leech.gif;”显示一张防盗链图片
“access_log off;”不记录访问日志,减轻压力
“expires 3d”所有文件3天的浏览器缓存


location ~*^.+\.(jpg|jpeg|gif|png|swf|rar|zip|css|js)$ {
valid_referers none blocked *.linuxidc.com*.linuxidc.net localhost 208.97.167.194;
if ($invalid_referer) {
rewrite ^/ http://img.linuxidc.net/leech.gif;
return 412;
break;
}
access_log  off;
root /opt/lampp/htdocs/web;
expires 3d;
break;
}


11.只允许固定ip访问网站,并加上密码


root /opt/htdocs/www;
allow  208.97.167.194; 
allow  222.33.1.2; 
allow  231.152.49.4;
deny  all;
auth_basic “C1G_ADMIN”;
auth_basic_user_file htpasswd;


12将多级目录下的文件转成一个文件,增强seo效果
/job-123-456-789.html 指向/job/123/456/789.html


rewrite^/job-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /job/$1/$2/jobshow_$3.html last;


13.文件和目录不存在的时候重定向:


if (!-e $request_filename) {
proxy_pass http://127.0.0.1;
}


14.将根目录下某个文件夹指向2级目录
如/shanghaijob/ 指向 /area/shanghai/
如果你将last改成permanent,那么浏览器地址栏显是/location/shanghai/
rewrite ^/([0-9a-z]+)job/(.*)$ /area/$1/$2last;
上面例子有个问题是访问/shanghai时将不会匹配
rewrite ^/([0-9a-z]+)job$ /area/$1/ last;
rewrite ^/([0-9a-z]+)job/(.*)$ /area/$1/$2last;
这样/shanghai 也可以访问了,但页面中的相对链接无法使用,
如./list_1.html真实地址是/area/shanghia/list_1.html会变成/list_1.html,导至无法访问。
那我加上自动跳转也是不行咯
(-d $request_filename)它有个条件是必需为真实目录,而我的rewrite不是的,所以没有效果
if (-d $request_filename){
rewrite ^/(.*)([^/])$ http://$host/$1$2/permanent;
}
知道原因后就好办了,让我手动跳转吧
rewrite ^/([0-9a-z]+)job$ /$1job/permanent;
rewrite ^/([0-9a-z]+)job/(.*)$ /area/$1/$2last;


15.域名跳转
server
{
listen      80;
server_name  jump.linuxidc.com;
index index.html index.htm index.php;
root  /opt/lampp/htdocs/www;
rewrite ^/ http://www.linuxidc.com/;
access_log  off;
}


16.多域名转向
server_name  www.linuxidc.comwww.linuxidc.net;
index index.html index.htm index.php;
root  /opt/lampp/htdocs;
if ($host ~ "linuxidc\.net") {
rewrite ^(.*) http://www.linuxidc.com$1permanent;
}


六.nginx全局变量
arg_PARAMETER    #这个变量包含GET请求中,如果有变量PARAMETER时的值。
args                    #这个变量等于请求行中(GET请求)的参数,如:foo=123&bar=blahblah;
binary_remote_addr #二进制的客户地址。
body_bytes_sent    #响应时送出的body字节数数量。即使连接中断,这个数据也是精确的。
content_length    #请求头中的Content-length字段。
content_type      #请求头中的Content-Type字段。
cookie_COOKIE    #cookie COOKIE变量的值
document_root    #当前请求在root指令中指定的值。
document_uri      #与uri相同。
host                #请求主机头字段,否则为服务器名称。
hostname          #Set to themachine’s hostname as returned by gethostname
http_HEADER
is_args              #如果有args参数,这个变量等于”?”,否则等于”",空值。
http_user_agent    #客户端agent信息
http_cookie          #客户端cookie信息
limit_rate            #这个变量可以限制连接速率。
query_string          #与args相同。
request_body_file  #客户端请求主体信息的临时文件名。
request_method    #客户端请求的动作,通常为GET或POST。
remote_addr          #客户端的IP地址。
remote_port          #客户端的端口。
remote_user          #已经经过Auth Basic Module验证的用户名。
request_completion #如果请求结束,设置为OK. 当请求未结束或如果该请求不是请求链串的最后一个时,为空(Empty)。
request_method    #GET或POST
request_filename  #当前请求的文件路径,由root或alias指令与URI请求生成。
request_uri          #包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。不能修改。
scheme                #HTTP方法(如http,https)。
server_protocol      #请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
server_addr          #服务器地址,在完成一次系统调用后可以确定这个值。
server_name        #服务器名称。
server_port          #请求到达服务器的端口号。


七.Apache和Nginx规则的对应关系
Apache的RewriteCond对应Nginx的if
Apache的RewriteRule对应Nginx的rewrite
Apache的[R]对应Nginx的redirect
Apache的[P]对应Nginx的last
Apache的[R,L]对应Nginx的redirect
Apache的[P,L]对应Nginx的last
Apache的[PT,L]对应Nginx的last


例如:允许指定的域名访问本站,其他的域名一律转向www.linuxidc.net
  Apache:
RewriteCond %{HTTP_HOST} !^(.*?)\.aaa\.com$[NC]
RewriteCond %{HTTP_HOST} !^localhost$ 
RewriteCond %{HTTP_HOST}!^192\.168\.0\.(.*?)$
RewriteRule ^/(.*)$ http://www.linuxidc.net[R,L]


  Nginx:
if( $host ~* ^(.*)\.aaa\.com$ )
{
set $allowHost ‘1’;
}
if( $host ~* ^localhost )
{
set $allowHost ‘1’;
}
if( $host ~* ^192\.168\.1\.(.*?)$ )
{
set $allowHost ‘1’;
}
if( $allowHost !~ ‘1’ )
{
rewrite ^/(.*)$ http://www.linuxidc.netredirect ;
}

 

JPEG文件编/解码详解

cat_ng 猫猫

JPEGJoint Photographic Experts Group)是联合图像专家小组的英文缩写。它由国际电话与电报咨询委员会CCITTThe International Telegraph and Telephone Consultative Committee)与国际标准化组织ISO1986年联合成立的一个小组,负责制定静态数字图像的编码标准。

小组一直致力于标准化工作,开发研制出连续色调、多级灰度、静止图像的数字图像压缩编码方法,即JPEG算法。JPEG算法被确定为国际通用标准,其适用范围广泛,除用于静态图像编码外,还推广到电视图像序列的帧内图像压缩。而用JPEG算法压缩出来的静态图片文件称为JPEG文件,扩展名通常为*.jpg*.jpe*.jpeg

JPEG专家组开发了两种基本的压缩算法、两种数据编码方法、四种编码模式。具体如下:

压缩算法:

l 有损的离散余弦变换(Discrete Cosine TransformDCT);

l 无损的预测技术压缩。

数据编码方法:

l 哈夫曼编码;

l 算术编码;

编码模式:

l 基于DCT顺序模式:编/解码通过一次扫描完成;

l 基于DCT递进模式:编/解码需要多次扫描完成,扫描效果从粗糙到精细,逐级递进;

l 无损模式:基于DPCM,保证解码后完全精确恢复到原图像采样值;

l 层次模式:图像在多个空间多种分辨率进行编码,可以根据需要只对低分辨率数据作解码,放弃高分辨率信息。

在实际应用中,JPEG图像使用的是离散余弦变换、哈夫曼编码、顺序模式。

 

JPEG压缩编码算法的主要计算步骤如下:

(0) 8*8分块。

(1) 正向离散余弦变换(FDCT)

(2) 量化(quantization)

(3) Z字形编码(zigzag scan)

(4) 使用差分脉冲编码调制(DPCM)对直流系数(DC)进行编码。

(5) 使用行程长度编码(RLE)对交流系数(AC)进行编码。

(6) 熵编码。

 

笔者在实践过程中查阅了大量的资料,发现大多数书籍资料和网上资料都是从编码角度分析JPEG的编/解码方式,并且都只是介绍编码过程中的主要方法。所以,本文从解码角度详细分析JPEG的编/解码过程,并且加入许多笔者实践过程中遇到的问题和解决方法,希望从另一个角度说明问题,以更好帮助读者结合其他资料解决问题。

不过,介绍解码过程之前,首先要了解JPEG文件中数据的存储格式。


一、JPEG文件格式介绍

JPEG文件使用的数据存储方式有多种。最常用的格式称为JPEG文件交换格式(JPEG File Interchange FormatJFIF)。而JPEG文件大体上可以分成两个部分:标记码(Tag)和压缩数据。

标记码由两个字节构成,其前一个字节是固定值0xFF,后一个字节则根据不同意义有不同数值。在每个标记码之前还可以添加数目不限的无意义的0xFF填充,也就说连续的多个0xFF可以被理解为一个0xFF,并表示一个标记码的开始。而在一个完整的两字节的标记码后,就是该标记码对应的压缩数据流,记录了关于文件的诸种信息。

常用的标记有SOIAPP0DQTSOF0DHTDRISOSEOI

注意,SOI等都是标记的名称。在文件中,标记码是以标记代码形式出现。例如SOI的标记代码为0xFFD8,即在JPEG文件中的如果出现数据0xFFD8,则表示此处为一个SOI标记。

本文附录列出一张完整的JPEG定义的标记表,供读者查阅。这里仅列出几个常用标记的标记代码、占用字节长度和表示的意义。

 

l         SOIStart of Image,图像开始

u  标记代码                                 2字节     固定值0xFFD8

 

l       APP0Application,应用程序保留标记0

u  标记代码                                 2字节     固定值0xFFE0

u  包含9个具体字段:
 
数据长度                         2字节     ~9个字段的总长度
                                                           
即不包括标记代码,但包括本字段
 
标识符                             5字节    固定值0x4A46494600,即字符串“JFIF0”
 
版本号                             2字节    一般是0x0102,表示JFIF的版本号
1.2
                                                           
可能会有其他数值代表其他版本

 
XY的密度单位           1字节     只有三个值可选
                                                            0
:无单位;1:点数/英寸;2:点数/厘米
 
X方向像素密度               2字节     取值范围未知
 
Y方向像素密度               2字节     取值范围未知  
 
缩略图水平像素数目        1字节     取值范围未知

 
缩略图垂直像素数目        1字节     取值范围未知
 
缩略图RGB位图             长度可能是3的倍数           缩略图RGB位图数据

 

本标记段可以包含图像的一个微缩版本,存为24位的RGB像素。如果没有微缩图像(这种情况更常见),则字段缩略图水平像素数目和字段缩略图垂直像素数目的值均为0

 

l       APPnApplication,应用程序保留标记n,其中n=115(任选)

u  标记代码                                 2字节     固定值0xFFE1~0xFFF

u  包含2个具体字段:
 
数据长度                         2字节     ~2个字段的总长度
                                                           
即不包括标记代码,但包括本字段
 
 详细信息            数据长度-2字节   内容不定
                                                    

例如,Adobe Photoshop生成的JPEG图像中就用了APP1APP13两个标记段分别存储了一幅图像的副本。

 

l       DQTDefine Quantization Table,定义量化表

u  标记代码                          2字节            固定值0xFFDB

u  包含9个具体字段:
 
数据长度                  2字节            字段①和多个字段②的总长度
                                                           
即不包括标记代码,但包括本字段
 
 量化表        数据长度-2字节

a)         精度及量化表ID   1字节            4位:精度,只有两个可选值
                                                              0
8位;116
                                               
4位:量化表ID,取值范围为03

b)        表项       (64×(精度+1))字节              例如8位精度的量化表
                                               
其表项长度为64×0+1=64字节

 

本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次。

 

l       SOF0Start of Frame,帧图像开始

u  标记代码                   2字节     固定值0xFFC0

u  包含9个具体字段:
 
数据长度           2字节     ~六个字段的总长度
                                             
即不包括标记代码,但包括本字段
 
精度                 1字节     每个数据样本的位数
                                             
通常是8位,一般软件都不支持 12位和16
 
图像高度           2字节     图像高度(单位:像素),如果不支持 DNL 就必须 >0
 
图像宽度           2字节     图像宽度(单位:像素),如果不支持 DNL 就必须
>0
 
颜色分量数        1字节     只有3个数值可选

                                              1
:灰度图;3YCrCbYIQ4CMYK
                                             
JFIF中使用YCrCb,故这里颜色分量数恒为
3
 
颜色分量信息      颜色分量数×3字节(通常为9字节)

a)         颜色分量ID                 1字节    

b)        水平/垂直采样因子      1字节            4位:水平采样因子
                                                      
4位:垂直采样因子
                                                      
(曾经看到某资料把这两者调转了)

c)        量化表                         1字节            当前分量使用的量化表的ID

本标记段中,字段⑥应该重复出现,有多少个颜色分量(字段⑤),就出现多少次(一般为3次)。

 

l       DHTDifine Huffman Table,定义哈夫曼表

u  标记代码                                 2字节            固定值0xFFC4

u  包含2个具体字段:
 
数据长度                             2字节            字段①和多个字段②的总长度
                                                                  
即不包括标记代码,但包括本字段
 
 哈夫曼表              数据长度-2字节

a)       ID和表类型            1字节            4位:类型,只有两个值可选
                                                                     0
DC直流;1AC交流
                                                       
4位:哈夫曼表ID
                                                                    
注意,DC表和AC表分开编码

b)      不同位数的码字数量    16字节

c)      编码内容       16个不同位数的码字数量之和(字节)

本标记段中,字段②可以重复出现(一般4次),也可以致出现1次。例如,Adobe Photoshop 生成的JPEG图片文件中只有1DHT标记段,里边包含了4个哈夫曼表;而Macromedia Fireworks生成的JPEG图片文件则有4DHT标记段,每个DHT标记段只有一个哈夫曼表。

 

l       DRIDefine Restart Interval定义差分编码累计复位的间隔

u  标记代码                                 2字节     固定值0xFFDD

u  包含2个具体字段:
 
数据长度                             2字节     固定值0x0004~两个字段的总长度
                                                           
即不包括标记代码,但包括本字段
 
MCU块的单元中的重新开始间隔
                                              2
字节     设其值为n,则表示每nMCU块就有一个
                                                           RSTn
标记。第一个标记是RST0,第二个是
                                                            RST1
等,RST7后再从RST0重复。

 

如果没有本标记段,或间隔值为0时,就表示不存在重开始间隔和标记RST

 

l       SOSStart of Scan,扫描开始 12字节

u  标记代码                          2字节     固定值0xFFDA

u  包含2个具体字段:
 
数据长度                      2字节     ~两个字段的总长度
                                                    
即不包括标记代码,但包括本字段
 
颜色分量数                 1字节     应该和SOF中的字段⑤的值相同,即:
                                                     1
:灰度图是;3 YCrCbYIQ4CMYK

                                                         JFIF中使用YCrCb,故这里颜色分量数恒为3
   
颜色分量信息

        a)
颜色分量ID           1字节
        b)
直流/交流系数表号 1字节     4位:直流分量使用的哈夫曼树编号
                                                       
4位:交流分量使用的哈夫曼树编号

  压缩图像数据
        a)
谱选择开始                     1字节     固定值0x00
        b)
谱选择结束                     1字节     固定值
0x3F
        c)
谱选择                            1字节     在基本JPEG中总为00

 

本标记段中,字段③应该重复出现,有多少个颜色分量(字段②),就出现多少次(一般为3次)。本段结束后,紧接着就是真正的图像信息了。图像信息直至遇到一个标记代码就自动结束,一般就是以EOI标记表示结束。

 

l        EOIEnd of Image,图像结束 2字节

u  标记代码                   2字节     固定值0xFFD9

 

这里补充说明一下,由于在JPEG文件中0xFF具有标志性的意思,所以在压缩数据流(真正的图像信息)中出现0xFF,就需要作特别处理。具体方法是,在数据0xFF后添加一个没有意义的0x00。换句话说,如果在图像数据流中遇到0xFF,应该检测其紧接着的字符,如果是

10x00,则表示0xFF是图像流的组成部分,需要进行译码;

20xD9,则与0xFF组成标记EOI,则图像流结束,同时图像文件结束;

30xD0~0xD7,则组成RSTn标记,则要忽视整个RSTn标记,即不对当前0xFF和紧接的0xDn两个字节进行译码,并按RST标记的规则调整译码变量;

30xFF,则忽视当前0xFF,对后一个0xFF再作判断;

4)其他数值,则忽视当前0xFF,并保留紧接的此数值用于译码。

 

二、        JPEG解码过程详解

 

下面来详细讲述JPEG文件的解码过程。

 

1.读入文件的相关信息

按照上述的JPEG文件数据存储方式,把要解码的文件的相关信息一一读出,为接下来的解码工作做好准备。参考方法是,设计一系列的结构体对应各个标记,并存储标记内表示的信息。其中图像长宽、多个量化表和哈夫曼表、水平/垂直采样因子等多项信息比较重要。以下给出读取过程中的两个问题。

 

1)整个文件的大体结构

JFIF格式的JPEG文件(*.jpg)的一般顺序为:

SOI(0xFFD8)

APP0(0xFFE0)

[APPn(0xFFEn)]可选

DQT(0xFFDB)

SOF0(0xFFC0)

DHT(0xFFC4)

SOS(0xFFDA)

压缩数据

EOI(0xFFD9)

 

2)字的高低位问题

JPEG文件格式中,一个字(16位)的存储使用的是 Motorola 格式, 而不是 Intel 格式。也就是说, 一个字的高字节(高8位)在数据流的前面, 低字节(低8位)在数据流的后面,与平时习惯的Intel格式不一样。.

 

3)读出哈夫曼表数据

a)理论说明

在标记段DHT内,包含了一个或者多个的哈夫曼表。对于单一个哈夫曼表,应该包括了三部分:

l 哈夫曼表ID和表类型
这个字节的值为一般只有四个0x000x010x100x11
0x00
表示DC直流0号表;
0x01
表示DC直流1号表;
0x10
表示AC交流0号表;
0x11
表示AC交流1号表。

l 不同位数的码字数量

JPEG文件的哈夫曼编码只能是1~16位。这个字段的16个字节分别表示1~16位的编码码字在哈夫曼树中的个数。

l 编码内容

这个字段记录了哈夫曼树中各个叶子结点的权。所以,上一字段(不同位数的码字数量)的16个数值之和就应该是本字段的长度,也就是哈夫曼树中叶子结点个数。

 

b)举例说明

以下面一段哈夫曼表数据举例说明(数据全部以16进制表示):

11  00  02  02  00  05  01  06  01  00  00  00  00  00  00  00  00
00  01  11  02  21  03  31  41  12  51   61 71 81 91 22 13  32

红色部分(第1字节)为哈夫曼表ID和表类型,其值0x11表示此部分数据描述的是AC交流1号表。

蓝色部分2~17字节)为不同位数的码字的数量。这16个数值实际意义为:没有1位和4位的哈夫曼码字;2位和3位的码字各有2个;5位码字有5个;6位和8位码字各有1个;7位码字各有6个;没有9位或以上的码字。

绿色部分18~34字节)为编码内容。由蓝色部分数据知道,此哈夫曼树有0+2+2+0+5+1+6+1=17个叶子结点,即本字段应该有17个字节。这段数据表示17个叶子结点按从小到大排列,其权值依次为011122133141……

 

4)建立哈夫曼树

a)理论说明

在读出哈夫曼表的数据后,就要建立哈夫曼树。具体方法为:

1)第一个码字必定为0
如果第一个码字位数为1,则码字为0
如果第一个码字位数为2,则码字为00
如此类推。

2)从第二个码字开始,
如果它和它前面的码字位数相同,则当前码字为它前面的码字加1
如果它的位数比它前面的码字位数大,则当前码字是前面的码字加1后再在后边添若干个0,直至满足位数长度为止。

b)举例说明

继续以上边的例子说明问题。

n           由于没有1位的码字,所以第一个码字的位数为2,即码字为00

n           由于2位的码字有两个,所以第二个码字位数仍为2,即码字为00+1=01

n           第三个码字为3位,比第二个码字长1位,所以第三个码字为:01+1=10,然后再添1个“0”,得100

n           ……

如此类推,最后得到这个哈夫曼树如下:

序号

码字长度

码字

权值

1

2

00

0x00

2

2

01

0x01

3

3

100

0x11

4

3

101

0x02

5

5

11000

0x21

6

5

11001

0x03

7

5

11010

0x31

8

5

11011

0x41

9

5

11100

0x12

10

6

111010

0x51

11

7

1110110

0x61

12

7

1110111

0x71

13

7

1111000

0x81

14

7

1111001

0x91

15

7

1111010

0x22

16

7

1111011

0x13

17

8

11111000

0x32

 

特别注意的是,如果中间有某个位数的码字缺失,例如没有4位码字,则应该在3位码字加1后,添加“00”补足5位,形成下一个5位码字。

 

在准备好所有的图片信息后,就可以对图片数据进行解码了。

 

2初步了解图像数据流的结构

1)理论说明

分析图像数据流的结构,笔者准备以一个从宏观到微观的顺序为读者详细剖析,即:

 

数据流à最小编码单元à数据单元与颜色分量。

 

a)         在图片像素数据流中,信息可以被分为一段接一段的最小编码单元(Minimum Coded UnitMCU)数据流。所谓MCU,是图像中一个正方矩阵像素的数据。

矩阵的大小是这样确定的:

查阅标记SOF0,可以得到图像不同颜色分量的采样因子,即YCrCb三个分量各自的水平采样因子和垂直采样因子。大多图片的采样因子为411111。其中,411即(2*2):(1*1):(1*1));111即(1*1):(1*1):(1*1)。记三个分量中水平采样因子最大值为Hmax,垂直采样因子最大值为Vmax,那么单个MCU矩阵的宽就是Hmax*8像素,高就是Vmax*8像素。

如果,整幅图像的宽度和高度不是MCU宽度和高度的整数倍,那么编码时会用某些数值填充进去,保证解码过程中MCU的完整性(解码完成后,可直接忽视图像宽度和高度外的数据)。

在数据流中,MCU的排列方法是从左到右,从上到下。

 

b)        每个MCU又分为若干个数据单元。数据单元的大小必定为8*8,所以每个MCU的数据单元个数为Hmax*Vmax

另外JPEG的压缩方法与BMP文件有所不同,它不是把每个像素的颜色分量连续存储在一起的,而是把图片分成YCrCb三张子图,然后分别压缩。而三个颜色分量的采样密度(即采样因子)可能一样(例如111)也可能不一样(例如411)。

每个MCU内部,数据的顺序是YCrCb。如果一个颜色分量有多个数据单元,则顺序是从左到右,从上到下。

 

2)举例说明

下面通过一幅32*35的图像,对上面两个问题列出两种采样因子的具体说明。

         

1 整张完整的图像(411                            2 将图像的MCU1放大

 

1及图3中灰色部分为实际图像大小(32px*35px);粗虚线表示各个MCU的分界;细虚线表示MCU内部数据单元的分界。

 

a)    采样因子为411

此时,Hmax=max211=2Vmax=max211=2。所以,MCU的宽为Hmax*8=16像素,高为Vmax*8=16像素。图像实际的宽刚好是2MCU,但高则稍稍大于2MCU的高度,所以要补足3MCU

在数据流中,MCU的顺序是MCU1àMCU2àMCU3àMCU4àMCU5àMCU6

每个MCU又分为Hmax*Vmax=2*2=4个数据单元。由于采样因子是411,即(2*2):(1*1):(1*1),所以Y分量的水平和垂直方向都是每2个像素采样2次;Cr分量和Cb分量的水平和垂直方向都是每2个像素采样1次。因此,在一个MCU来里边,Y分量有256个采样点,即4个完整的数据单元;Cr分量和Cb分量各自只有64个采样点。

如果以MCU1说明MCU数据的次序,则依次为Y1 Y2 Y5 Y6 Cr1256 Cb1256 。图2中全部256个点均是Y的采样点,红色部分为Cr分量和Cr分量的采样点。

换句话说,对于整张图片来说,数据流的数据依次是:
[Y1
Y2 Y5 Y6 Cr1256 Cb1256] [Y3 Y4 Y7 Y8 Cr3478 Cb3478] [Y9 Y10 Y13 Y14 Cr9101314 Cb9101314 ]、……

3 整张完整的图像(111

 

b)    采样因子为111

如图3Hmax=max111=1Vmax=max111=2。所以,MCU的宽为Hmax*8=8像素,高为Vmax*8=8像素。图像实际的宽刚好是4MCU,但高则稍稍大于4MCU的高度,所以要补足5MCU

在数据流中,MCU的顺序是:
MCU1
àMCU2àMCU3àMCU4à ………… àMCU18àMCU19àMCU20

每个MCU又分为Hmax*Vmax=1*1=1个数据单元,也就是一个数据单元就是一个MCU。由于采样因子是111,即(1*1):(1*1):(1*1),所以Y分量、Cr分量和Cb分量的水平和垂直方向都是每1个像素采样1次,也就是图象的每一个像素都是采样点。因此,在一个MCU来里边,Y分量、Cr分量和Cb分量各自有64个采样点。有

因此,对于整张图片来说,数据流的数据依次是:
[Y1
Cr1Cb1] [Y2  Cr2 Cb2] [Y3 Cr3Cb3] 、………… [Y19 Cr19Cb19][Y20 Cr20Cb20]

 

3.颜色分量单元的内部解码

1)理论说明

“颜色分量单元”是笔者为说明问题而建立的概念,指的是MCU中某个颜色分量中的一个8*8数据块,例如上面提到的Y1 Cr1Cb1 都是一个颜色分量单元。

图像数据流是以位(bit)为单位存储信息的。并且内部的数据都是在编码时通过正向离散余弦变换(FDCT)进行时空域向频率域变换而得到的结果,所以对于每个颜色分量单元都应该由两部分组成:1个直流分量和63个交流分量。

解码的过程其实就是哈夫曼树的查找过程。

首先查阅标记段SOS中的颜色分量信息,可以得出各个颜色分量对应使用的直流分量和交流分量使用的哈夫曼树编号。一般来说,
Y
分量:直流分量:直流0号哈夫曼树,交流分量:交流0号哈夫曼树;

Cr分量:直流分量:直流1号哈夫曼树,交流分量:交流1号哈夫曼树;

Cb分量:直流分量:直流1号哈夫曼树,交流分量:交流1号哈夫曼树。

 

颜色分量单元内部综合运用了RLE行程编码和哈夫曼编码来压缩数据。每个像素的数据流由两部分构成:编码和数值,并且两者基本以互相隔开方式出现(除非该编码的权值为零)。具体读入单个颜色分量单元的步骤如下:

a)从此颜色分量单元数据流的起点开始一位一位的读入,直到读入的编码与该分量直流哈夫曼树的某个码字(叶子结点)一致,然后用直流哈夫曼树查得该码字对应的权值。权值(共8位)表示该直流分量数值的二进制位数,也就是接下来需要读入的位数。

b)继续读入位数据,直到读入的编码与该分量交流哈夫曼树的某个码字(叶子结点)一致,然后用交流哈夫曼树查得该码字对应的权值。权值的高4位表示当前数值前面有多少个连续的零,低4位表示该交流分量数值的二进制位数,也就是接下来需要读入的位数。

c)不断重复步骤b,直到满足交流分量数据结束的条件。而结束条件有两个,只要满足其中一个即可:

当读入码字的权值为零,表示往后的交流变量全部为零;

②已经读入63个交流分量。

d)各个数值的译码是按下表进行的:

                  实际数值

编码长度

编码

0

-

-1,1

1

0,1

-3,-2,2,3 

2

00,01,10,11

-7,-6,-5,-4,4,5,6,7 

3

000,001,010,011,100,101,110,111

-15,……,-8,8,……,15

4

0000,……,0111,1000,……,1111

-31,……,-16,16,……,31

5

00000,……,01111,10000,……,11111

-63,……,-32,32,……,63

6

……

-127,……,-64,64,……,127 

7

……

-255,……,-128,128,……,255

8

……

-511,……,-256,256,……,511

9

……

-1023,……,-512,512,……,1023

10

……

-2047,……,-1024,1024,……,2047

11

……

-4095,……,-2048,2048,……,4095

12

……

-8191,……,-4096,4096,……,8191

13

……

-16383,……,-8192,8192,……,16383

14

……

-32767,……,-16384,16384,……,32767 

15

……

 

2)举例说明

下面举例说明以上几点。某个颜色分量单元数据如下:

D3 5E 6E 4D 35 f5 8A

若以二进制表示,则为:

1101 0011 0101 1110 0110 1110 0100 1101 0011 0101 1111 0101 1000 1010

假设该颜色分量单元对应以下直流哈夫曼树和交流哈夫曼树,则可将各个以位为单位的数据流拆分如下:

110 1001101 01 1 11001 101 11001 001 101 00 11010 1 1111010 11 00 01010

 

直流哈夫曼树                                                               交流哈夫曼树

序号

码字长度

码字

权值

1

2

00

0x00

2

2

01

0x01

3

2

10

0x02

4

3

110

0x07

5

4

1110

0x1e

6

5

11110

0x2e

序号

码字长度

码字

权值

1

2

00

0x00

2

2

01

0x01

3

3

100

0x11

4

3

101

0x02

5

5

11000

0x21

6

5

11001

0x03

7

5

11010

0x31

8

5

11011

0x41

9

5

11100

0x12

10

6

111010

0x51

11

7

1110110

0x61

12

7

1110111

0x71

13

7

1111000

0x81

14

7

1111001

0x91

15

7

1111010

0x22

16

7

1111011

0x13

17

8

11111000

0x32

 

详细说明一下:

读入数据流并对照直流哈夫曼树,第一个哈夫曼编码为110,其权值为6,所以往后读入6位数据“1001101”,译码成数值为77。因为每个颜色分量单元只有一个直流分量,所以下一个就是第一个交流分量了。

继续读入数据流并对照交流哈夫曼树,得哈夫曼编码为01,其权值为1,所以它的前面没有零,并往后读如1位数据“1”,译码成数值为1。如此往复,最后读到哈夫曼编码“00”,其权值为0,所以满足交流变量结束条件(最后剩余的“01010”对本颜色分量单元来说是冗余的,它可能属于下一个颜色分量单元)。

实际上,这段数据译码为:

77,(01),(05),(0-6),(0-3),(51),(23

因此,把它置于18*8的矩阵中应为:

77

1

5

-6

-3

0

0

0

0

0

1

0

0

3

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

 

4.直流系数的差分编码

把所有的颜色分量单元按颜色分量(YCrCb)分类。每一种颜色分量内,相邻的两个颜色分量单元的直流变量是以差分来编码的。也就是说,通过步骤3解码出来的直流变量数值只是当前颜色分量单元的实际直流变量减去前一个颜色分量单元的实际直流变量。也就是说,当前直流变量要通过前一个颜色分量单元的实际(非解码)直流分量来校正:

DCn=DCn-1+Diff

其中Diff为差分校正变量,也就是直接解码出来的直流系数。但如果当前颜色分量单元是第一个单元,则解码出来的直流数值就是真正的直流变量。

再次提醒的是,3个颜色分量的直流变量是分开进行差分编码的。也就是说,为1张图片解码时应设置3个独立的直流校正变量。另一个问题是,当数据流中出现标记RSTn,则3个颜色分量的直流差分校正变量Diff都需要重新复位到0

 

5.反量化

不同的颜色分量使用不同的量化表,这个可以从标记段SOF中的颜色分量信息字段查得。一般是Y分量使用量化表0,而CrCb两个分量共同使用量化表1

反量化的过程比较简单。只需要对8*8的颜色分量单元的64个值逐一乘以对应的量化表内位置相同的值则可。图像内全部的颜色分量单元都要进行反量化。

 

6Zig-zag编码

如果将反量化后的每个8*8颜色分量单元的每个元素编号,如下图4,那么各反Zig-zag编码的过程就是把矩阵元素按图5重新排列。

       

4 将颜色分量单元元素编码                          5 Zig-zag编码

 

关于量化和反Zig-zag编码的先后顺序,笔者查阅的几份资料有不同的见解。经过实践试验,解码的过程中,是应该直接用文件提供的量化表反量化矩阵数据,再将其反Zig-zag编码才能正确解码。

 

7.隔行的正负纠正

这个问题比较特别,因为在笔者认真阅读的几份资料中都没有提及此问题。而是笔者通过对已知图像进行JPEG编码压缩,然后和该图的JPEG文件数据对比发现的问题。具体原因不明。

实际上,就是必须对每个颜色分量单元的奇数行(每个颜色分量单元有8行,假设把它按01、……、67编出行号),即1357行,进行取相反数操作(正的变负,负的变正)。

 

8.反离散余弦变换

之前提到,文件中的数据是在编码时通过正向离散余弦变换(FDCT)进行时空域向频率域变换而得到的结果,所以现在解码就必须将其反向离散余弦变换(IDCT),就是把颜色分量单元矩阵中的频率域数值向时空域转换。并且,原来的频率域的矩阵大小为8*8,则经过反向离散余弦变换后,时空域的矩阵仍然是8*8

设正负纠正后的频率域矩阵为F[u][v],而反向离散余弦变换后的矩阵为f[i][j],其中0u,v,i,j7。具体使用的公式如下:

,其中

Cu= (当u=0),Cu=1(当u0);

Cv= (当v=0),Cu=1(当v0);

 

另外补充一下正向离散余弦变换的公式,用于编码:

 

9YCrCbRGB转换

要在屏幕上显示图像,就必须以RGB模式表示图像的颜色。所以,解码时需要把YCrCb模式向RGB模式转换。

正如前面提到,并不是每种颜色分量的采样因子都一样,所以转换时需要注意。如果采样因子是111,则每一个像素点的3个颜色分量都被采样,所以没有问题。但411的采样因子就不一样了。由“初步了解图像数据流的结构”一节中对411的采样因子的分析,可以知道一个MCU里有4Y分量单元,而Cr分量和Cb分量各自只有1个分量单元。以图2为例,仅有的一个Cr分量单元(红色的64个采样点)应该平铺用于4Y分量单元,即左上角16个值用于Y1,右上角16个值用于Y2,左下角16个值用于Y5,右下角16个值用于Y6。换句话说,一个Cr采样点服务于4Y采样点。对于Cb分量,道理一样。

另外,由于离散余弦变化要求定义域的对称,所以在编码时把RGB的数值范围从[0255]统一减去128偏移成[-128127]。因此解码时必须为每个分量加上128。具体公式如下:

R=Y                       +1.402*Cb     +128;

G=Y-0.34414*Cr    -0.71414*Cb   +128;

B=Y                       +1.772*Cb     +128;

还有一个问题,通过变换得出的RGB值可能超出了其定义域,所以要作出检查。如果大于255,则截断为255;如果小于0,则截断为0

 

下面补充RGB模式向YCrCb模式的公式:

Y =0.299*R            +0.587*G       +0.114*B    ;

Cr=   -0.1687*R      - 0.3313*G     +0.5*B  +128;

Cb=0.5 *R              - 0.4187*G     - 0.0813*B+128;

 

至此,每个MCU的解码已经完成。而每一个MCU如何组成一幅完整的图像,请参考初步了解图像数据流的结构分析。


参考文献

[1] 李才伟,中山大学计算机系多媒体课程教学课件.

[2]     张益贞,Visual C++实现MPEG/JPEG编解码技术.北京:人民邮电出版社

[3] CCITInformation Technology-digital Compression and Conding of Continuous-ton Still Images-requirements and Guidelineshttp://www.wotsit.org/download.asp?f=itu-1150PDF (访问日期:2007-1-1

[4] 公子御风,JFIF文件格式即JPEG文件交换格式(JPEG File Interchonge Format)http://cat1226.bokee.com/4574350.html (访问日期:2006-12-29

[5] 云风,JPEG 简易文档 V2.11http://rtornados.bokee.com/2442419.html (访问日期:2006-12-30


附录:JPEG定义的标记

标记名

标记代码

说明

帧开始标记,Start of Frame,非层次哈夫曼编码

SOF0

0xFFC0

基线离散余弦变换

SOF1

0xFFC1

扩展顺序离散余弦变换

SOF2

0xFFC2

递进离散余弦变换

SOF3

0xFFC3

空间顺序无损

帧开始标记,Start of Frame,层次哈夫曼编码

SOF5

0xFFC5

差分离散余弦变换

SOF6

0xFFC6

差分层次离散余弦变换

SOF7

0xFFC7

差分空间无损

帧开始标记,Start of Frame,非层次算术编码

JPEG

0xFFC8

JPEG扩展保留

SOF9

0xFFC9

扩展顺序离散余弦变换

SOF10

0xFFCA

递进离散余弦变换

SOF11

0xFFCB

空间顺序无损

帧开始标记,Start of Frame,层次算术编码

SOF13

0xFFCD

差分离散余弦变换

SOF14

0xFFCE

差分层次离散余弦变换

SOF15

0xFFCF

差分空间无损

其他标记

DHT

0xFFC4

定义哈夫曼树表

DAC

0xFFCC

定义算术编码表

RST0

OxFFD0

差分编码累计复位,共8

……

……

RST7

OxFFD7

SOI

OxFFD8

图像开始

EOI

OxFFD9

图像结束

SOS

0xFFDA

开始扫描,图像数据开始

DQT

0xFFDB

定义量化表

DNL

0xFFDC

定义线数

DRI

0xFFDD

定义差分编码累计复位的间隔

DHP

0xFFDE

定义层次级数

EXP

0xFFDF

展开参考图像

APP0

0xFFE0

为应用程序保留,共15

……

……

APP15

0xFFEE

JPG0

0xFFF0

JPEG扩展保留,共14

……

……

JPG13

0xFFFD

COM

0xFFFE

注释

TEM

0xFF01

算术编码中作临时之用

RES

0xFF02

保留,共189

……

……

RES

0xFFBF

 

 

 

从显示列表中移除 obj.parent && obj.parent.removeChild(obj)
查看有多少个子容器 obj.numChildren

添加子容器 obj.addChild(child)
添加子容器到指定zIndex obj.addChildAt(child, childZIndex) 起始值是0

从显示队列删除子容器 obj.removeChild(child)
从显示队列删除子容器 obj.removeChildAt(zIndex)

删除所有子容器 obj.removeChildren() 或者下面这样写
const numChild: number = sprcon.numChildren;
for (const t: number = 0; t < numChild; t++) {
    sprcon.removeChildAt(0);
}

同一个zIndex只能有一个DisplayObject

不同zIndex交换顺序
容器.swapChildren( 显示对象, 显示对象 )
容器.swapChildrenAt( 深度值, 深度值 )

指定zIndex
容器.setChildIndex( 显示对象, 新的深度值 );
obj.zIndex = 3; 十倍性能优化 
要给一个对象使用 zIndex,包含此显示对象的 DisplayObjectContainer 对象一定要设置 sortableChildren = true,开启排序功能,否则设置 zIndex 是无效的

let container = new egret.Sprite();
container.sortableChildren = true; //注意,一定要设置为true
this.addChild(container);

let texture: egret.Texture = RES.getRes("bird_png");
let b1 = new egret.Bitmap();
b1.texture = texture;
b1.x = 100;
this.addChild(b1);

let b2 = new egret.Bitmap();
b2.texture = texture;
b2.x = 270;
this.addChild(b2);

let b3 = new egret.Bitmap();
b3.texture = texture;
b3.x = 440;
this.addChild(b3);

b2.zIndex = 3; //将第二个图片设置到顶部


查询现实对象
容器.getChildAt( 深度值 );
容器.getChildByName( 显示对象 )
const _spr: egret.DisplayObject = sprcon.getChildAt(1);
_spr.alpha = 0.5;

通过graphics对象绘制图形

class GraphicsTest extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(event: egret.Event) {
        const shp: egret.Shape = new egret.Shape();
        shp.graphics.beginFill(0xff0000, 1);
        shp.graphics.drawRect(0, 0, 100, 200);
        shp.graphics.endFill();
        this.addChild(shp);
    }
}

shp.graphics.lineStyle(10, 0x00ff00); 提前声明画笔: 线条宽度, 颜色
shp.graphics.beginFill(0xff0000, 1); 红色, 1不透明, 开始填充
绘制形状
shp.graphics.endFill(); 结束填充

矩形
 shp.graphics.drawRect(0, 0, 100, 200);
 相对于shp的锚点(0,0), 宽度x=100,高度y=200

圆形
 drawCircle(x: number, y: number, radius: number): void;
 相对于shap的锚点, 指定圆心坐标(x,y),半径大小

直线
    moveTo(x: number, y: number): void; 起点坐标
    lineTo(x: number, y: number): void; 终点坐标(允许多条调用)

直线: 不需要开始填充, 只要结束填充

class GraphicsTest extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(event: egret.Event) {
        const shp: egret.Shape = new egret.Shape();
        shp.graphics.lineStyle(2, 0x00ff00);
        shp.graphics.moveTo(10, 10);
        shp.graphics.lineTo(167, 76);
        shp.graphics.lineTo(221, 118);
        shp.graphics.lineTo(290, 162);
        shp.graphics.lineTo(297, 228);
        shp.graphics.lineTo(412, 250);
        shp.graphics.lineTo(443, 174);
        shp.graphics.endFill();
        this.addChild(shp);
    }
}

二次贝赛尔曲线
moveTo(x: number, y: number): void; P0起点坐标
curveTo(x1: number, y1: number, x2: number, y2: number): void; P1控制点,P2结束点

class GraphicsTest extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(event: egret.Event) {
        const shp: egret.Shape = new egret.Shape();
        shp.graphics.lineStyle(2, 0x00ff00);
        shp.graphics.moveTo(50, 50);
        shp.graphics.curveTo(100, 100, 200, 50);
        shp.graphics.endFill();
        this.addChild(shp);
    }
}


圆弧
drawArc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise: boolean): void;
圆弧路径的圆心位置(x,y)
圆弧半径radius
圆弧起点的角度startAngle (从x 轴方向开始计算,以弧度为单位)
圆弧终点的角度endAngle
绘制方向anticlockwise (true,逆时针绘制圆弧, false,顺时针绘制, 通常false)

一个从 0 到 π 的圆弧(上半圆,扇形)
class GraphicsTest extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(event: egret.Event) {
        const shp: egret.Shape = new egret.Shape();
        shp.graphics.beginFill(0x1122cc);
        shp.graphics.drawArc(200, 200, 100, 0, Math.PI, true);
        shp.graphics.endFill();
        this.addChild(shp);
    }
}


线条型 弧线, 不填充颜色
const shape: egret.Shape = new egret.Shape();
shape.graphics.lineStyle(2, 0xffff00);
shape.graphics.drawArc(50, 50, 50, 0, Math.PI / 180 * 30, false);
shape.graphics.endFill();

拱形
画拱和画弧的区别就是:画拱需要填充图形,画弧不需要填充图形
const shape: egret.Shape = new egret.Shape();
shape.graphics.beginFill(0xff0000);
shape.graphics.drawArc(50, 50, 50, 0, Math.PI / 180 * 60, false);
shape.graphics.endFill();

扇形
const r: number = 50;
const shape: egret.Shape = new egret.Shape();
shape.graphics.beginFill(0xff0000);
shape.graphics.moveTo(r, r); //绘制点移动(r, r)点
shape.graphics.lineTo(r * 2, r); //画线到弧的起始点
shape.graphics.drawArc(50, 50, 50, 0, 260 * Math.PI / 180, false); //从起始点顺时针画弧到终点
shape.graphics.lineTo(r, r); //从终点画线到圆形。到此扇形的封闭区域形成
shape.graphics.endFill();

弧形进度条
class GraphicsText {
    private getArcProgress(): egret.Shape {
        const shape: egret.Shape = new egret.Shape();
        let angle: number = 0;
        egret.startTick(function (timeStamp: number) {
            angle += 1;
            changeGraphics(angle);
            angle = angle % 360;
            return true;
        }, this);
        function changeGraphics(angle) {
            shape.graphics.clear();
            shape.graphics.lineStyle(2, 0x0000ff, 1);
            shape.graphics.drawArc(50, 50, 50, 0, angle * Math.PI / 180, false);
            shape.graphics.endFill();
        }
    }
}

扇形进度条
class GraphicsText {
    private getSectorProgress(): egret.Shape {
        const shape: egret.Shape = new egret.Shape();
        let angle: number = 0;
        egret.startTick(function (timeStamp: number): boolean {
            angle += 1;
            changeGraphics(angle);
            angle = angle % 360;
            return true;
        }, this);
        return shape;
        function changeGraphics(angle) {
            shape.graphics.clear();
            shape.graphics.beginFill(0xff0000);
            shape.graphics.moveTo(50, 50);
            shape.graphics.lineTo(100, 50);
            shape.graphics.drawArc(50, 50, 50, 0, angle * Math.PI / 180, false);
            shape.graphics.lineTo(50, 50);
            shape.graphics.endFill();
        }
    }
}

不规则边框进度条
class GraphicsText {
    private getSectorProgress(): egret.DisplayObjectContainer {
        const container: egret.DisplayObjectContainer = new egret.DisplayObjectContainer();
        const w: number = 100;
        const h: number = 100;
        const r: number = Math.max(w, h) / 2 * 1.5;
        const bitmap = new egret.Bitmap(RES.getRes(key));
        container.addChild(bitmap);
        bitmap.width = w;
        bitmap.height = h;
        const shape: egret.Shape = new egret.Shape();
        shape.x = bitmap.width / 2;
        shape.y = bitmap.height / 2;
        bitmap.mask = shape;
        container.addChild(shape);
        let angle = 0;
        egret.startTick(function (timeStamp: number): boolean {
            angle += 1;
            changeGraphics(angle);
            angle = angle % 360;
            return true;
        }, this);
        return container;
        function changeGraphics(angle) {
            shape.graphics.clear();
            shape.graphics.beginFill(0x00ffff, 1);
            shape.graphics.lineTo(r, 0);
            shape.graphics.drawArc(0, 0, r, 0, angle * Math.PI / 180, true);
            shape.graphics.lineTo(0, 0);
            shape.graphics.endFill();
        }
    }
}

多个形状绘制, 互相是独立的,每一次绘制填充,都必须以 endFill() 结束,才能开始下一次绘制
 this.graphics.beginFill(0x0000ff);
this.graphics.drawRect(0, 0, 50, 50);
this.graphics.endFill();
this.graphics.beginFill(0x0000ff);
this.graphics.drawRect(50, 50, 50, 50);
this.graphics.endFill();
this.graphics.beginFill(0xff0000);
this.graphics.drawRect(50, 0, 50, 50);
this.graphics.endFill();
this.graphics.beginFill(0xff0000);
this.graphics.drawRect(0, 50, 50, 50);
this.graphics.endFill();

遮罩层
遮罩的作用是指定一个显示对象的可见区域,所有显示对象都具备遮罩功能。

1.矩形遮罩
矩形遮罩,即显示对象的可见区域是方形显示区域而非不规则显示区域。
用法为:将一个矩形对象赋值给显示对象的 mask 属性。
shp.mask = new egret.Rectangle(20, 20, 30, 50);

2.显示对象遮罩
显示对象遮罩,即显示对象的可见区域由另一个显示对象确定,可实现不规则遮罩。
用法为:将被遮罩显示对象的 mask 属性设置为遮罩对象:

mySprite.mask = maskSprite; //将maskSprite设置为mySprite的遮罩
mySprite.mask = null; // 删除遮罩

//画一个红色的正方形
const square: egret.Shape = new egret.Shape();
square.graphics.beginFill(0xff0000);
square.graphics.drawRect(0, 0, 100, 100);
square.graphics.endFill();
this.addChild(square);
//画一个蓝色的圆形
const circle: egret.Shape = new egret.Shape();
circle.graphics.beginFill(0x0000ff);
circle.graphics.drawCircle(25, 25, 25);
circle.graphics.endFill();
this.addChild(circle);
square.mask = circle; // 正方形的遮罩是圆形, 最终可见到红色的圆形

用作遮罩的显示对象可设置动画、动态调整大小。
遮罩显示对象不一定需要添加到显示列表中。
但是,如果希望在缩放舞台时也缩放遮罩对象,或者如果希望支持用户与遮罩对象的交互(如调整大小),则必须将遮罩对象添加到显示列表中。

清空绘图
清空绘图操作是将已经绘制的图像全部清空,可以执行 Graphics 中的 clear() 方法

shp.graphics.clear();

文本

1.普通文本

const label: egret.TextField = new egret.TextField();
label.text = "This is a text!";
this.addChild(label);

2.输入文本
把egret.TextField.type 改为 egret.TextFieldType.INPUT 即可输入

const txInput: egret.TextField = new egret.TextField();
txInput.type = egret.TextFieldType.INPUT;
txInput.width = 282;
txInput.height = 43;
txInput.x = 134;
txInput.y = 592;
txInput.textColor = 0x000000;
/// 注意_container是事先建立好的一个显示容器,即 egret.Sprite,并且已经添加到显示列表中
this._container.addChild(txInput);

聚焦 textField.setFocus();

const textIput: egret.TextField = new egret.TextField();
textIput.type = egret.TextFieldType.INPUT;
this.addChild(textIput);

const button: egret.Shape = new egret.Shape();
button.graphics.beginFill(0x00cc00);
button.graphics.drawRect(0, 0, 100, 40);
button.graphics.endFill();
button.y = 50;
this.addChild(button);
button.touchEnabled = true;
button.addEventListener(egret.TouchEvent.TOUCH_BEGIN, (e) => {
    textIput.setFocus();
}, this);

设置输入文本的样式为 text,密码(默认输入英文字母),电话(数字输入框)

text.inputType = egret.TextFieldInputType.TEXT;
egret.TextFieldInputType.PASSWORD
egret.TextFieldInputType.TEL

3.位图文本 egret.BitmapText

位图文本是借助位图字体渲染的文本类型。egret.BitmapText 类表示位图文本类型。

加载位图字体文件, 将加载后的字体对象赋值给 egret.BitmapText 的 font 属性。

class Main extends egret.DisplayObjectContainer {
    public constructor() {
        super();
        this.once(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    }
    private onAddToStage(evt: egret.Event) {
        RES.getResByUrl("resource/cartoon-font.fnt", this.onLoadComplete, this, RES.ResourceItem.TYPE_FONT);
    }
    private _bitmapText: egret.BitmapText;
    private onLoadComplete(font: egret.BitmapFont): void {
        this._bitmapText = new egret.BitmapText();
        this._bitmapText.font = font;
        this._bitmapText.x = 50;
        this._bitmapText.y = 300;
        this.addChild(this._bitmapText);
    }
}

对齐方式

label.textAlign = egret.HorizontalAlign.CENTER;  水平
label.verticalAlign = egret.VerticalAlign.MIDDLE; 居中

字体

label.fontFamily = "Impact";

字号

label.size = 20

egret.TextField.default_size 属性可设置全局的默认文本字号大小。
egret.TextField 的对象自身尺寸会根据首次设置的文本内容自动计算

字体颜色, 默认白色

label.textColor = 0xff0000;

文字描边

label.strokeColor = 0x0000ff;
label.stroke = 2; 描边粗度

加粗

label.bold = true

斜体

label.italic =true

文字流, 不同字符, 不同的颜色样式

const tx: egret.TextField = new egret.TextField;
tx.width = 400;
tx.x = 10;
tx.y = 10;
tx.textColor = 0;
tx.size = 20;
tx.fontFamily = "微软雅黑";
tx.textAlign = egret.HorizontalAlign.CENTER;
tx.textFlow = <Array<egret.ITextElement>>[
    { text: "Text in ", style: { "size": 20 } },
    { text: "Egret", style: { "textColor": 0x336699, "size": 60, "strokeColor": 0x6699cc, "stroke": 2 } },
    { text: " can ", style: { "fontFamily": "Impact" } },
    { text: "be set ", style: { "fontFamily": "Times New Roman" } },
    { text: "to a ", style: { "textColor": 0xff0000 } },
    { text: "\n" },
    { text: "variety ", style: { "textColor": 0x00ff00 } },
    { text: "of ", style: { "textColor": 0xf000f0 } },
    { text: "styles ", style: { "textColor": 0x00ffff } },
    { text: "with", style: { "size": 56 } },
    { text: "different ", style: { "size": 16 } },
    { text: "colors, ", style: { "size": 26 } },
    { text: "\n" },
    { text: "fonts ", style: { "italic": true, "textColor": 0x00ff00 } },
    { text: "and ", style: { "size": 26, "textColor": 0xf000f0, fontfamily = "Quaver" } },
    { text: "sizes", style: { "italic": true, "textColor": 0xf06f00 } }
];
this.addChild(tx);

文字超链接, 必须用textFlow, 且可触摸

tx.textFlow = new Array<egret.ITextElement>(
{ text: "这段文字有链接", style: { "href": "http://www.egret.com/" } },
{ text: "\n这段文字没链接", style: {} }
);
tx.touchEnabled = true;

fdisk -l
mkfs.ext4 /dev/vdb
mount /dev/vdb /data
vi /etc/fstab
/dev/vdb /data ext4 defaults 0 0

交换分区是

/var/swapfile swap swap defaults 0 0