Js版的Bad Apple
最近做了个Javascript版的Bad Apple,正好又是新年来临,权当是新年的礼物吧。以下是部分代码及解释:
MP3播放器
js用flash MP3播放器--as 2.0
var mp = new Sound();
var dt=0,df=false,ds=false;
mp.onLoad = function(){
/*trace(mp.getBytesLoaded());
trace(mp.getBytesTotal());
trace(mp.duration);*/
dt=0;
ds=true;
mp.stop();
};
//mp.loadSound("02 - In the Hall of the Mountain King.mp3",true);
mp.onSoundComplete = function(){dt=0;df=false;ds=true;};
var opArr={
mpadd:function(){
try{
mp.loadSound(arguments[1],true);
}
catch(ex){
return false;
}
return true;
},
mpplay:function(){
if(ds){
mp.start(0,1);
ds=false;
}else if(df){
mp.start(dt,1);
df=false;
}
return true;
},
mppause:function(){
dt=mp.position/1000;
df=true;
mp.stop();
return true;
},
mpstop:function(){
dt=0;
ds=true;
mp.stop();
return true;
},
mpbytesTotal:function(){
return mp.getBytesTotal();
},
mpbytesLoaded:function(){
return mp.getBytesLoaded();
},
mpduration:function(){
return mp.duration;
},
mpposition:function(){
return mp.position;
}
};
import flash.external.ExternalInterface;
ExternalInterface.addCallback('mphdl',this,function(op:String){
if(!!opArr[op])
return opArr[op].apply(this,arguments);
else
return 'have no this function!';
});
将截图从第一帧起,与后面的一帧的差异像素保存为数据,并编码保存。
处理截图,生成数据文件(C#)
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Collections;
namespace BA_Pics
{
class Program
{
private static string[] fileNames = Directory.GetFiles("D:\\我的文档\\The KMPlayer\\Capture\\8060");
private static int length = fileNames.Length;
private static int width = new Bitmap(fileNames[0]).Width,
height = new Bitmap(fileNames[0]).Height;
private static string code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-=`~!@#$%^&*()_+[]ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øù";
static void Main(string[] args)
{
string data = getDataString();
StreamWriter tw = new StreamWriter("D:\\data_badapple.txt", false, Encoding.BigEndianUnicode);
tw.Write(data);
tw.Flush();
tw.Close();
}
/// <summary>
/// 将所有的图片转换成2进制数组,颜色>0x777777的标记为1,否则为0
/// </summary>
/// <param name="path">文件夹路径</param>
/// <returns></returns>
private static BitArray[] readPic()
{
BitArray[] picsBit = new BitArray[length];
for (int i = 0; i < length; i++)
{
Bitmap pic = new Bitmap(fileNames[i]);
int mid = int.Parse("777777", System.Globalization.NumberStyles.AllowHexSpecifier);
picsBit[i] = new BitArray(width * height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
picsBit[i].Set(width * y + x, (mid > int.Parse(pic.GetPixel(x, y).Name.Remove(0, 2), System.Globalization.NumberStyles.AllowHexSpecifier) ? true : false));
}
}
}
//FileStream fs = new FileStream("D:\\data_badapple.bin", FileMode.Append);
//BinaryWriter bw = new BinaryWriter(fs);
//for (int i = 0; i < length; i++)
//{
// for (int j = 0; j < height * width; j++)
// {
// bw.Write(picsBit[i].Get(j));
// }
//}
return picsBit;
}
/// <summary>
/// 获取每一帧图片与其前一张图片的二进制数组的异或数组,即差异数组,第一帧直接克隆(与 0x00异或)
/// </summary>
/// <returns></returns>
private static BitArray[] xorPics()
{
BitArray[] xPics = new BitArray[length],
picsBit = readPic();
for (int i = 0; i < length; i++)
{
if (i == 0)
{
xPics[i] = new BitArray(picsBit[i]);
}
else
{
BitArray b1 = new BitArray(picsBit[i]),
b2 = new BitArray(picsBit[i-1]);
xPics[i] = new BitArray(b1.Xor(b2));
}
}
return xPics;
}
/// <summary>
/// 将差异数组转换为差异字符串
/// </summary>
/// <returns></returns>
private static string getDataString()
{
BitArray[] xPics = xorPics();
StringBuilder sb = new StringBuilder(1024 * 1024);
for (int i = 0; i < length; i++)
{
for (int j = 0; j < width * height; j++)
{
if (xPics[i].Get(j))
{
sb.Append(code[j % width]);
sb.Append(code[j / width]);
//sb.Append(Convert.ToChar(j % width));
//sb.Append(Convert.ToChar(j / width));
}
}
sb.Append('|');
}
return sb.ToString();
}
}
}
将编码后的文件用LZW算法压缩为浏览器能识别的字符串,保存为文本(80*60像素的完整mv可以压缩为939KB的文本文件)。
将生成的文件使用LZW算法压缩,生成压缩文件(C#)
using System;
using System.IO;
using System.Text;
namespace LZW_Compress_Decompress
{
class Program
{
//这里maxMark值不要再变大了,否则会有浏览器不认识的空字符,造成数据错误
private static int minMark = 256, maxMark = 54000;
private static int count = 0;
static void Main(string[] args)
{
TextReader tr = new StreamReader("D:\\data_badapple.txt");
string input = tr.ReadToEnd();
tr.Close();
string output = compress(input);
TextWriter tw = new StreamWriter("D:\\t.txt",false,Encoding.BigEndianUnicode);
tw.Write(output);
//for (int i = 0, len = output.Length; i < len; i++)
// tw.Write(output[i]);
tw.Flush();
tw.Close();
}
#region 数组初始化及清零操作
/// <summary>
/// 数组初始化
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
private static bool initArr(char[][] arr)
{
for (int i = 0, len = arr.Length; i < len; i++)
{
arr[i] = new char[minMark];
if (i == 0)
for (int j = 0, len_1 = arr[i].Length; j < len_1; j++)
arr[i][j] = (char)j;
}
cleanArr(arr);
return true;
}
/// <summary>
/// 数组清零
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
private static bool cleanArr(char[][] arr)
{
count++;
for (int i = 1, len = arr.Length; i < len; i++)
{
for (int j = 0, len_1 = arr[i].Length; j < len_1; j++)
{
arr[i][j] = (char)0;
}
}
return true;
}
#endregion
#region LZW压缩算法
/// <summary>
/// 压缩输入字符串,并返回输出字符串
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private static string compress(string input)
{
//输出流
StringBuilder output = new StringBuilder(1024 * 1024);
//标记字典
char[][] markArray = new char[maxMark][];
//标记、前缀、后缀
char mark, prefix = (char)0, suffix = (char)0;
//标记是否读过数据
bool flag = false;
//当前最大标记号
int currentMaxMark = minMark;
initArr(markArray); //初始化字典
for (int i = 0, length = input.Length; i < length; i++)
{
//读字符
suffix = input[i];
flag = true;
//查找标记
mark = markArray[(int)prefix][(int)suffix];
if (mark == 0) //标记未出现过
{
markArray[(int)prefix][(int)suffix] = (char)currentMaxMark;
if (currentMaxMark == maxMark) //字典满额,回归操作
{
output.Append(prefix);
i--;
//currentMaxMark++;
currentMaxMark = minMark;
prefix = (char)0;
//清除字典
cleanArr(markArray);
//追加clear标记
output.Append((char)(254));
continue;
}
else //通常情况
{
output.Append(prefix);
currentMaxMark++;
}
prefix = suffix;
}
else //标记出现过
{
prefix = mark;
}
}
if (flag)
output.Append(prefix);
return output.ToString();
}
#endregion
}
}
js版的LZW解压缩算法,由于考虑到浏览器的解压速度,所以使用了空间换时间的方式,增大了解压时的内存消耗(理论上解压需要约20M内存开销用于换取时间),但大大减少的时间消耗,需要Chrome浏览器,并且系统要有250M以上内存空余(本人win7下,chrome无插件,解压160*120分辨率mv文件并还原为我的屏幕系统能播放的帧数组后的内存消耗时的测试结果)。
JavaScript的lzw解压缩
/*LZW Decompression
*lzwString : 待解压的字符串
*/
var lzwDecompress = function(lzwString){
//解压后的字符串
var s = '',
//用于查询字符串的字典
dictionary = [],
//用于查询是否存在的字典
boolDic = [],
//clear标记
clear = 254,
//原始数据长度
oriLength = 256,
//当前最大标记号
currentMaxMark = 256,
//前缀
prefix,
//后缀
suffix,
//最小自定义标记号
minMark = 256,
//最大自定义标记号
maxMark = 54000,
//是否读取过压缩字符串
isRead = false,
//组合
entry = '',
//As it's name~
initDictionary = function(length){
var dic = [];
for(var i = 0; i < length; i++)
dic[i] = String.fromCharCode(i);
return dic;
},
initDic = function(){
var dic = [];
for(var i = 0; i < maxMark; i++){
dic[i] = new Array(minMark);
for(var j = 0; j < minMark; j++){
if(i == 0)
dic[i][j] = true;
else
dic[i][j] = false;
}
}
return dic;
},
//查字典获取字符串
getString = function(dicIndex){
//var isNumber = !!dicIndex.match(/^\d+$/);
dicIndex = dicIndex.charCodeAt();
return dictionary[dicIndex];
//if(dicIndex < oriLength)
// return dictionary[dicIndex];
//else
// return getString(dictionary[dicIndex][0]) + getString(dictionary[dicIndex][1]);
},
//查字典,看词组是否已存在于字典中
inDic = function(pre,suf){
pre = pre.charCodeAt();
suf = suf.charCodeAt();
return boolDic[pre][suf];
},
//查字典获取第一个字符
getFirstChar = function(dicIndex){
dicIndex = dicIndex.charCodeAt();
return dictionary[dicIndex][0];
//if(dicIndex < oriLength)
// return dictionary[dicIndex];
//else
// return getFirstChar(dictionary[dicIndex][0]);
};
if(lzwString.length > 2){
isRead = true;
dictionary = initDictionary(minMark);
boolDic = initDic();
prefix = lzwString[0];
var has = false,
firstChar;
for(var i = 1, length = lzwString.length; i < length; i++){
suffix = lzwString[i];
suffix = suffix.charCodeAt();
if(clear == suffix){ //发现清空标号
s += getString(prefix);
currentMaxMark = minMark;
dictionary = initDictionary(minMark);
boolDic = initDic();
prefix = lzwString[++i];
continue;
}
if(suffix < currentMaxMark){ //后缀为单字符或者为字典内已有的自定义标号
firstChar = getFirstChar(String.fromCharCode(suffix));
/*
for(var j = (prefix.charCodeAt() > minMark ? prefix.charCodeAt() : minMark); j < currentMaxMark; j++){//查询组合是否在字典中
if(dictionary[j] == getString(prefix) + firstChar){//存在于字典中
//if(dictionary[j][0] == prefix && dictionary[j][1] == firstChar){
has = true;
break;
}
}*/
if(!inDic(prefix,firstChar)){//如果组合不存在于字典
dictionary.push(getString(prefix) + firstChar);
boolDic[prefix.charCodeAt()][firstChar.charCodeAt()] = true;
currentMaxMark++;
//has = false;
}
s += getString(prefix);
prefix = String.fromCharCode(suffix);
}
else if(suffix == currentMaxMark){ //后缀为当前准备写入字典的编码标号
dictionary.push(getString(prefix) + getFirstChar(prefix));
boolDic[prefix.charCodeAt()][getFirstChar(prefix).charCodeAt()] = true;
currentMaxMark++;
s += getString(prefix);
prefix = String.fromCharCode(suffix);
}
}
}
if(isRead)
s += getString(prefix);
return s;
};
PS:下载后,由于Flash版的MP3播放器不支持本地使用,请将文件部署到服务器,并使用Chrome打开 Demo.html 页面,其他浏览器基本上会半残。
另外Chrome在解压文件时,可能会出现让你选择"关闭网页"与"等待"的选项,请点击"等待"。
80*60分辨率的文件解压约需要10秒,160*120的文件约需要1分钟,请耐心等待。
以上,欢迎各种技术讨论,谢绝喷,谢谢。
下面是运行160*120分辨率的截图
lzw参考资料:http://blog.csdn.net/whycadi/archive/2006/05/29/760576.aspx
http://www.cnblogs.com/jillzhang/archive/2006/11/06/551298.html