JPEG格式的图片是最常见的图片格式,它采用有损压缩算法,很好的处理了图片体积和质量之间的关系,更有利于传播,所以JPEG也是最常见的网络标准文件格式。使用JPEG格式压缩的图片文件一般也被称为JPEG Files,最普遍被使用的扩展名格式为.jpg。但JEPG标准只是一些压缩压法的描述,它并没有指定一个文件格式,为了能够保存和传输文件,一些文件格式标准被发明出来,最常见的文件格式是JPEG/Exif和JPEG/JFIF。JPEG和JIFF/Exif的关系类似于unicode和utf-8,一个用来编码一个用来存储。
JPEG文件一般来说采用有损压缩,所谓有损压缩,是指JPEG通过破坏性数据压缩达到减少图片尺寸,便于传输的目的。我们最常见的用photoshop对图片进行质量选择,质量越低,图片体积越小,质量越高,图片体积越大。 在web应用中,我们经常会被要求对图片进行压缩处理,以达到减少宽带占用,加快页面展示的目的。一些测试软件也经常会对web页面的图片大小给出建议,例如thinkwithgoogle。我们经常被告知最简单的方式是用photoshtop把图片调整成相应大小,“另存为web”格式,然后选择合适的质量即可。那么通过Photoshop压缩之后,真的可以高枕无忧,图片完全没有其它优化的余地了吗,要弄清这个问题,我们需要分析一下JPEG文件的存储格式。
JPEG元数据结构
JPEG文件包含多个数据段(segment),每个段包括不同类型的数据,数据段之间通过两个字节被称之为marker的标记分开,marker以0xff开头,后面一个接一个字节来表述不同类型的marker。一些marker只有单独两个字节,另外一些会包括数据的长度和数据。具体的结构如下:
JEPG的元数据存放在APPn段中,注释数据存放在com段中,一些厂商可能会在APPn段中添加他们自己的数据,例如相机型号,GPS信息等。
哪些信息可以被压缩
com是图片的注释信息,而APPn是应用程序存放的相关信息,这些信息对图片本身来说都没有作用,都是可以移出的,我们拿来一张PS处理之后的图片,用二进制打开,发现以下内容:
很显然,这些信息对于图片来说都是无用的内容,是可以删掉的,而且删掉之后,并不会影响图片质量。
如何压缩
PHP
<?php class JPEG{ //JPEG Metadata Structure private $markerkey=array("soi"=>0xd8,"sof0"=>0xc0,"sof2"=>0xc2,"dht"=>0xc4,"dqt"=>0xdb,"dri"=>0xdd,"sos"=>0xda,"rst"=>array(0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7),"app"=>array(0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef),"com"=>0xfe,"eoi"=>0xd9); private $markerdata=array(); private $ImageScan; private $Image; private $PosPointer; private $ImageSize; public function __construct($argument){ if(file_exists($argument)){ $this->Image = fopen($argument,"rb+"); $this->ImageSize= filesize($argument); }else{ die("File Not Exists"); } if($this->GetByte(2)!==0xd8ff){ die("Not a JPEG Image"); } $this->Parse(); } public function GetByte($len){ if(feof($this->Image)) return NULL; $tmp = fread($this->Image,$len); $tmp2=array(); $this->PosPointer=ftell($this->Image); if($len==1){ $tmp2=unpack("C",$tmp); }else if($len==2){ $tmp2=unpack("S",$tmp); }else if($len==4){ $tmp2=unpack("L",$tmp); }else{ $tmp2[1]=$tmp; } return $tmp2[1]; } public function GetMarkerData(){ $buffer=""; foreach($this->markerdata as $d){ foreach($d as $c){ $buffer.=$c; } } return $buffer; } public function get_marker($byte){ foreach($this->markerkey as $meta=>$bit){ if(is_array($bit)){ foreach($bit as $b){ if($byte == $b) return $meta; } } if($byte == $bit) return $meta; } return NULL; } public function compress(){ //Remove Com Marker $this->markerdata['com']=array(); //Remove App Marker $this->markerdata['app']=array(); } public function Parse(){ while(($byte=$this->GetByte(1))!==NULL){ $nxtbyte = @$this->GetByte(1); if($byte == 0xff){ $marker = $this->get_marker($nxtbyte); //Variable Size Marker if($marker == "sof" || $marker == "sof2" || $marker == "dht" || $marker == "dqt" || $marker == "sof0" || $marker == "app" || $marker == "com"){ $offset1= $this->GetByte(1); $offset2 = $this->GetByte(1); $len = $offset1*256+$offset2; $data = $this->GetByte($len-2); //full data include marker and length $fulldata = pack("C",0xff).pack("C",$nxtbyte).pack("C",$offset1).pack("C",$offset2).$data; $this->markerdata[$marker][]= $fulldata; }else if($marker=="dri"){ //pass }else if($marker=="rst"){ //pass }else if($marker=="sos"){ //SOS Segment $len = $this->ImageSize-$this->PosPointer-1; $this->ImageScan = pack("C",0xff). pack("C",0xda).$this->GetByte($len); } } } } public function GetImageBin(){ return pack("S",0xd8ff).$this->GetMarkerData().$this->ImageScan.pack("S",0xd9ff); } public function Storage(){ fseek($this->Image,0); fwrite($this->Image,$this->GetImageBin()); } public function __destruct(){ fclose($this->Image); } } $image = new JPEG("test.jpg"); $image->compress(); $image->Storage();
评论列表: