-
MIDI文件格式分析(补充和勘误)
2018-04-12 15:40:10MIDI文件格式分析(补充和勘误) 本文是对《MIDI文件格式分析》博客链接的一点补充: ...文件头一般包括文件的类型,因为Midi文件仅以.mid为扩展名的就有0类和1类两种,而大家熟悉的位图文件的格式就更多了,所以才会...MIDI文件格式分析(补充和勘误)
本文是对《MIDI文件格式分析》博客链接的一点补充:
- 原文复制
- vim编辑二进制文件的方法
- 文中制作的midi文件内容和改动
原文复制
MIDI文件属于二进制文件,这种文件一般都有如下基本结构: 文件头+数据描述
文件头一般包括文件的类型,因为Midi文件仅以.mid为扩展名的就有0类和1类两种,而大家熟悉的位图文件的格式就更多了,所以才会出现文件头这种东西。
而数据描述部份是主体,我们现在来一起分析它的结构:
在每个Midi文件的开头都有如下内容,它们的十六进制代码为:“4d 54 68 64 00 00 00 06 ff ff nn nn dd dd”。
前四个是ASCII字符“MThd”是用来鉴别是否Midi文件,而随后的四个字节是指明文件头描述部分的字节数,它总是6,所以一定是“00 00 00 06”,以下是剩余部分的含义:
以上就是MIDI文件头了,后面的所有内容都是真正做事的,我们先来看看它的构成。MIDI的数据是由若干个格式相同的子数据构成的,这些子数据在多音轨的格式中记录了一个轨道的所有信息。多加一个音轨,就简单地把数据追加在前一音轨的后面就可以了,不过不要忘记更改文件头中的nn nn(轨道数)。
先看全局音轨。全局音轨包括歌曲的附加信息(比如标题和版权)、歌曲速度和系统码(Sysx)等内容。
不管是全局音轨还是含有音符的音轨,都以“4D 54 72 6B”开头,它其实是ASCII字符“MTrk”,其后跟着一个4个字节的整数,它标志了该轨道的字节数,这不包括前面的4个字节和本身的4个字节。这一点,我们可以在后面的例子中去理解。
接着就是记录数据的地方了,每一个数据有着相同的结构:时间差+事件。
所谓时间差,指的是前一个事件到该事件的时间数,它的单位是tick(MIDI的最小时间单位)。它的构成比较特殊,这里要用二进制来说明。
一个字节有8位,如果仅使用7位,它可以表示0~127这128个数,而剩下的一位,则用来作为标志。如果要表示的数在以上范围,则这个标志为0,这时,一个7位的字节可以表示0~127tick。如果要表示的数超出了这个范围(比如240),则把标志设置成1,然后记录下高7位,剩下的留给下一个字节,在该例中240可以分解成128*1+112,这里的1就是第一个字节要记录的,加上标志位,应该为10000001,即十六进制的81;而112是下一个字节记录的,它的十六进制为70:所以要表示240这个时间,要写成81 70。同理,如果要表示65535tick,则可以先计算出65535=1282*3+1281*127+1280*127,然后得出结果:83 FF 7F。由此,我们反过来也可以知道如何确定时间差:只要标志位为0,则表示结束读取时间差。比如82 C0 03表示1282*2+1281*64+1280*3=40963,如果基本时间为120,则有341:043个四分音符。
以这种方式记录整数的字节称为动态字节,它根据记录的整数改变自身的长度,这在后面还要用到,所以必须熟练计算。
看完了这么麻烦的东西,我们再来看个更麻烦的东西:事件。在这些标准的解释后面,我们会通过一些例子来进一步掌握这些内容。
事件大体上可以分为音符、控制器和系统信息这几个种类。对于这些事件,都有统一的表达结构:种类+参数。
对于一个音符,由于它的有效范围是0~127,所以直接用00~7F作为“种类”,可以认为是个音符,比如3C表示中央C。而一个音符的最重要的参数是力度(也叫速度:velocity)。比如,3C 64 表示一个力度为十进制100的中央C音符。
因为一个字节有8位,所以剩余的一位如果置1,再联合其他的7位,则可以表示各种信息。我们暂且无视一个音轨到底是全局的还是用于记录音符的。它们归根结底都是用来记录各种事件的,只不过有些应出现在全局音轨比较合乎逻辑而已。既然这样,我们就可以从下面的表来看事件:
下表中,x表示音轨0~F,比如81表示松开第二轨的音符。
下表详细地列出了FF的详细情况,对于字节数由数据决定的情况,表中以“–”表示。
这些就是MIDI结构的全部内容,在下一讲,我们将通过一个实例来分析。要书写二进制(十六进制)文件,应该准备好一些工具,比如我自己用的是VC++,因为学习MIDI格式无非是想写它的软件,既然VC++可以编辑二进制文件,就将就着用吧。其次,应该找个可以编辑和播放MIDI文件的软件,比如Cakewalk,这样就可以开始了。
首先书写文件头“4d 54 68 64 00 00 00 06”,我们直接写同步多音轨的格式,先写1个音轨,并以120为一个音符的基本时间。这样,随后的字节是:“00 01 00 01 00 78”。
现在,如果用Cakewalk打开会失败,因为我们指定的音轨数为1,但是并没有书写任何音轨,如果改成“00 01 00 00 00 78”再打开,就不会出问题了。所以,今后如果更改了音轨数,千万不要忘记向“上头”汇报。
把轨道数改回01,继续我们的实验。先写音轨的头信息:“4D 54 72 6B”(MTrk),因为我们还不能确定后面有多少字节,所以先把它假设成“00 00 00 00”,以后再回来改。
我们先尝试设置歌曲的速度和节拍等基本信息。假设一个四分音符的时间是半秒,即0.5*106微秒。它的十六进制数是07A120,再看事件表,设置速度是51,但是在其前面必须是FF,然后它须要3个字节作为参数,因此字节数为03,参数为“07 A1 20”,也就是“FF 51 03 07 A1 20”。这是事件部分,不要忘记在其之前有个参数——时间差。这是一开始就应该设置的参数,因此时间差为00。所以,完整的事件应该是“00 FF 51 03 07 A1 20”,我们把这一段追加在Midi文件末尾。
这时先不要急着用Cakewalk验证,因为我们还没有向“上级”报告,没错,把前面表示字节数的“00 00 00 00”改成“00 00 00 07”,如果用VC++作为二进制文件的编辑器,选择了事件后,可以在状态栏看到选择的字节长。保存后,再用Cakewalk打开,就可以看见速度是120。
我们再来设置节拍和调号,因为一般用Cakewalk新建一个Midi会默认地设置成4/4,C大调,我们就改设成6/8,A大调。查阅事件表知道,58和59是分别用来设置节拍和调号的。虽然设置节拍的参数很多,但在现在的系统中,后两个参数是被忽略的,而且Cakewalk还会对其进行修正。因此,我们只要设置好实际有用的就可以了。分子是6,分母是8,所以第一个参数是06,第二个参数是03(23=8)。最后,补上前面的时间差和后面的两个被忽略的参数,它应该是“00 FF 58 04 06 03 00 00”;再看调号,A调有3个升号,因此可以这样的事件可以表示为“00 FF 59 02 03 00”。事实上,大小调是个被忽略的参数。我们统计一下至今为止事件的字节数,然后更改前面的参数,即把“00 00 00 07”改成“00 00 00 15”。保存后用Cakewalk打开,再进入五线谱窗口,就可以马上验证了。细心的你可能已经发现,进入五线谱窗口前和平常有些延迟,这是因为我们并没有设置好那些可以忽略的字节,而Cakewalk就是在对其进行重新验证,这一点,我们以后再讨论。
用同样的方法,您可以很容易地设置歌曲的标题和版权,这作为一个练习,在这里就不多写了。我们现在学习写一个含有音符的轨道。首先您应该知道要做哪些事:1、写新音轨的信息头;2、向上级汇报多了一个音轨。接下来,我们开始写入一个简单的音符。
假设向第一拍写一个中音A,这里可能要先说明一下,音符是从C0开始一起向上数的,数到中央C(C5)是十六进制的3C,则中音A应该为45,在附件中有详细的计算方法。我们知道在音乐中一个音符通常有三个属性:音高、力度和时值。可是我们在事件表中并没有看见有什么可以直接设置音符时值的标志。不错,事实上,音符的时值是由按下的时间和松开的时间决定的。我们假设要写入一个八分音符。它的Tick数是四分音符的一半,即60,十六进制表示成3C。我们先来看看与音符有关的标志。
在事件表中,9x是用来打开一个音符,我们这里假设使用第7个通道(注意到MIDI有16个通道(Channel),而第10个被默认地用作打击乐,所以,我们在这个阶段(没有学习Sysx之前),先不要使用第10个通道),则9x中的x是6;再看它的参数,一个是音符,这里我们写入45,第二个是力度,我们用70,因为是一开始就触发的,所以前面的时间差还是00。这样我们就在第5个通道以力度112按下了一个中音A。对应的字节描述是“00 96 45 70”。它的时值不用想都知道一定是0,这取决于什么时候把它松开。
特别地,如果一个音符的力度为0,则MIDI认为用户想松开这个键,因为9x已经打开了通道,所以我们直接写入一个带00力度的同一音符就可以决定这个音符的时值了。根据前面的分析,这个时间差应该是3C,所以我们在写入3C后写上音符45和它的力度00,即“3C 45 00”。统计好字节数并向这一轨的头信息中更新,然后保存到磁盘,用Cakewalk打开并进入事件列表窗口便可以验证了。
在这个基础上,我们再尝试在A的后面增加一个四分音符中音#G。因为96已经打开了通道,我们没有必要每次都使用9x,只要输入事件信息即可。对于中音#G,它的十六进制是44,相对刚才输入00力度的A来说时间差为00,因此可以表示成“00 44 64”,这里我们已经假设力度为100;然后是松开它,因为是四分音符,所以时间差是78H,别忘记力度是00,它的字节应表示成“78 44 00”,做好后面的工作,然后验证看对不对。
我们再做个稍微复杂一点的实验:在原来的基础上,在同一轨的第一拍加上一个附点四分中音D。这里就不能再使用追加的方法了,因为前面的事件已经过了3个八分音符的时间,无论再加上什么,都只会发生在后面,所以我们要在前面插入一些字节。
9x已经打开了通道,我们直接在9x按下的音符后加上一个音符事件“00 3E 64”,这里的00显然是个时间差,3E是中音D,64是力度,也就是说,在按下中音A的同时按下了中音D。我们又按下一个键了,要在什么时候,在哪里松开才能保证输入的是个附点八分音符呢?首先,它的时值是3个八分音符,即180,这里还有一点要注意,180是个大于128的数,它的动态字节就应该表示成“81 34”,在哪里输入才好呢?如果你觉得在按下D后输入,或者在任何什么地方输入这个时间差,然后再写上“3E 00”可以表示松开的话就完全误解了时间差的概念。其实,我们只要简单地在松开#G的时候松开D就可以了,所以应该在末尾补上“00 3E 00”。统计好字节数后到Cakewalk中去验证验证吧。这里附有我们目前写下的Midi文件样本。
到目前为此,我们应该可以输入任何形式的音符了,不过MIDI除了音符以外,还可以包括各种控制器和系统码,它们的地位不亚于音符,我们现在马上学习如何使用控制器。
控制器比音符要简单多了,我们尝试在#G前加入相位控制(Pan),它的十进制代码是10,十六进制是0A,我们将参数设置成111,即十六进制的6F。首先查得控制器是Bx,这里的x和上面一样,也是6。接下来写入控制器号0A,然后是参数6F,别忘了前面的时间差是00。所以这段字节是“00 B6 0A 6F”,它应放在松开#G的事件之前,与按下#G同时。不过,一旦使用了非音符,而后面还有音符事件时,则必须重新通知打开音符,这说起来复杂,做起来还是比较容易的,我们只要稍微改写下一个音符事件即可:原本是“时间差+音符+力度”,我们加入一个打开音符的标志,成为“时间差+9x+音符+力度”即可。校验过头信息后,去Cakewalk中进行更进一步的检验便知它的可行。
其实,时间差为00的控制事件如果出现的时间也是00,则在Cakewalk中会尽可能地把它们放在轨道信息中,而不在事件列表中重复。我们可以利用这一点给音轨设置初始乐器和音量,这就作为一个练习,在此就不再说明了。
至于其他的诸如触后键等与控制器类似的格式在此就不多说了。在这里有必要提醒的是滑音。滑音的乐理范围是-8192~8191,但是在使用时参数是个正数,比如要设置成0,则应该是0-(-8192)=8192,它才是参数。8192的7位双字节表示成“8192 mod 128=00H;8192 div 128=40H”。如果时间差是00,则应表示成“00 E6 00 40”
最后我们看看系统码。系统码的构成本来是“F0 厂家ID 设备号码 格式代码 传送命令 具体参数 F7”,而在文件中,则不以开头的“F0”为系统码,而字节数也仅记录剩余的系统码,比如XG的复位码是“F0 43 10 4C 00 00 7E 00 F7”,则在文件中应写成“00 F0 08 43 10 4C 00 00 7E 00 F7”,其中第一个00是时间差,F0是系统码标志,08是后面的字节数。有一点要注意的是,几个系统码不可以写在一起,比如“00 F0 0D 43 10 4C 00 00 7E 00 F7 F0 AA BB CC F7”或“00 F0 0C 43 10 4C 00 00 7E 00 F7 AA BB CC F7”都是不好的写法。如果存在以上系统码集,可以分成两个事件:“00 F0 08 43 10 4C 00 00 7E 00 F7 00 F0 04 AA BB CC F7”
当然系统码可以写在任何音轨,不过一般我们会考虑把歌曲播放前发送的系统码写在全局音轨中,并把时间差设成00。
作为一个参考,这里再附上一个MIDI样本。
虽然我们只讨论了同步多音轨的格式,其实对于其他两种,比如较常见的单音轨格式,所有的事件只写在一个音轨中,即只要存在一个“MTrk”就足够了。而相对地,用于记录音轨数的两个字节也永远为“00 01”,连续事件如果出现的通道不同,也必须重新指定通道(8x~Ex)。在此不详细讨论了。
音符十六进制的计算 关于乐器选择 RPN和NRPN
在MIDI中,中央C是C5,最低音是C0,最高音是G5,要计算任何一个音符对应的十六进制,可以使用这个公式:
假设音符是NO,表示第O八度的音名N,比如G2中,N为G,O为2,则它的十进制为O*12+N,N的值为了简便起见,用下表给出:
这样G2的十进制值为2*12+7=31,十六进制为1F。
若知道音符的十六进制,也可以很容易求出音符,比如64(16)=100(10),
而100 div 12=8,100 mod 12=4,对应音符为E8。写成公式就是:
N=B mod 12;O=B div 12;(设B为表示音符的字节的十进制数)
乐器是MIDI中比较重要的因素,要选择所有的乐器不仅仅只是使用Cx号标志就能完成的,还必须结合BankSelect(乐队选择),而BankSelect其实是由0号控制器和32号控制器完成的,它们的十六进制代码分别是00和20.比如要选择出XG标准中的Slow Violin,它在第08H个乐队中的第28H号乐器中,所以它的完整代码应为“00 B0 00 00 00 20 08 00 C0 28”。我们来分析它的构成:这里我们假设时间差为00,所有信息都发给通道00,所以第一个00是时间差,B0是打开控制器的标志,并指定发送到通道00,接下来的00 00,是由控制器号00和控制器参数00构成的,它事实上是表示0号控制器的参数为0,即BankSelect-MSB的参数为0,然后是下一个事件,它是“00 20 08”,即时间差是00,使用20H号控制器,参数为08H,即BankSelect-LSB的参数是08H,这样就指定了Bank(乐队)。再下来就是“00 C0 28”,就是所谓的Patch Change事件了,它的时间差为00,参数是28H。这样就完成了标准的乐器选择。
事实上,它是由三个事件共同完成的,如果某次选择乐器和上次的乐器有共同的参数,可以不必重复使用相关的操作。
在本例中,您可能发现了一点,当连续使用同类操作时可以不必每次指定操作种类,比如这里的连续再次使用控制器,所以第二个控制器并没有使用B0作为标志,而是直接使用控制器号码和它的参数。这一点和音符是一样的。其实,如果连续使用Patch Change事件(我是说如果),则也可不必每次都写Cx,打开了一次就可以了;不过就Patch Change事件而言,连续地更换乐器的结果是仅最后一个有效而已。
在前面的文档中并没有提及RPN和NRPN,其实它们是由四个连续的控制器来实现的。我们假设要使用RPN事件的Coarse Turning,它的参数假设是4096,则它的字节是“00 B0 65 00 00 64 02 00 06 20 00 26 00”,我们来分析这段字节:首先我们先看看RPN是由哪四个控制器组成的——首先设置RPN-MSB和RPN-LSB,分别对应的控制器是65H和64H,Coarse Turning的RPN码是2,所以MSB为0,LSB为2;然后是设置Data Entry MSB和Data Entry LSB,对应的控制器是06H和26H,而4096 div 128=32,4096 mod 128=0,对应的十六进制数分别是20H和00H。因此就构成了上面的字节。而NRPN和RPN原理是一样的,只不过不用RPN-MSB和RPN-LSB,而改用NRPN-MSB和NRPN-LSB而已,它们对应的十六进制数分别为63H和62H。
原文
MIDI文件属于二进制文件,这种文件一般都有如下基本结构: 文件头+数据描述文件头一般包括文件的类型,因为Midi文件仅以.mid为扩展名的就有0类和1类两种,而大家熟悉的位图文件的格式就更多了,所以才会出现文件头这种东西。
而数据描述部份是主体,我们现在来一起分析它的结构:
在每个Midi文件的开头都有如下内容,它们的十六进制代码为:“4d 54 68 64 00 00 00 06 ff ff nn nn dd dd”。
前四个是ASCII字符“MThd”是用来鉴别是否Midi文件,而随后的四个字节是指明文件头描述部分的字节数,它总是6,所以一定是“00 00 00 06”,以下是剩余部分的含义:
以上就是MIDI文件头了,后面的所有内容都是真正做事的,我们先来看看它的构成。
MIDI的数据是由若干个格式相同的子数据构成的,这些子数据在多音轨的格式中记录了一个轨道的所有信息。多加一个音轨,就简单地把数据追加在前一音轨的后面就可以了,不过不要忘记更改文件头中的nn nn(轨道数)。
先看全局音轨。全局音轨包括歌曲的附加信息(比如标题和版权)、歌曲速度和系统码(Sysx)等内容。
不管是全局音轨还是含有音符的音轨,都以“4D 54 72 6B”开头,它其实是ASCII字符“MTrk”,其后跟着一个4个字节的整数,它标志了该轨道的字节数,这不包括前面的4个字节和本身的4个字节。这一点,我们可以在后面的例子中去理解。
接着就是记录数据的地方了,每一个数据有着相同的结构:时间差+事件。
所谓时间差,指的是前一个事件到该事件的时间数,它的单位是tick(MIDI的最小时间单位)。它的构成比较特殊,这里要用二进制来说明。
一个字节有8位,如果仅使用7位,它可以表示0~127这128个数,而剩下的一位,则用来作为标志。如果要表示的数在以上范围,则这个标志为0,这时,一个7位的字节可以表示0~127tick。如果要表示的数超出了这个范围(比如240),则把标志设置成1,然后记录下高7位,剩下的留给下一个字节,在该例中240可以分解成128*1+112,这里的1就是第一个字节要记录的,加上标志位,应该为10000001,即十六进制的81;而112是下一个字节记录的,它的十六进制为70:所以要表示240这个时间,要写成81 70。同理,如果要表示65535tick,则可以先计算出65535=1282*3+1281*127+1280*127,然后得出结果:83 FF 7F。由此,我们反过来也可以知道如何确定时间差:只要标志位为0,则表示结束读取时间差。比如82 C0 03表示1282*2+1281*64+1280*3=40963,如果基本时间为120,则有341:043个四分音符。
以这种方式记录整数的字节称为动态字节,它根据记录的整数改变自身的长度,这在后面还要用到,所以必须熟练计算。
看完了这么麻烦的东西,我们再来看个更麻烦的东西:事件。在这些标准的解释后面,我们会通过一些例子来进一步掌握这些内容。
事件大体上可以分为音符、控制器和系统信息这几个种类。对于这些事件,都有统一的表达结构:种类+参数。
对于一个音符,由于它的有效范围是0~127,所以直接用00~7F作为“种类”,可以认为是个音符,比如3C表示中央C。而一个音符的最重要的参数是力度(也叫速度:velocity)。比如,3C 64 表示一个力度为十进制100的中央C音符。
因为一个字节有8位,所以剩余的一位如果置1,再联合其他的7位,则可以表示各种信息。我们暂且无视一个音轨到底是全局的还是用于记录音符的。它们归根结底都是用来记录各种事件的,只不过有些应出现在全局音轨比较合乎逻辑而已。既然这样,我们就可以从下面的表来看事件:
下表中,x表示音轨0~F,比如81表示松开第二轨的音符。
下表详细地列出了FF的详细情况,对于字节数由数据决定的情况,表中以“–”表示。
这些就是MIDI结构的全部内容,在下一讲,我们将通过一个实例来分析。
要书写二进制(十六进制)文件,应该准备好一些工具,比如我自己用的是VC++,因为学习MIDI格式无非是想写它的软件,既然VC++可以编辑二进制文件,就将就着用吧。其次,应该找个可以编辑和播放MIDI文件的软件,比如Cakewalk,这样就可以开始了。
首先书写文件头“4d 54 68 64 00 00 00 06”,我们直接写同步多音轨的格式,先写1个音轨,并以120为一个音符的基本时间。这样,随后的字节是:“00 01 00 01 00 78”。
现在,如果用Cakewalk打开会失败,因为我们指定的音轨数为1,但是并没有书写任何音轨,如果改成“00 01 00 00 00 78”再打开,就不会出问题了。所以,今后如果更改了音轨数,千万不要忘记向“上头”汇报。
把轨道数改回01,继续我们的实验。先写音轨的头信息:“4D 54 72 6B”(MTrk),因为我们还不能确定后面有多少字节,所以先把它假设成“00 00 00 00”,以后再回来改。
我们先尝试设置歌曲的速度和节拍等基本信息。假设一个四分音符的时间是半秒,即0.5*106微秒。它的十六进制数是07A120,再看事件表,设置速度是51,但是在其前面必须是FF,然后它须要3个字节作为参数,因此字节数为03,参数为“07 A1 20”,也就是“FF 51 03 07 A1 20”。这是事件部分,不要忘记在其之前有个参数——时间差。这是一开始就应该设置的参数,因此时间差为00。所以,完整的事件应该是“00 FF 51 03 07 A1 20”,我们把这一段追加在Midi文件末尾。
这时先不要急着用Cakewalk验证,因为我们还没有向“上级”报告,没错,把前面表示字节数的“00 00 00 00”改成“00 00 00 07”,如果用VC++作为二进制文件的编辑器,选择了事件后,可以在状态栏看到选择的字节长。保存后,再用Cakewalk打开,就可以看见速度是120。
我们再来设置节拍和调号,因为一般用Cakewalk新建一个Midi会默认地设置成4/4,C大调,我们就改设成6/8,A大调。查阅事件表知道,58和59是分别用来设置节拍和调号的。虽然设置节拍的参数很多,但在现在的系统中,后两个参数是被忽略的,而且Cakewalk还会对其进行修正。因此,我们只要设置好实际有用的就可以了。分子是6,分母是8,所以第一个参数是06,第二个参数是03(23=8)。最后,补上前面的时间差和后面的两个被忽略的参数,它应该是“00 FF 58 04 06 03 00 00”;再看调号,A调有3个升号,因此可以这样的事件可以表示为“00 FF 59 02 03 00”。事实上,大小调是个被忽略的参数。我们统计一下至今为止事件的字节数,然后更改前面的参数,即把“00 00 00 07”改成“00 00 00 15”。保存后用Cakewalk打开,再进入五线谱窗口,就可以马上验证了。细心的你可能已经发现,进入五线谱窗口前和平常有些延迟,这是因为我们并没有设置好那些可以忽略的字节,而Cakewalk就是在对其进行重新验证,这一点,我们以后再讨论。
用同样的方法,您可以很容易地设置歌曲的标题和版权,这作为一个练习,在这里就不多写了。我们现在学习写一个含有音符的轨道。首先您应该知道要做哪些事:1、写新音轨的信息头;2、向上级汇报多了一个音轨。接下来,我们开始写入一个简单的音符。
假设向第一拍写一个中音A,这里可能要先说明一下,音符是从C0开始一起向上数的,数到中央C(C5)是十六进制的3C,则中音A应该为45,在附件中有详细的计算方法。我们知道在音乐中一个音符通常有三个属性:音高、力度和时值。可是我们在事件表中并没有看见有什么可以直接设置音符时值的标志。不错,事实上,音符的时值是由按下的时间和松开的时间决定的。我们假设要写入一个八分音符。它的Tick数是四分音符的一半,即60,十六进制表示成3C。我们先来看看与音符有关的标志。
在事件表中,9x是用来打开一个音符,我们这里假设使用第7个通道(注意到MIDI有16个通道(Channel),而第10个被默认地用作打击乐,所以,我们在这个阶段(没有学习Sysx之前),先不要使用第10个通道),则9x中的x是6;再看它的参数,一个是音符,这里我们写入45,第二个是力度,我们用70,因为是一开始就触发的,所以前面的时间差还是00。这样我们就在第5个通道以力度112按下了一个中音A。对应的字节描述是“00 96 45 70”。它的时值不用想都知道一定是0,这取决于什么时候把它松开。
特别地,如果一个音符的力度为0,则MIDI认为用户想松开这个键,因为9x已经打开了通道,所以我们直接写入一个带00力度的同一音符就可以决定这个音符的时值了。根据前面的分析,这个时间差应该是3C,所以我们在写入3C后写上音符45和它的力度00,即“3C 45 00”。统计好字节数并向这一轨的头信息中更新,然后保存到磁盘,用Cakewalk打开并进入事件列表窗口便可以验证了。
在这个基础上,我们再尝试在A的后面增加一个四分音符中音#G。因为96已经打开了通道,我们没有必要每次都使用9x,只要输入事件信息即可。对于中音#G,它的十六进制是44,相对刚才输入00力度的A来说时间差为00,因此可以表示成“00 44 64”,这里我们已经假设力度为100;然后是松开它,因为是四分音符,所以时间差是78H,别忘记力度是00,它的字节应表示成“78 44 00”,做好后面的工作,然后验证看对不对。
我们再做个稍微复杂一点的实验:在原来的基础上,在同一轨的第一拍加上一个附点四分中音D。这里就不能再使用追加的方法了,因为前面的事件已经过了3个八分音符的时间,无论再加上什么,都只会发生在后面,所以我们要在前面插入一些字节。
9x已经打开了通道,我们直接在9x按下的音符后加上一个音符事件“00 3E 64”,这里的00显然是个时间差,3E是中音D,64是力度,也就是说,在按下中音A的同时按下了中音D。我们又按下一个键了,要在什么时候,在哪里松开才能保证输入的是个附点八分音符呢?首先,它的时值是3个八分音符,即180,这里还有一点要注意,180是个大于128的数,它的动态字节就应该表示成“81 34”,在哪里输入才好呢?如果你觉得在按下D后输入,或者在任何什么地方输入这个时间差,然后再写上“3E 00”可以表示松开的话就完全误解了时间差的概念。其实,我们只要简单地在松开#G的时候松开D就可以了,所以应该在末尾补上“00 3E 00”。统计好字节数后到Cakewalk中去验证验证吧。这里附有我们目前写下的Midi文件样本。
到目前为此,我们应该可以输入任何形式的音符了,不过MIDI除了音符以外,还可以包括各种控制器和系统码,它们的地位不亚于音符,我们现在马上学习如何使用控制器。
控制器比音符要简单多了,我们尝试在#G前加入相位控制(Pan),它的十进制代码是10,十六进制是0A,我们将参数设置成111,即十六进制的6F。首先查得控制器是Bx,这里的x和上面一样,也是6。接下来写入控制器号0A,然后是参数6F,别忘了前面的时间差是00。所以这段字节是“00 B6 0A 6F”,它应放在松开#G的事件之前,与按下#G同时。不过,一旦使用了非音符,而后面还有音符事件时,则必须重新通知打开音符,这说起来复杂,做起来还是比较容易的,我们只要稍微改写下一个音符事件即可:原本是“时间差+音符+力度”,我们加入一个打开音符的标志,成为“时间差+9x+音符+力度”即可。校验过头信息后,去Cakewalk中进行更进一步的检验便知它的可行。
其实,时间差为00的控制事件如果出现的时间也是00,则在Cakewalk中会尽可能地把它们放在轨道信息中,而不在事件列表中重复。我们可以利用这一点给音轨设置初始乐器和音量,这就作为一个练习,在此就不再说明了。
至于其他的诸如触后键等与控制器类似的格式在此就不多说了。在这里有必要提醒的是滑音。滑音的乐理范围是-8192~8191,但是在使用时参数是个正数,比如要设置成0,则应该是0-(-8192)=8192,它才是参数。8192的7位双字节表示成“8192 mod 128=00H;8192 div 128=40H”。如果时间差是00,则应表示成“00 E6 00 40”
最后我们看看系统码。系统码的构成本来是“F0 厂家ID 设备号码 格式代码 传送命令 具体参数 F7”,而在文件中,则不以开头的“F0”为系统码,而字节数也仅记录剩余的系统码,比如XG的复位码是“F0 43 10 4C 00 00 7E 00 F7”,则在文件中应写成“00 F0 08 43 10 4C 00 00 7E 00 F7”,其中第一个00是时间差,F0是系统码标志,08是后面的字节数。有一点要注意的是,几个系统码不可以写在一起,比如“00 F0 0D 43 10 4C 00 00 7E 00 F7 F0 AA BB CC F7”或“00 F0 0C 43 10 4C 00 00 7E 00 F7 AA BB CC F7”都是不好的写法。如果存在以上系统码集,可以分成两个事件:“00 F0 08 43 10 4C 00 00 7E 00 F7 00 F0 04 AA BB CC F7”
当然系统码可以写在任何音轨,不过一般我们会考虑把歌曲播放前发送的系统码写在全局音轨中,并把时间差设成00。
作为一个参考,这里再附上一个MIDI样本。
虽然我们只讨论了同步多音轨的格式,其实对于其他两种,比如较常见的单音轨格式,所有的事件只写在一个音轨中,即只要存在一个“MTrk”就足够了。而相对地,用于记录音轨数的两个字节也永远为“00 01”,连续事件如果出现的通道不同,也必须重新指定通道(8x~Ex)。在此不详细讨论了。
音符十六进制的计算 关于乐器选择 RPN和NRPN
在MIDI中,中央C是C5,最低音是C0,最高音是G5,要计算任何一个音符对应的十六进制,可以使用这个公式:
假设音符是NO,表示第O八度的音名N,比如G2中,N为G,O为2,则它的十进制为O*12+N,N的值为了简便起见,用下表给出:
这样G2的十进制值为2*12+7=31,十六进制为1F。若知道音符的十六进制,也可以很容易求出音符,比如64(16)=100(10),
而100 div 12=8,100 mod 12=4,对应音符为E8。写成公式就是:
N=B mod 12;O=B div 12;(设B为表示音符的字节的十进制数)
乐器是MIDI中比较重要的因素,要选择所有的乐器不仅仅只是使用Cx号标志就能完成的,还必须结合BankSelect(乐队选择),而BankSelect其实是由0号控制器和32号控制器完成的,它们的十六进制代码分别是00和20.比如要选择出XG标准中的Slow Violin,它在第08H个乐队中的第28H号乐器中,所以它的完整代码应为“00 B0 00 00 00 20 08 00 C0 28”。我们来分析它的构成:这里我们假设时间差为00,所有信息都发给通道00,所以第一个00是时间差,B0是打开控制器的标志,并指定发送到通道00,接下来的00 00,是由控制器号00和控制器参数00构成的,它事实上是表示0号控制器的参数为0,即BankSelect-MSB的参数为0,然后是下一个事件,它是“00 20 08”,即时间差是00,使用20H号控制器,参数为08H,即BankSelect-LSB的参数是08H,这样就指定了Bank(乐队)。再下来就是“00 C0 28”,就是所谓的Patch Change事件了,它的时间差为00,参数是28H。这样就完成了标准的乐器选择。
事实上,它是由三个事件共同完成的,如果某次选择乐器和上次的乐器有共同的参数,可以不必重复使用相关的操作。
在本例中,您可能发现了一点,当连续使用同类操作时可以不必每次指定操作种类,比如这里的连续再次使用控制器,所以第二个控制器并没有使用B0作为标志,而是直接使用控制器号码和它的参数。这一点和音符是一样的。其实,如果连续使用Patch Change事件(我是说如果),则也可不必每次都写Cx,打开了一次就可以了;不过就Patch Change事件而言,连续地更换乐器的结果是仅最后一个有效而已。
在前面的文档中并没有提及RPN和NRPN,其实它们是由四个连续的控制器来实现的。我们假设要使用RPN事件的Coarse Turning,它的参数假设是4096,则它的字节是“00 B0 65 00 00 64 02 00 06 20 00 26 00”,我们来分析这段字节:首先我们先看看RPN是由哪四个控制器组成的——首先设置RPN-MSB和RPN-LSB,分别对应的控制器是65H和64H,Coarse Turning的RPN码是2,所以MSB为0,LSB为2;然后是设置Data Entry MSB和Data Entry LSB,对应的控制器是06H和26H,而4096 div 128=32,4096 mod 128=0,对应的十六进制数分别是20H和00H。因此就构成了上面的字节。而NRPN和RPN原理是一样的,只不过不用RPN-MSB和RPN-LSB,而改用NRPN-MSB和NRPN-LSB而已,它们对应的十六进制数分别为63H和62H。
vim编辑二进制文件的方法
参考链接
打开
vim -b x.mid
设置16进制格式显示:
:%!xxd
只用编辑16进制部分的内容,修改其他部分的内容不会有其他影响期间不要保存改动
最后保存前返回二进制形式:
:%!xxd -r
:wq文中制作的midi文件内容和改动
原文描述中每个音轨结束后面没有加“00 ff 2f 00”, 同时在字节数中加上4,可以直接通过windows中的音乐播放器播放:
00000000: 4d54 6864 0000 0006 0001 0002 0078 4d54 MThd………xMT
00000010: 726b 0000 0019 00ff 5103 07a1 2000 ff58 rk……Q… ..X
00000020: 0406 0300 0000 ff59 0203 0000 ff2f 004d …….Y…../.M
00000030: 5472 6b00 0000 0b00 9645 703c 4500 00ff Trk……Ep -
位图
2006-04-01 19:12:00如何创建位图(VC版)这篇文章介绍了一种创建位图的方法以及如何在位图中绘图并保存成为位图文件...在这里我们讨论的位图是属于DIB(device-independent-bitmap)文件格式,我们称之为与设备环境(在这里可以称为显示设备如何创建位图(VC版)
这篇文章介绍了一种创建位图的方法以及如何在位图中绘图并保存成为位图文件。位图本质上是视频图像的内存表示,它做为windows的一种资源可以用于很多场合,被几乎所有的绘图软件所支持。象图标、墙纸、图形、图像都可以以位图的形式来存储。在这里我们讨论的位图是属于DIB(device-independent-bitmap)文件格式,我们称之为与设备环境(在这里可以称为显示设备)无关的位图。位图文件可以分为三部分来组成(有些人把它分为四部分):
一、 文件头(BITMAPFILEHEADER):
我们看文件头的数据结构:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;// 用来指定文件类型,其值必须为BM即:0x424d。
DWORD bfSize;//以字节为单位指定位图文件的大小。
WORD bfReserved1;//保留,但其值必须为0。
WORD bfReserved2; //保留,但其值必须为0。
DWORD bfOffBits;///以字节为单位的从文件头到位图数据的偏移量
} BITMAPFILEHEADER;
二、 位图信息(BITMAPINFO):
我们看位图信息的数据结构:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;/*信息头结构,包含位图的尺寸和颜色格式。对于不同系统版本,我们有不同的信息头结构版本:BITMAPINFOHEADER用于NT3.51及老的版本, BITMAPV4HEADER用于NT4.0和WIN95版本,BITMAPV5HEADER用于NT5.0和WIN98版本。*/
RGBQUAD bmiColors[1];///调色板结构,用来存放颜色的。常以数组(也常常称///为调色表)形式出现。
} BITMAPINFO
我们看信息头结构BITMAPINFOHEADER:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; ///此结构所需要的字节数。
LONG biWidth; ///以像素为单位的位图的宽度。
LONG biHeight; ///以像素为单位的位图的高度。见详细说明一。
WORD biPlanes; ///指定目标设备的平面数,目前这个值必须设置为1。
WORD biBitCount ///指定每个像素点所需要的位的个数。见详细说明二。
DWORD biCompression; ///指定压缩的类型。见详细说明三。
DWORD biSizeImage; ///以字节位单位的图像大小。见详细说明四。
LONG biXPelsPerMeter;///以像素/米为单位来说明水平分辨率。
LONG biYPelsPerMeter; ///以像素/米为单位来说明垂直分辨率。
DWORD biClrUsed; ///规定了位图中使用调色表索引的数目。一般此值为0,///表示可使用所有的颜色索引。
DWORD biClrImportant; ///指定调色表的一个索引,这个索引所代表的颜色///将做为显示位图需要的很重要的颜色。如果0代表所///有索引。
} BITMAPINFOHEADER;
详细说明一:
如果这个高度值是正数(>0),这个位图的原点坐标位于左下角(意味着位图的方向是倒的),如果是负数,这个位图的原点坐标位于左上角(意味着位图的方向是正的)。
详细说明二:
这个值指定了在位图中一个像素点用多少个位(bit)来表示。位数必须是下面值的一种:
1、 0。意味着jpeg格式。只能够在win98,NT5.0或更高的版本上使用。
2、 1。每个像素点需要一个位。两种颜色,缺省的是黑色和白色。在这种情况下,位图数据中的每一个位表示一个像素。如果这个位是0,那么这个像素就用调色板表的第一个索引所对应的颜色值来表示;如果这个位是1,那么这个像素就用调色表中的第二个索引所对应的颜色值来表示。
3、 4。每个像素点需要四个位。最多有十六种颜色。每一个像素可以用在调色表中从0到15的索引所对应的颜色值来表示。举例来讲,在位图数据种的第一个字节如果是0x1F,那么这两个像素的第一个像素所包含的颜色在调色表中由索引值为1的索引所代表的颜色组成;而第二个像素所包含的颜色在调色表中由索引值为15的索引所代表的颜色组成。
4、8。每个像素点需要八个位。最多有256种颜色。每一个像素可以用在调色表中从0到255的索引所对应的颜色值来表示。因为8个位即为一个字节,所以每个像素点由单个字节组成。举例而言,在位图数据中如果有某一个数据为OxFF,那么这个像素点所包含的颜色在调色表中由索引值为255的索引所代表的颜色组成。
8、16。用两个字节(16bit)来表示一个像素点。最多有65536(2^16)种颜色。如果biCompression的值是BI_RGB,那么BITMAPINFO 的bmiColors成员须为NULL值。红、绿、蓝三基色都用5个位(bit)来表示,这三基色的位的顺序为:0(不用)00000(红)00000(绿)00000(蓝),即由小到大分别为蓝、绿、红。
9、24。用24个位来表示一个像素点。 最多有16777216(2^24)种颜色。BITMAPINFO 的bmiColors成员必须为NULL值。在位图数据中,每三个字节一组用来表示一个像素点。
10、32。用32个位表示一个像素点。最多有4294967296(2^32)种颜色。如果biCompression的值是BI_RGB,那么BITMAPINFO 的bmiColors成员须为NULL值。
详细说明三:
压缩仅仅对于倒向(左下角坐标的位图)的位图有效。可以有下面的选择:
1、 BI_RGB:不压缩的格式。
2、 BI_RLE8:8位每像素点的RLE格式。
3、 BI_RLE4:4位每像素点的RLE格式。
4、 BI_BITFIELDS:位图不压缩,对于16位和32位位图有效。
5、 BI_JPEG:对于win98、NT5.0或较高的版本有效。指示这个图像是一个JPEG的文件格式。
详细说明四:
如果是BI_RGB格式,其值可以设为零。如果biCompressionshi是JBI_JPEG,则:其值指的是jpeg图像缓冲的大小。
我们看调色板结构RGBQUAD:
typedef struct tagRGBQUAD {
BYTE rgbBlue; ///指定蓝色的深度
BYTE rgbGreen; ///指定绿色的深度
BYTE rgbRed; ///指定红色的深度
BYTE rgbReserved; ///保留,暂时不用
} RGBQUAD;
第三部分(DATA CONTENT):
这部分存放位图的图像数据。
仅仅创建一个位图,大部分情况不是我们编程的目的,在位图中绘出我们所需要的东西,才是我们真正需要的。WINDOWS提供了专门的绘图对象如画刷(填充颜色)、画笔(画点、线)及其它的GDI对象如:字体、矩形等等。有了这些对象我们就可以在位图中画出自己想要的图,并保存为位图文件。生成一个位图文件,大体上可以分为以下几个步骤:
一、 根据自己的需要初始化位图信息结构BITMAPINFO,同时建立设备环境(显示设备环境)和与此设备环境逻辑兼容的内存设备环境。内存设备环境对我们很重要,在本文中几乎所有的绘图操作都是针对它而言的。还好windows提供了CreateCompatibleDC这个函数,用它可以建立内存设备环境对象。
二、 建立自己的位图对象或位图句柄(可以利用CreateDIBSection函数),并将其选进(或者说关联到)内存设备环境上,以后我们就可以通过内存设备环境来对位图操作了。
三、 建立自己需要的绘图工具,如画刷、画笔并选择它们的颜色。以及其它所需要的GDI对象如字体、矩形等等。
四、 通过内存设备环境选择自己将要用的绘图工具,要选择绘图工具(对象)SelectObject这个函数(或者说成员)是我们最佳的选择。
五、 利用绘图工具开始绘图,如画线,画点、画矩形、画圆等等,以及填充某些区域。在这个过程中我们可以根据需要更换自己不同颜色的画笔和画刷。这好像一个画家当他画太阳时用红色的画笔,然后用红色的画刷去填充,画蓝天时就用蓝色的画笔,然后用蓝色的画刷去填充。
六、 当所有的绘图操作完成以后,开始对位图进行保存,因此我们要建立和初始化位图文件头结构BITMAPFILEHEADER,为建立一个位图文件做准备。当然需要保存的文件格式必须严格按照位图(bmp)的文件格式,即:文件头、位图信息和图像数据。对于图像数据我们可以通过位图数据的入口指针(由函数CreateDIBSection的第四个参数*ppvBits
确定)来获得。
下面给出一个完整的实例函数(仅供参考),在VC5.0上编译通过,其目的用来绘出某一股票的指数K线图(在内存设备环境上),并将其保存为位图文件(可以在本人个人信息中看到生成的图片)。
void CImage::MakeIndex(TIndex *pIndex,TDate &pDate,const TCHAR *pchTitle,int iDays,int iRectWidth)
{
//* Create a file about bmp and Create BITMAPINFOHEADER First.
LPBITMAPINFO lpbmih = new BITMAPINFO;
lpbmih->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
lpbmih->bmiHeader.biWidth = m_iWidth;
lpbmih->bmiHeader.biHeight = m_iHeight;
lpbmih->bmiHeader.biPlanes = 1;
lpbmih->bmiHeader.biBitCount = g_iPixel;
lpbmih->bmiHeader.biCompression = BI_RGB;
lpbmih->bmiHeader.biSizeImage = 0;
lpbmih->bmiHeader.biXPelsPerMeter = 0;
lpbmih->bmiHeader.biYPelsPerMeter = 0;
lpbmih->bmiHeader.biClrUsed = 0;
lpbmih->bmiHeader.biClrImportant = 0;
//* Create Data for bmp
HDC hdc,hdcMem;
HBITMAP hBitMap = NULL;
CBitmap *pBitMap = NULL;
CDC *pMemDC = NULL;
BYTE *pBits;
CBrush brWhite,brBlack;
hdc = CreateIC(TEXT("DISPLAY"),NULL,NULL,NULL);
hdcMem = CreateCompatibleDC(hdc);
hBitMap = CreateDIBSection(hdcMem,lpbmih,DIB_PAL_COLORS,(void **)&pBits,NULL,0);
pBitMap = new CBitmap;
pBitMap->Attach(hBitMap);
pMemDC = new CDC;
pMemDC->Attach(hdcMem);
pMemDC->SelectObject(pBitMap);
brWhite.CreateSolidBrush(RGB(255,255,255));
brBlack.CreateSolidBrush(RGB(0,0,0));
//* Clear First and draw coordinate with gray.
pMemDC->FillRect(CRect(0,0,m_iWidth,m_iHeight),&brWhite);
int iGray = RGB(192,192,192),iDrakGray = RGB(128,128,128),iDateHeight = 11; //* Color Gray and eg.2002-3-8's and 2-11M's and MinIndex's and MaxIndex's Height.
int iSpaceLine = 12; //* The space with two lines of bottom.
int iTitleHeight; //* eg.上海A股's Height.
TEXTMETRIC ptm;
CRect rect;
CPen newPenDot(PS_DASH,1,iDrakGray);
CPen penDrakGray(PS_SOLID,1,iDrakGray);
CPen penGray(PS_SOLID,1,iGray);
GetTextMetrics(hdcMem,&ptm);
iTitleHeight = ptm.tmHeight;
pMemDC->SelectObject(&penDrakGray);
//* Draw K line for Index.
int iOffsetClose = -1;
for(int i=0; i<iDays; i++)
{
if(pIndex[i].fOpen <= pIndex[i].fClose) //* 阳线
{
//* Up-shadow line.
pMemDC->MoveTo(pIndex[i].iDay,pIndex[i].fClose); //* iRectWidth equate 2
pMemDC->LineTo(pIndex[i].iDay,pIndex[i].fHigh);
//* Entity of white
pMemDC->Rectangle(pIndex[i].iDay - iRectWidth,pIndex[i].fOpen,pIndex[i].iDay + iRectWidth,pIndex[i].fClose);
//* Bottom-shadow line.
pMemDC->MoveTo(pIndex[i].iDay,pIndex[i].fOpen);
pMemDC->LineTo(pIndex[i].iDay,pIndex[i].fLow);
//* Draw Volume.
pMemDC->SelectObject(&penDrakGray);
pMemDC->Rectangle(pIndex[i].iDay - iRectWidth,m_iHeight - iDateHeight,pIndex[i].iDay + iRectWidth,pIndex[i].iVolume);
pMemDC->SelectStockObject(BLACK_PEN);
}
else //* 阴线
{
//* Up-shadow line.
pMemDC->MoveTo(pIndex[i].iDay,pIndex[i].fOpen);
pMemDC->LineTo(pIndex[i].iDay,pIndex[i].fHigh);
//* Entity of black.
rect.SetRect(pIndex[i].iDay - iRectWidth,pIndex[i].fClose,pIndex[i].iDay + iRectWidth,pIndex[i].fOpen);
pMemDC->FillRect(rect,&brBlack);
//* Draw Vloume.
rect.SetRect(pIndex[i].iDay - iRectWidth,m_iHeight - iDateHeight,pIndex[i].iDay + iRectWidth,pIndex[i].iVolume);
pMemDC->FillRect(rect,&brBlack);
}
}
//* Save to File.And Create BITMAPFILEHEADER.
BITMAPFILEHEADER bmfh;
ZeroMemory(&bmfh,sizeof(BITMAPFILEHEADER));
*((char *)&bmfh.bfType) = 'B';
*(((char *)&bmfh.bfType) + 1) = 'M';
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmfh.bfSize = bmfh.bfOffBits + (m_iWidth * m_iHeight) * g_iPixel / 8;
TCHAR szBMPFileName[32];
int iBMPBytes = m_iWidth * m_iHeight * g_iPixel / 8;
strcpy(szBMPFileName,m_szFileName);
CFile file;
if(file.Open(szBMPFileName,CFile::modeWrite | CFile::modeCreate))
{
file.Write(&bmfh,sizeof(BITMAPFILEHEADER));
file.Write(&(lpbmih->bmiHeader),sizeof(BITMAPINFOHEADER));
file.Write(pBits,iBMPBytes);
file.Close();
}
pMemDC->DeleteDC();
delete pMemDC;
delete pBitMap;
delete lpbmih;
}————————————————————————
有编程经验的程序员都知道:要使应用程序的界面美观不可避免的要使用大量位图。现在流行的可视化编程工具对位图的使用提供了很好的支持,被称为三大可视化开发工具的VB、VC、Delphi通过封装位图对象对位图使用提供了很好的支持:VB提供了两个功能很强的对象:PictureBox及Image,通过使用它们,装载、显示位图变得非常容易。Delphi中也提供了一个位图对象:TImage,它的功能与用法与VB中的Image类似。在VC中通过使用设备相关类CDC与GDI对象类CBitmap来完成位图的操作。
然而在VC中使用CBitmap类必须将BMP位图装入资源中,然后通过类 CBitmap的成员函数使用它,在通过CDC类的成员函数操作它。这样做有两点缺陷:将位图装入资源导致可执行文件增大,不利于软件发行;只能使用资源中有限的位图,无法选取其它位图。而且BMP位图文件是以DIB(设备无关位图)方式保存,BMP位图装入资源后被转换为DDB(设备相关位图),类CBitmap就是对一系列DDB操作的API函数进行了封装,使用起来有一定的局限性,不如DIB可以独立于平台特性。
要弥补使用资源位图的两点不足,就必须直接使用BMP位图文件。VC的示例中提供了一种方法读取并显示BMP位图文件,但使用起来相当的麻烦。首先使用API函数GlobalAlloc分配内存并创建HDIB位图句柄,所有操作只能直接读写内存,然后通过StrechDIBits及SetDIBsToDevice函数来显示于屏幕上,操作起来费时费力。
因此笔者通过研究类CBitmap的封装与DIB结构,使用Win32中提供的新函数,建立了一个专用于操作BMP文件的类,而且完全仿照类CBitmap的实现:从类CGdiObject派生,新类的所有接口与类CBitmap 的部分接口完全相同。这样对于习惯使用CBitmap类接口用法的程序员来说两者的接口在使用上没有什么分别。
首先我们先简单介绍一下DIB的结构。DIB位图既可以存在于内存,也可以以文件形式保存在磁盘上(BMP文件)。所有DIB都包含两部分信息:位图信息(BITMAPINFO),包括位图信息头和颜色表;位图数据。对于内存中DIB的只要有上述两部分就行,而对于DIB文件则还要加上位图文件头。
其次,Win32中提供了一个新函数CreateDIBSection,通过它可以创建一个存储DIB位的内存区域,既可以执行相应的GDI操作,又可以直接通过指向DIB位区域的指针方位DIB位区域。这是一个非常有用的函数,通过它我们可以用DIB替代DDB。
在了解了相应的知识后,我们可以自己由类CGdiObject派生一个操作BMP文件的类:CBitmapFile。
在自己编写类时有两点值得注意:
在BitmapFile.h文件中定义类CBitmapFile,首先必须声明类CBitmapFile是从类CGdiObject中公有派生。然后在类中首先使用宏DECLARE_DYNAMIC(CBitmapFile)表明新类的最高父类是类CObject,是符合MFC的类库规范。紧接着宏DECLARE_DYNAMIC的是声明静态函数FromHandle,这两个声明必须放在类定义的最前面。
在BitmapFile.cpp文件中类的成员函数的实现前加上IMPLEMENT_DYNAMIC(CBitmapFile,CGdiObject);表明类CBitmapFile直接派生于类CGdiObject。
在类CBitmapFile的声明中有三个函数与类Cbitmap中的定义稍有不同:
在类CbitmapFile中LoadBitmap函数的参数是LPCTSTR型,保存的是BMP文件的文件名。
在类CbitmapFile中CreateBitmap函数的参数中少了参数nPlanes,在函数内部默认为1。
在类CbitmapFile中CreateBitmapIndirect函数的参数中多了参数lpBits,它指向指定位图DIB位的内存区域。
在成员函数中最重要的是函数CreateBitmapIndirect和函数LoadBitmap:
在函数CreateBitmapIndirect中使用函数CreateDIBSection创建了一个以兼容DC为基础的HBITMAP句柄,并用继承自类CGdiObject 的函数Attach把它与类CGdiObject的句柄m_hObject关联起来。然后将指定位图的DIB位图数据拷贝到由函数CreateDIBSection创建的DIB位的内存区域。
在函数LoadBitmap中首先从指定文件名的文件中读取以结构BITMAPFILEHEADER为大小的数据块,然后由文件头标志判断文件是否为BMP位图文件,然后由BITMAPFILEHEADER中bfSize保存的文件大小与文件的真实大小比较文件是否有损坏,再由BITMAPFILEHEADER中bfOffBits与BITMAPFILEHEADER结构大小相减计算出位图信息头和颜色表一共的大小,动态申请一块空间保存位图信息头和颜色表信息,再由BITMAPFILEHEADER中bfSize与bfOffBits相减计算出DIB位图数据的大小,动态申请一块空间保存DIB位图数据,最后调用成员函数CreateBitmapIndirect来创建DIB位图。
在应用程序的OnPaint()事件中绘制DIB位图的方法与使用类CBitmap时绘制位图的方法完全相同,但有一点要注意的是由于CDC类没有提供返回新类CBitmapFile指针类型的将DIB位图选入内存的SelectObject函数,所以在使用SelectObject时要将返回类型强制转换为CbitmapFile *类型。
至此,关于新类CBitmapFile编写中的一些要点和使用时一些要注意的问题就介绍这么多了。 -
MIDI文件格式分析
2008-03-13 17:52:00MIDI文件格式分析(理论篇) MIDI文件属于二进制文件,这种文件一般都有如下基本结构: 文件头+数据描述 文件头一般包括文件的类型,因为Midi文件仅以.mid为扩展名的就有0类和1类两种,而大家熟悉的位图文件的格式就...MIDI文件格式分析
(理论篇)
MIDI文件属于二进制文件,这种文件一般都有如下基本结构: 文件头+数据描述
文件头一般包括文件的类型,因为Midi文件仅以.mid为扩展名的就有0类和1类两种,而大家熟悉的位图文件的格式就更多了,所以才会出现文件头这种东西。
而数据描述部份是主体,我们现在来一起分析它的结构:
在每个Midi文件的开头都有如下内容,它们的十六进制代码为:“4d 54 68 64 00 00 00 06 ff ff nn nn dd dd”。
前四个是ASCII字符“MThd”是用来鉴别是否Midi文件,而随后的四个字节是指明文件头描述部分的字节数,它总是6,所以一定是“00 00 00 06”,以下是剩余部分的含义:
ff ff
指定Midi的格式
00 00
单音轨
00 01
多音轨,且同步。这是最常见的
00 02
多音轨,但不同步
nn nn
指定轨道数
实际音轨数加上一个全局的音轨
dd dd
指定基本时间
一般为120(00 78),即一个四分音符的tick数,tick是MIDI中的最小时间单位
以上就是MIDI文件头了,后面的所有内容都是真正做事的,我们先来看看它的构成。
MIDI的数据是由若干个格式相同的子数据构成的,这些子数据在多音轨的格式中记录了一个轨道的所有信息。多加一个音轨,就简单地把数据追加在前一音轨的后面就可以了,不过不要忘记更改文件头中的nn nn(轨道数)。
先看全局音轨。全局音轨包括歌曲的附加信息(比如标题和版权)、歌曲速度和系统码(Sysx)等内容。
不管是全局音轨还是含有音符的音轨,都以“4D 54 72 6B”开头,它其实是ASCII字符“MTrk”,其后跟着一个4个字节的整数,它标志了该轨道的字节数,这不包括前面的4个字节和本身的4个字节。这一点,我们可以在后面的例子中去理解。
接着就是记录数据的地方了,每一个数据有着相同的结构:时间差+事件。
所谓时间差,指的是前一个事件到该事件的时间数,它的单位是tick(MIDI的最小时间单位)。它的构成比较特殊,这里要用二进制来说明。
一个字节有8位,如果仅使用7位,它可以表示0~127这128个数,而剩下的一位,则用来作为标志。如果要表示的数在以上范围,则这个标志为0,这时,一个7位的字节可以表示0~127tick。如果要表示的数超出了这个范围(比如240),则把标志设置成1,然后记录下高7位,剩下的留给下一个字节,在该例中240可以分解成128*1+112,这里的1就是第一个字节要记录的,加上标志位,应该为10000001,即十六进制的81;而112是下一个字节记录的,它的十六进制为70:所以要表示240这个时间,要写成81 70。同理,如果要表示65535tick,则可以先计算出65535=1282*3+1281*127+1280*127,然后得出结果:83 FF 7F。由此,我们反过来也可以知道如何确定时间差:只要标志位为0,则表示结束读取时间差。比如82 C0 03表示1282*2+1281*64+1280*3=40963,如果基本时间为120,则有341:043个四分音符。
以这种方式记录整数的字节称为动态字节,它根据记录的整数改变自身的长度,这在后面还要用到,所以必须熟练计算。
看完了这么麻烦的东西,我们再来看个更麻烦的东西:事件。在这些标准的解释后面,我们会通过一些例子来进一步掌握这些内容。
事件大体上可以分为音符、控制器和系统信息这几个种类。对于这些事件,都有统一的表达结构:种类+参数。
对于一个音符,由于它的有效范围是0~127,所以直接用00~7F作为“种类”,可以认为是个音符,比如3C表示中央C。而一个音符的最重要的参数是力度(也叫速度:velocity)。比如,3C 64 表示一个力度为十进制100的中央C音符。
因为一个字节有8位,所以剩余的一位如果置1,再联合其他的7位,则可以表示各种信息。我们暂且无视一个音轨到底是全局的还是用于记录音符的。它们归根结底都是用来记录各种事件的,只不过有些应出现在全局音轨比较合乎逻辑而已。既然这样,我们就可以从下面的表来看事件:
下表中,x表示音轨0~F,比如81表示松开第二轨的音符。
种类
参数(十六进制)
字节
含义
8x
松开音符
音符(00~7F):松开的音符
力度:00~7F
9x
按下音符
音符(00~7F):按下的音符
力度:00~7F
Ax
触后音符
(Key After Touch)
音符:00~7F
力度:00~7F
Bx
控制器
控制器号码:00~7F
控制器参数:00~7F
Cx
改变乐器
乐器号码:00~7F
Dx
触后通道
值:00~7F
Ex
滑音
音高(Pitch)低位:Pitch mod 128
音高高位:Pitch div 128
F0
系统码
系统码字节数:动态字节
系统码:不含开头的F0,但包括结尾的F7
FF
其他格式
程式种类:00~FF
数据占用的字节数:动态字节
数据:个数由上一参数确定
00~7F
上次激活格式的参数(8x、9x、Ax、Bx、Cx、Dx、Ex)
下表详细地列出了FF的详细情况,对于字节数由数据决定的情况,表中以“--”表示。
种类
字节数
数据
字节
含义
00
设置轨道音序
02
音序号 00 00~ FF FF
01
歌曲备注
--
文本信息
音轨文本
文本信息
02
歌曲版权
--
版权信息
03
歌曲标题
--
歌曲标题:用于全局音轨,第一次使用表示主标题,第二次表示副标题
音轨名称
--
音轨名
04
乐器名称
--
音轨文本(同01/2)
05
歌词
--
歌词
06
标记
--
用文本标记(Marker)
07
开始点
--
用文本记录开始点(同01/2)
2F
音轨结束标志
00
无
51
速度
03
3字节整数,1个四分音符的微秒数
58
节拍
04
分子
分母:00(1),01(2),02(4),03(8)等
节拍器时钟
一个四分音符包含的三十二分音符的个数
59
调号
02
升降号数:-7~-1(降号),0(C),1~7(升号)
大小调:0(大调),1(小调)
7F
音序特定信息
--
音序特定信息
这些就是MIDI结构的全部内容,我们现在通过一个实例来分析。
MIDI文件格式分析
(RMI篇)
MIDI文件除了有常用的以.mid为扩展名的格式以外,还有一种以.rmi为扩展名的。这种格式与.mid不同的是它在.mid格式的前面增加了一个文件头,后面的部分几乎和.mid的一样。即.rmi=RMI格式文件头+.mid文件。
这个文件头可以描述成以下这种形式:
52 49 46 46 LL LL LL LL 52 4D 49 44 64 61 47 61 SS SS SS SS
和.mid文件格式类似,这个文件头的前四个字节是“RIFF”,接下来的是一个四个字节的整数,它表示从最后一个LL起到文件结束的字节数。假设这个数是100,则这四个字节就是“64 00 00 00”。然后紧接着的八个字节又是文字标识,不过这次是“RMIDdata”,最后的四个字节SS,表示MIDI文件的字节数,如果MIDI文件长度为139,则这四个字节就是“8B 00 00 00”。文件头后就是和MIDI文件一样的内容了,这些内容的长度就是SS的值。
-
简述新图像文件格式——SVG
2019-08-02 16:57:36一、概述 .jpg .png-8 png-24 .jpeg 位图 (固定的...其他图像格式都是基于像素处理的,SVG 则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。 XML 没有明确的标签 储存数...一、概述
.jpg .png-8 png-24 .jpeg 位图 (固定的像素点 单位像素点) 缩放会失真
SVG 是一种基于 XML 语法的图像格式,全称是可缩放矢量图(Scalable Vector Graphics)。其他图像格式都是基于像素处理的,SVG 则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。
XML 没有明确的标签 储存数据的结构
HTML 结构 明确的标签
在线转换网址: http://www.bejson.com/convert/
SVG 文件可以直接插入网页,成为 DOM 的一部分,然后用 JavaScript 和 CSS 进行操作。
<!DOCTYPE html> <html> <head></head> <body> <svg id="mysvg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet" > <circle id="mycircle" cx="400" cy="300" r="50" /> <svg> </body> </html>
上面是 SVG 代码直接插入网页的例子。
SVG 代码也可以写在一个独立文件中,然后用
<img>
、<object>
、<embed>
、<iframe>
等标签插入网页。<img src="circle.svg"> <object id="object" data="circle.svg" type="image/svg+xml"></object> <embed id="embed" src="icon.svg" type="image/svg+xml"> <iframe id="iframe" src="icon.svg"></iframe>
CSS 也可以使用 SVG 文件。
.logo { background: url(icon.svg); }
二、语法
2.1
<svg>
标签SVG 代码都放在顶层标签
<svg>
之中。下面是一个例子。<svg width="100%" height="100%"> <circle id="mycircle" cx="50" cy="50" r="50" /> </svg>
<svg>
的width
属性和height
属性,指定了 SVG 图像在 HTML 元素中所占据的宽度和高度。除了相对单位,也可以采用绝对单位(单位:像素)。如果不指定这两个属性,SVG 图像默认大小是300像素(宽) x 150像素(高)。如果只想展示 SVG 图像的一部分,就要指定
viewBox
属性。<svg width="100" height="100" viewBox="50 50 50 50"> <circle id="mycircle" cx="50" cy="50" r="50" /> </svg>
==<viewBox>
属性的值有四个数字,分别是左上角的横坐标和纵坐标、视口的宽度和高度==。上面代码中,SVG 图像是100像素宽 x 100像素高,viewBox
属性指定视口从(50, 50)
这个点开始。所以,实际看到的是右下角的四分之一圆。注意,视口必须适配所在的空间。上面代码中,视口的大小是 50 x 50,由于 SVG 图像的大小是 100 x 100,所以视口会放大去适配 SVG 图像的大小,即放大了四倍。
如果不指定
width
属性和height
属性,只指定viewBox
属性,则相当于只给定 SVG 图像的长宽比。这时,SVG 图像的默认大小将等于所在的 HTML 元素的大小。2.2
<circle>
标签<circle>
标签代表圆形。<svg width="300" height="180"> <circle cx="30" cy="50" r="25" /> <circle cx="90" cy="50" r="25" class="red" /> <circle cx="150" cy="50" r="25" class="fancy" /> </svg>
上面的代码定义了三个圆。
<circle>
标签的cx
、cy
、r
属性分别为横坐标、纵坐标和半径,单位为像素。坐标都是相对于<svg>
画布的左上角原点。class
属性用来指定对应的 CSS 类。.red { fill: red; } .fancy { fill: none; stroke: black; stroke-width: 3px; }
SVG 的 CSS 属性与网页元素有所不同。
- fill:填充色
- stroke:描边色
- stroke-width:边框宽度
2.3
<line>
标签<line>
标签用来绘制直线。<svg width="300" height="180"> <line x1="0" y1="0" x2="200" y2="0" style="stroke:rgb(0,0,0);stroke-width:5" /> </svg>
上面代码中,
<line>
标签的x1
属性和y1
属性,表示线段起点的横坐标和纵坐标;x2
属性和y2
属性,表示线段终点的横坐标和纵坐标;style
属性表示线段的样式。2.4
<polyline>
标签<polyline>
标签用于绘制一根折线。<svg width="300" height="180"> <polyline points="3,3 30,28 3,53" fill="none" stroke="black" /> </svg>
<polyline>
的points
属性指定了每个端点的坐标,横坐标与纵坐标之间与逗号分隔,点与点之间用空格分隔。2.5
<rect>
标签<rect>
标签用于绘制矩形。<svg width="300" height="180"> <rect x="0" y="0" height="100" width="200" style="stroke: #70d5dd; fill: #dd524b" /> </svg>
<rect>
的x
属性和y
属性,指定了矩形左上角端点的横坐标和纵坐标;width
属性和height
属性指定了矩形的宽度和高度(单位像素)。2.6
<ellipse>
标签<svg width="300" height="180"> <ellipse cx="60" cy="60" ry="40" rx="20" stroke="black" stroke-width="5" fill="silver"/> </svg>
<ellipse>
的cx
属性和cy
属性,指定了椭圆中心的横坐标和纵坐标(单位像素);rx
属性和ry
属性,指定了椭圆横向轴和纵向轴的半径(单位像素)。2.7
<polygon>
标签<polygon>
标签用于绘制多边形。<svg width="300" height="180"> <polygon fill="green" stroke="orange" stroke-width="1" points="0,0 100,0 100,100 0,100 0,0"/> </svg>
<polygon>
的points
属性指定了每个端点的坐标,横坐标与纵坐标之间与逗号分隔,点与点之间用空格分隔。2.8
<path>
标签<path>
标签用于制路径。<svg width="300" height="180"> <path d=" M 18,3 L 46,3 L 46,40 L 61,40 L 32,68 L 3,40 L 18,40 Z "></path> </svg>
<path>
的d
属性表示绘制顺序,它的值是一个长字符串,每个字母表示一个绘制动作,后面跟着坐标。- M:移动到(moveto)
- L:画直线到(lineto)
- Z:闭合路径
2.9
<text>
标签<text>
标签用于绘制文本。<svg width="300" height="180"> <text x="50" y="25">Hello World</text> </svg>
<text>
的x
属性和y
属性,表示文本区块基线(baseline)起点的横坐标和纵坐标。文字的样式可以用class
或style
属性指定。2.10
<use>
标签<use>
标签用于复制一个形状。<svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg"> <circle id="myCircle" cx="5" cy="5" r="4"/> <use href="#myCircle" x="10" y="0" fill="blue" /> <use href="#myCircle" x="20" y="0" fill="white" stroke="blue" /> </svg>
<use>
的href
属性指定所要复制的节点,x
属性和y
属性是<use>
左上角的坐标。另外,还可以指定width
和height
坐标。2.11
<g>
标签<g>
标签用于将多个形状组成一个组(group),方便复用。<svg width="300" height="100"> <g id="myCircle"> <text x="25" y="20">圆形</text> <circle cx="50" cy="50" r="20"/> </g> <use href="#myCircle" x="100" y="0" fill="blue" /> <use href="#myCircle" x="200" y="0" fill="white" stroke="blue" /> </svg>
2.12
<defs>
标签<defs>
标签用于自定义形状,它内部的代码不会显示,仅供引用。<svg width="300" height="100"> <defs> <g id="myCircle"> <text x="25" y="20">圆形</text> <circle cx="50" cy="50" r="20"/> </g> </defs> <use href="#myCircle" x="0" y="0" /> <use href="#myCircle" x="100" y="0" fill="blue" /> <use href="#myCircle" x="200" y="0" fill="white" stroke="blue" /> </svg>
2.13
<pattern>
标签<pattern>
标签用于自定义一个形状,该形状可以被引用来平铺一个区域。<svg width="500" height="500"> <defs> <pattern id="dots" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse"> <circle fill="#bee9e8" cx="50" cy="50" r="35" /> </pattern> </defs> <rect x="0" y="0" width="100%" height="100%" fill="url(#dots)" /> </svg>
上面代码中,
<pattern>
标签将一个圆形定义为dots
模式。patternUnits="userSpaceOnUse"
表示<pattern>
的宽度和长度是实际的像素值。然后,指定这个模式去填充下面的矩形。2.14
<image>
标签<image>
标签用于插入图片文件。<svg viewBox="0 0 100 100" width="100" height="100"> <image xlink:href="path/to/image.jpg" width="50%" height="50%"/> </svg>
上面代码中,
<image>
的xlink:href
属性表示图像的来源。2.15
<animate>
标签<animate>
标签用于产生动画效果。<svg width="500px" height="500px"> <rect x="0" y="0" width="100" height="100" fill="#feac5e"> <animate attributeName="x" from="0" to="500" dur="2s" repeatCount=" indefinite " /> indefinite 无限次 </rect> </svg>
上面代码中,矩形会不断移动,产生动画效果。
<animate>
的属性含义如下。- attributeName:发生动画效果的属性名。
- from:单次动画的初始值。
- to:单次动画的结束值。
- dur:单次动画的持续时间。
- repeatCount:动画的循环模式。
可以在多个属性上面定义动画。
<animate attributeName="x" from="0" to="500" dur="2s" repeatCount="indefinite" /> <animate attributeName="width" to="500" dur="2s" repeatCount="indefinite" />
2.16
<animateTransform>
标签<animate>
标签对 CSS 的transform
属性不起作用,如果需要变形,就要使用<animateTransform>
标签。<svg width="500px" height="500px"> <rect x="250" y="250" width="50" height="50" fill="#4bc0c8"> <animateTransform attributeName="transform" type="rotate" begin="0s" dur="10s" from="0 200 200" to="360 400 400" repeatCount="indefinite" /> </rect> </svg>
上面代码中,
<animateTransform>
的效果为旋转(rotate
),这时from
和to
属性值有三个数字,第一个数字是角度值,第二个值和第三个值是旋转中心的坐标。from="0 200 200"
表示开始时,角度为0,围绕(200, 200)
开始旋转;to="360 400 400"
表示结束时,角度为360,围绕(400, 400)
旋转。 -
BMP文件格式详解(BMP file format)
2013-06-22 22:38:46看过很过讲BMP文件格式的博客,这篇文章写的很给力,大赞一把! BMP文件格式,又称为Bitmap(位图)或是DIB(Device-Independent Device,设备无关位图),是Windows系统中广泛使用的图像文 -
TTF文件的制作——打造属于自己的字体
2010-03-05 09:44:15这种字体是针对特定的显示分辨率以不同大小存储的位图,用于Windows系统中屏幕上的菜单、按钮等处文字的显示。它并不是以矢量描述的,放大以后会出现锯齿,只适合屏幕描述。不过它的显示速度非常快,所以作为系统... -
教你制作属于自己的字体库Font Creator Program工具字体文件制作
2011-04-14 11:24:48上次发布了一篇直接用CorelDRW制作字体文件的文章,但是CorelDRW毕竟只是一个设计 软件,对于它制作的字体 文件应付一般的应用是没有...这种字体是针对特定的显示分辨率以不同大小存储的位图,用于Windows系统中屏... -
Flv视频格式 编码调研
2016-04-14 09:29:23Flash MX以前,flash 中的视频文件得导入是一帧一帧变成位图。结果导致文件巨大,限制了它的应用范围。 随着Flash MX的推出,Macromedia 公司开发了属于自己流式视频格式——FLV。 这种格式是在sorenson 公司的... -
流式flash格式——FLV
2010-01-03 10:54:00FlashMX以前,flash中的视频文件得导入是一帧一帧变成位图。结果导致文件巨大,限制了它的应用范围。 随着FlashMX的推出,Macromedia公司开发了属于自己流式视频格式——FLV.这种格式是在sorenson公司的压缩算法的... -
让你使用MediaPlayer也能播放FLV视频文件
2007-06-01 10:02:00FLV,全称Flash Video,是现在的视频播客网站唯一使用的视频格式,Flash MX以前,flash 中的视频文件得导入是一帧一帧变成位图。结果导致文件巨大,限制了它的应用范围。随着Flash MX的推出,Macromedia 公司开发了... -
oracle学习文档 笔记 全面 深刻 详细 通俗易懂 doc word格式 清晰 连接字符串
2017-05-06 20:26:52简单来说是本身可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据运行新增、截取、更新、删除等操作。 常见的数据模型 1. 层次结构模型: 层次结构模型实质上是一种有根结点的定向有序树,IMS... -
html5中的svg
2020-10-02 14:17:01其他图像格式都是基于像素处理的,SVG 则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。此外SVG 是万维网联盟的标准,SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体... -
svg是什么以及第一个svg图形以及如何在html中引用它
2020-05-30 20:52:49svg:scalable vector graphics,即可伸缩的矢量图形,不管你对它...令任难以理解的是,svg是使用xml来定义的.......,它就是一个文本文件,里面的内容是xml格式的,这个xml被浏览器解析就显示成了svg矢量图片。基本上... -
XML Drawable与9-Patches
2016-11-23 15:46:00可绘制对象资源 两种设计工具均属于drawable。Android把任何可绘制在屏幕上的图形图像都称为drawable drawable可以是一种抽象的图形、一个继承 Drawable 类的...Android 支持以下三种格式的位图文件:.png(首选)... -
电脑设计常见题型
2019-03-08 09:41:001.电脑设计中属于位图文件格式有( ) A.CDR B.DWG C.PSD D.AI 2.RGB模式中,G是( ) A.红 B.绿 C.黄 D.蓝 3.画区选好后,沿方向移动选区10个像素的快捷键是( ) A.F5 B.Ctrl+T C.Shift+方向键 D.Ctrl+W 4.魔术棒工具... -
HTML学习笔记chap3
2018-09-18 17:36:58图片的格式 ...保存为一个图像,从而变为动画,所以仍属于图文文件格式。 3.PNG支持透明效果,通常应用于Java、S60或网页中 插入图片 1.插入图片的标记为img标签,相关定义属性如下: src:图片... -
第一阶段-day09-svg
2019-11-22 09:54:36一、概述 .jpg .png-8 png-24 .jpeg 位图 ...其他图像格式都是基于像素处理的,SVG 则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。 SVG 文件可以直接插入网页,成为 D... -
FLV流媒体解析
2008-12-31 15:16:32Flash MX以前,flash 中的视频文件得导入是一帧一帧变成位图。结果导致文件巨大,限制了它的应用范围。 随着Flash MX的推出,Macromedia 公司开发了属于自己流式视频格式(即FLASH8.0)–FLV.这种格式是在sorenson ... -
flash shiti
2014-03-14 10:32:4142.图形文件中,哪几种格式的矢量图能被Flash直接引用? A. *.ai B. *.eps C. *.cdr D. *.wmf 43.下图中哪几项可以打开平滑与整平工具? A. 从附属选项中挑选 和 钮 B. 从附属选项中挑选 和 钮 C. 选取... -
SDL Passolo 2011 合作版 SP9 (11.9.0.53) 已注册中文版
2014-12-28 12:18:17它支持众多文件格式,包括可执行程序文件、资源文件和基于 XML 的文件。文本可以被翻译为多种语言,包括亚洲语系(Unicode 码)以及书写方式为从右向左的语言,比如希伯来语和阿拉伯语。 Passolo 的使用非常容易,... -
Microsoft C# Windows程序设计(上下册)
2011-08-05 10:28:1311.2 位图文件格式 11.3 加载和绘制 11.4 图像信息 11.5 绘制图像 11.6 匹配矩形 11.7 旋转和剪切 11.8 显示部分图像 11.9 在图像上绘制 11.10 关于image类的更多内容 11.11 bitmap类 11.12 ... -
C#开发实战1200例(第1卷).(清华出版.王小科.王军.扫描版).part1
2016-06-16 20:55:43实例235 在RichTextBox控件中显示RTF格式的文件 实例236 使用RichTextBox控件保存文件 实例237 为RichTextBox控件添加自定义滚动条 实例238 在RichTextBox控件中实现关键字描红 实例239 在RichTextBox控件中... -
C#开发实战1200例(第1卷).(清华出版.王小科.王军.扫描版).part2
2016-06-16 20:59:52实例235 在RichTextBox控件中显示RTF格式的文件 实例236 使用RichTextBox控件保存文件 实例237 为RichTextBox控件添加自定义滚动条 实例238 在RichTextBox控件中实现关键字描红 实例239 在RichTextBox控件中... -
C#开发实战1200例(第1卷).(清华出版.王小科.王军.扫描版).part3
2016-06-16 21:02:21实例235 在RichTextBox控件中显示RTF格式的文件 实例236 使用RichTextBox控件保存文件 实例237 为RichTextBox控件添加自定义滚动条 实例238 在RichTextBox控件中实现关键字描红 实例239 在RichTextBox控件中... -
C#开发实例大全(基础卷).软件开发技术联盟(带详细书签) PDF 下载
2018-02-20 01:26:553.4 日期时间格式的数据处理 67 实例053 动态获得系统当前日期和时间 67 实例054 手动设置系统日期时间 69 实例055 根据生日自动计算员工年龄 70 实例056 根据年份判断十二生肖 71 实例057 获取当前日期是星期几 72 ...
-
MySQL 主从复制 Replication 详解(Linux 和 W
-
RapidScada从入门到精通
-
上海交通大学819信号系统与信号处理历年考研真题汇编
-
基于电商业务的全链路数据中台落地方案(全渠道、全环节、全流程)
-
element input-number 默认值设置为空
-
十年再出发,Dubbo 3.0 Preview 即将在 3 月发布
-
Homebrew 国内源安装脚本
-
PHP查找该用户无限下级
-
成绩斐然!MSR165数据记录仪可测量一级方程式赛车的重心力
-
第一型错误与第二型错误( I 型错误 II 型错误)
-
移动可用性测试(一):概述
-
深究字符编码的奥秘,与乱码说再见
-
小红书直播数据!小红书图文视频笔记数据榜单
-
量化交易策略开发的前期实践
-
STC15W408AS_串口程序
-
OracleDB数据库维护
-
开发代码流程.pptx
-
Liunx 优化思路与实操步骤
-
Clover Configurator.app.zip
-
vue3从0到1-超详细