⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 compress.pl

📁 用perl实现图象数据压缩
💻 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 + -