📄 compress.pl
字号:
#!/usr/bin/perl
#全面压缩.pl
#将apoxi使用的cpp文件中表示BMP图片的二进制数据进行压缩
#对一个目录中的所有bmp文件压缩,压缩后的文件保存到另外一个目录树中
#如果没有压缩则将源文件复制到源文件对应的位置
#程序并未清空目标目录,未避免垃圾的存在,需要将目标目录清空
use File::Copy;
use File::Path;
use File::Basename;
use strict;
my $read_dir; #待压缩文件所在目录
my $output_dir; #压缩后文件保存的目录
my $report_file = "compress_report.txt"; #压缩结果报告
my $compress_min_count = 2; #多少个相同16位数才进行压缩,除2外其它值未经过测试
#以下两个条件都是针对一个图片块,两个条件都满足才对该图片块进行压缩。不是针对一个文件。
my $compress_rate = 0.9; #压缩比例小于等于比该比例值才可能进行压缩
my $compress_amount = 100; #压缩掉的字节数大于等于该值才可能进行压缩
open(REPORT_FILE,">$report_file");
my @ref_lines; #每个元素指向每行输出的引用,用于对行进行排序
my $flag;
($flag,$read_dir,$output_dir) = check_command_line(@ARGV); #得到待压缩目录和输出目录,或者待压缩文件和输出文件
if ($flag eq 'FILE'){
my @out = compress_file($read_dir,$output_dir,$compress_min_count,$compress_rate,$compress_amount);
gather_statistic_data(\@out,\@ref_lines,$read_dir);
}
elsif ($flag eq 'DIR'){
mkdir($output_dir) if (!(-e $output_dir));
die "指定的目录:$read_dir不存在" if (!(-d $read_dir));
die "指定的输出目录:$output_dir不存在" if (!(-d $output_dir));
compress_dir($read_dir,$output_dir,$compress_min_count,$compress_rate,$compress_amount,\@ref_lines);
}
else {
die "请输入两个参数:\n待压缩文件目录和输出目录,或者待压缩文件和输出文件\n";
}
format_report(\@ref_lines);
close REPORT_FILE;
sub compress_dir{
my ($currentdir,$outputdir,$compress_min_count,$compress_rate,$compress_amount,$ref_ref_lines) = @_;
opendir(DIR,$currentdir);
my @files = readdir(DIR);
my ($file,$compressed_file);
foreach $file(@files) {
if ($file eq '.' || $file eq '..'){
next;
}
$compressed_file = $outputdir."\\".$file;
$file =$currentdir."\\".$file;
if (-d $file){
mkdir($compressed_file) if (!(-e $compressed_file));
compress_dir($file,$compressed_file,$compress_min_count,$compress_rate,$compress_amount,$ref_ref_lines);
}
else {
my ($name,$path,$suffix) = fileparse($file,qr{\..*});
if (lc $suffix eq ".cpp"){
my @out = compress_file($file,$compressed_file,$compress_min_count,$compress_rate,$compress_amount);
gather_statistic_data(\@out,$ref_ref_lines,$file);
}
else {
copy($file,$compressed_file);
}
}
}
closedir(DIR);
}
sub compress_file{
my ($ori_file,$compressed_file,$compress_min_count,$compress_rate,$compress_amount) = @_;
open(ORI_FILE,"<$ori_file") || die "读取文件$ori_file发生错误";
my @lines = <ORI_FILE>;
close ORI_FILE;
my @output_lines;
my $error;
my ($Original_data,$Head_size,$Context_size);
my $flag_succeed = 0;
while (1){
my ($line_start,$line_end,$line_struct,$line_struct_compressed) = find_block_line(\@lines);
if ($line_start == -1){
$error = "格式无法识别,未找到待压缩块开始、结束行或总结构块行。\n";
last;
}
else {
my $line_index;
for($line_index = 0;$line_index <= $line_start;$line_index++){
push @output_lines,shift(@lines);
}
my @block_uncompressed;
for(;$line_index < $line_end;$line_index++){
push @block_uncompressed,shift(@lines);
}
my ($ref_block_compressed,@compressed_info) = compress_block(\@block_uncompressed,$compress_min_count);
if ($ref_block_compressed == 0){ #检测出格式错误
$flag_succeed = 0;
last;
}
elsif ($compressed_info[0]-($compressed_info[1]+$compressed_info[2]) < $compress_amount
|| ($compressed_info[1]+$compressed_info[2])/$compressed_info[0] > $compress_rate){ #不满足压缩条件
push @output_lines,@block_uncompressed;
for(;$line_index <= $line_struct;$line_index++){
push @output_lines,shift(@lines);
}
$flag_succeed = 1;
}
else { #对该块进行了压缩
push @output_lines,@$ref_block_compressed;
$Original_data += $compressed_info[0];
$Head_size += $compressed_info[1];
$Context_size += $compressed_info[2];
for(;$line_index < $line_struct;$line_index++){
push @output_lines,shift(@lines);
}
shift(@lines); #去掉原来的结构体一行
push @output_lines,$line_struct_compressed; #修改结构体压缩标记后写入
$flag_succeed = 1;
}
}
}
if ($flag_succeed == 0){
copy($ori_file,$compressed_file);
return "error","error\t$ori_file\t".$error;
}
else {
push @output_lines,@lines;
open(COMPRESSED_FILE,">$compressed_file") || die "写文件$compressed_file发生错误";
print COMPRESSED_FILE @output_lines;
close COMPRESSED_FILE;
return "succeed","succeed\t$ori_file\n",$Original_data,$Head_size,$Context_size;
}
}
sub compress_block{
my ($ref_block_uncompressed,$compress_min_count) = @_;
my ($Head_size,$Context_size);
#由多行文本得到一组16位点阵
my @results = get_element(@$ref_block_uncompressed);
return 0,$results[1] if ($results[0] == 0);
#$column_count为文本行每行多少列8位数,作为压缩后输出列数
my ($ref_numbers,$column_count) = @results;
my $Original_data = ($#$ref_numbers+1)*2; #8位数据的个数
#将16位点阵转化成点值和每个点相邻重复个数两个数组,个数经过处理没有超过65535的
my ($ref_counts,$ref_elements) = get_element_count($ref_numbers);
#进一步处理,打上压缩和未压缩标记
my ($ref_heads,$ref_context) = get_head_context($ref_counts,$ref_elements,$compress_min_count);
#输出图片头
while(($#$ref_heads+1)%4 != 0){ # 如果头不是4的倍数,需要用0x77补齐,即头必须是32位对齐的
push @$ref_heads,0x77;
}
$Head_size = $#$ref_heads+1+4; #4为头大小,包含1个字节算法,1个字节保留,两个字节标记的头长度
$Context_size = ($#$ref_context+1)*2; #统计减少尺寸时以8位数为单位
unshift @$ref_heads,($Head_size)%256;
unshift @$ref_heads,int(($Head_size)/256);
unshift @$ref_heads,0; #保留
unshift @$ref_heads,0; #压缩格式版本
#将16位还原成8位点阵,与头合并
foreach (@$ref_context) {
push @$ref_heads,int($_/256),$_%256;
}
#格式化输出结果
my $ref_block_compressed = format_compressed_block($ref_heads,$column_count);
return $ref_block_compressed,$Original_data,$Head_size,$Context_size;
}
sub find_block_line{
my ($ref_lines) = @_;
my $line;
my $count = 0;
my $line_start = -1;
my $line_end = -1;
my $line_struct = -1;
my $line_struct_compressed;
foreach $line(@$ref_lines) {
if ($line_start == -1 && $line =~ m/^\s*static\s+const\s+\w+(\s+\w+)+\s*\[\s*\]\s*\=\s*\{\s+$/){
$line_start = $count;
}
if ($line_end == -1 && $line =~ m/^\s*\}\s*;\s+$/ && $line_start > -1){
$line_end = $count;
}
if ($line_struct == -1 && $line =~ m/^(\s*(extern|static)\s+const\s+BitmapRes\s+\w+\s*=\s*\{\s*\{\s*\d\s*,\s*(\d)\s*,\s*)(\d)(\s*\}\s*,\s*\d+\s*,\s*\d+\s*,\s*\w+\s*,\s*0x[0-9a-fA-F]+\s*\}\s*;\s+)/ && $3 == 0 && $4 == 0){
$line_struct_compressed = $1."1".$5;
$line_struct = $count;
last;
}
$count++;
}
if ($line_start == -1 || $line_end == -1 || $line_struct == -1 || $line_start >= $line_end || $line_end >= $line_struct){
return -1;
}
else {
return $line_start,$line_end,$line_struct,$line_struct_compressed;
}
}
sub gather_statistic_data{
my ($ref_out,$ref_ref_lines,$file) = @_;
print REPORT_FILE $$ref_out[1];
if ($$ref_out[0] eq "succeed" &&
($$ref_out[2]!=0&&$$ref_out[3]!=0&&$$ref_out[4]!=0)){ #该文件找到图形数据块但都不符合压缩条件
shift(@$ref_out);
shift(@$ref_out);
push @$ref_out,$file;
push @$ref_ref_lines,$ref_out;
}
}
sub get_element_count{
my ($ref_numbers) = @_;
my (@counts,@context);
my ($i,$j);
for ($i = 0;$i <= $#$ref_numbers;) {
push @context,$$ref_numbers[$i];
my $count = 1;
for($j = $i+1;$j <= $#$ref_numbers;$j++){
if ($$ref_numbers[$j] == $$ref_numbers[$i]){
$count++;
}
else {
last;
}
}
while ($count > 65535) {
push @counts,65535;
push @context,$$ref_numbers[$i];
$count -= 65535;
}
$i = $j;
push @counts,$count;
}
return \@counts,\@context;
}
sub get_head_context{
my ($ref_counts,$ref_elements,$compress_min_count) = @_;
my(@heads,@context);
my $head;
my ($i,$j);
for ($i = 0;$i <= $#$ref_counts;) {
push @context,$$ref_elements[$i];
if ($$ref_counts[$i] >= $compress_min_count){
if ($$ref_counts[$i] > 127){
push @heads,0x80;
push @heads,int($$ref_counts[$i]/256);
push @heads,$$ref_counts[$i]%256;
}
else {
$head = 0x80|$$ref_counts[$i];
push @heads,$head;
}
$i++;
}
else {
$head = 1;
for ($j = $i+1;$j <= $#$ref_counts && $$ref_counts[$j] < $compress_min_count && $head < 65536-$$ref_counts[$j];$j++) {
for (my $k = 0;$k < $$ref_counts[$j];$k++) {
push @context,$$ref_elements[$j];
$head++;
}
}
if ($head > 127){
push @heads,0x00;
push @heads,int($head/256);
push @heads,$head%256;
}
else {
push @heads,$head;
}
$i = $j;
}
}
return \@heads,\@context;
}
sub get_element{
my @block_uncompressed = @_;
my $whole_line;
my $line;
my $column_count = 0;
foreach $line(@block_uncompressed) {
$line =~ s/\s+//;
chomp($line);
if (!($line =~ m/^(0x[\d\w]{2,2},)+/)){
return 0,"格式无法识别。\n"
}
$whole_line .= $line;
if ($column_count == 0){
my @columns = split(',',$line);
$column_count = $#columns+1;
}
}
my (@ori_numbers,@numbers);
@ori_numbers = split(',',$whole_line);
foreach (@ori_numbers) {
$_ = hex($_);
}
my ($i,$j);
return 0,"点阵个数不是偶数。\n" if (($#ori_numbers+1)%2 != 0);
for ($i = 0;$i < $#ori_numbers ;$i++,$i++) {
push @numbers,$ori_numbers[$i]*256+$ori_numbers[$i+1];
}
return \@numbers,$column_count;
}
sub format_compressed_block{
my ($ref_heads,$column_count) = @_;
my @block_compressed;
my $current_column = 0;
my $current_line = 0;
my $line;
foreach $line(@$ref_heads) {
push @block_compressed,"\t" if ($current_column == 0);
push @block_compressed,sprintf("0x%02x",$line);
push @block_compressed,"," if ($current_line != $#$ref_heads);
if ($current_column == $column_count-1){
push @block_compressed,"\n";
$current_column = 0;
}
else {
$current_column++;
}
$current_line++;
}
push @block_compressed,"\n" if ($current_column != 0);
return \@block_compressed;
}
sub format_report{
my ($ref_ref_lines) = @_;
my ($Original_data,$Head_size,$Context_size,$compress_rate);
foreach (@$ref_ref_lines) {
push @$_,$$_[0]-$$_[1]-$$_[2];
push @$_,int((($$_[1]+$$_[2])/$$_[0]*10000+0.5))/100; #百分数,四舍五入保持两位小数
$Original_data += $$_[0];
$Head_size += $$_[1];
$Context_size += $$_[2];
}
@$ref_ref_lines = sort{${$b}[4] <=> ${$a}[4]} @$ref_ref_lines; #按照减小的点阵个数大小排序
$compress_rate = int(($Head_size+$Context_size)/$Original_data*10000+0.5)/100;
my $Decrement_size = $Original_data-$Head_size-$Context_size;
print REPORT_FILE "\n\n";
print REPORT_FILE qq(total:
Original_data: $Original_data Head_size: $Head_size Context_size:$Context_size
Decrement_size: $Decrement_size compress_rat : $compress_rate\%);
print REPORT_FILE "\n\n";
print REPORT_FILE "目录$read_dir下程序转换结果:\n";
print REPORT_FILE "Original size\tHead size\tContext size\tDecrement size\tCompress rate\tFile name\n";
foreach (@$ref_ref_lines) {
print REPORT_FILE $$_[0],"\t",$$_[1],"\t",$$_[2],"\t",$$_[4],"\t",$$_[5],"%\t",$$_[3],"\n";
}
}
sub check_command_line(){
my @command_line = @_;
return 'ERROR' if ($#command_line >1 || $#command_line == -1);
if (-f $command_line[0]){
my $compressed_file;
if ($#command_line == 0){
$compressed_file = $command_line[0].'_compressed.cpp';
}
elsif (-f $command_line[1] || !(-e $command_line[1])){
$compressed_file = $command_line[1];
}
else {
return 'ERROR';
}
return 'FILE',$command_line[0],$compressed_file;
}
elsif (-d $command_line[0] && $#command_line == 1){
if (-d $command_line[1] || !(-e $command_line[1])){
return 'DIR',$command_line[0],$command_line[1];
}
else {
return 'ERROR';
}
}
else {
return 'ERROR';
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -