• 可以调整gif动画图片尺寸的很实用的php类


    类的使用demo:

    <?php 
    //http://www.cnblogs.com/roucheng/
    require_once "roucheng.php";  
    $gr = new gifresizer; 
    $gr->temp_dir = "keleyi"; 
    $gr->resize("keleyi.gif","keleyi_resized.gif",500,500); 
    ?>

    类的源代码,保存为roucheng.php文件:

      1 <? 
      2     /** 
      3     * 
      4     * Resizes Animated GIF Files
      5     *
      6     *   ///IMPORTANT NOTE: The script needs a temporary directory where all the frames should be extracted. 
      7     *   Create a directory with a 777 permission level and write the path into $temp_dir variable below. 
      8     *   
      9     *   Default directory is "frames".
     10     */
     11  
     12     class gifresizer {
     13  
     14         public $temp_dir = "frames";
     15         private $pointer = 0;
     16         private $index = 0;
     17         private $globaldata = array();
     18         private $imagedata = array();
     19         private $imageinfo = array();
     20         private $handle = 0;
     21         private $orgvars = array();
     22         private $encdata = array();
     23         private $parsedfiles = array();
     24         private $originalwidth = 0;
     25         private $originalheight = 0;
     26         private $wr,$hr;
     27         private $props = array();
     28         private $decoding = false;
     29  
     30         /** 
     31         * Public part of the class
     32         * 
     33         * @orgfile - Original file path
     34         * @newfile - New filename with path
     35         * @width   - Desired image width 
     36         * @height  - Desired image height
     37         */
     38         function resize($orgfile,$newfile,$width,$height){
     39             $this->decode($orgfile);
     40             $this->wr=$width/$this->originalwidth;
     41             $this->hr=$height/$this->originalheight;
     42             $this->resizeframes();
     43             $this->encode($newfile,$width,$height);
     44             $this->clearframes();
     45         }   
     46  
     47         /** 
     48         * GIF Decoder function.
     49         * Parses the GIF animation into single frames.
     50         */
     51         private function decode($filename){
     52             $this->decoding = true;            
     53             $this->clearvariables();
     54             $this->loadfile($filename);
     55             $this->get_gif_header();
     56             $this->get_graphics_extension(0);
     57             $this->get_application_data();
     58             $this->get_application_data();
     59             $this->get_image_block(0);
     60             $this->get_graphics_extension(1);
     61             $this->get_comment_data();
     62             $this->get_application_data();
     63             $this->get_image_block(1);
     64             while(!$this->checkbyte(0x3b) && !$this->checkEOF()){
     65                 $this->get_comment_data(1);
     66                 $this->get_graphics_extension(2);
     67                 $this->get_image_block(2);
     68             }
     69             $this->writeframes(time());      
     70             $this->closefile();
     71             $this->decoding = false;
     72         }
     73  
     74         /** 
     75         * GIF Encoder function.
     76         * Combines the parsed GIF frames into one single animation.
     77         */
     78         private function encode($new_filename,$newwidth,$newheight){
     79             $mystring = "";
     80             $this->pointer = 0;
     81             $this->imagedata = array();
     82             $this->imageinfo = array();
     83             $this->handle = 0;
     84             $this->index=0;
     85  
     86             $k=0;
     87             foreach($this->parsedfiles as $imagepart){
     88                 $this->loadfile($imagepart);
     89                 $this->get_gif_header();
     90                 $this->get_application_data();
     91                 $this->get_comment_data();
     92                 $this->get_graphics_extension(0);
     93                 $this->get_image_block(0);
     94  
     95                 //get transparent color index and color
     96                 if(isset($this->encdata[$this->index-1]))
     97                     $gxdata = $this->encdata[$this->index-1]["graphicsextension"];
     98                 else
     99                     $gxdata = null;
    100                 $ghdata = $this->imageinfo["gifheader"];
    101                 $trcolor = "";
    102                 $hastransparency=($gxdata[3]&&1==1);
    103  
    104                 if($hastransparency){
    105                     $trcx = ord($gxdata[6]);
    106                     $trcolor = substr($ghdata,13+$trcx*3,3);
    107                 }
    108  
    109                 //global color table to image data;
    110                 $this->transfercolortable($this->imageinfo["gifheader"],$this->imagedata[$this->index-1]["imagedata"]); 
    111  
    112                 $imageblock = &$this->imagedata[$this->index-1]["imagedata"];
    113  
    114                 //if transparency exists transfer transparency index
    115                 if($hastransparency){
    116                     $haslocalcolortable = ((ord($imageblock[9])&128)==128);
    117                     if($haslocalcolortable){
    118                         //local table exists. determine boundaries and look for it.
    119                         $tablesize=(pow(2,(ord($imageblock[9])&7)+1)*3)+10;
    120                         $this->orgvars[$this->index-1]["transparent_color_index"] = 
    121                         ((strrpos(substr($this->imagedata[$this->index-1]["imagedata"],0,$tablesize),$trcolor)-10)/3);        
    122                     }else{
    123                         //local table doesnt exist, look at the global one.
    124                         $tablesize=(pow(2,(ord($gxdata[10])&7)+1)*3)+10;
    125                         $this->orgvars[$this->index-1]["transparent_color_index"] = 
    126                         ((strrpos(substr($ghdata,0,$tablesize),$trcolor)-10)/3);    
    127                     }               
    128                 }
    129  
    130                 //apply original delay time,transparent index and disposal values to graphics extension
    131  
    132                 if(!$this->imagedata[$this->index-1]["graphicsextension"]) $this->imagedata[$this->index-1]["graphicsextension"] = chr(0x21).chr(0xf9).chr(0x04).chr(0x00).chr(0x00).chr(0x00).chr(0x00).chr(0x00);
    133  
    134                 $imagedata = &$this->imagedata[$this->index-1]["graphicsextension"];
    135  
    136                 $imagedata[3] = chr((ord($imagedata[3]) & 0xE3) | ($this->orgvars[$this->index-1]["disposal_method"] << 2));
    137                 $imagedata[4] = chr(($this->orgvars[$this->index-1]["delay_time"] % 256));
    138                 $imagedata[5] = chr(floor($this->orgvars[$this->index-1]["delay_time"] / 256));
    139                 if($hastransparency){
    140                     $imagedata[6] = chr($this->orgvars[$this->index-1]["transparent_color_index"]);
    141                 }
    142                 $imagedata[3] = chr(ord($imagedata[3])|$hastransparency);
    143  
    144                 //apply calculated left and top offset 
    145                 $imageblock[1] = chr(round(($this->orgvars[$this->index-1]["offset_left"]*$this->wr) % 256));
    146                 $imageblock[2] = chr(floor(($this->orgvars[$this->index-1]["offset_left"]*$this->wr) / 256));
    147                 $imageblock[3] = chr(round(($this->orgvars[$this->index-1]["offset_top"]*$this->hr) % 256));
    148                 $imageblock[4] = chr(floor(($this->orgvars[$this->index-1]["offset_top"]*$this->hr) / 256));           
    149  
    150                 if($this->index==1){
    151                     if(!isset($this->imageinfo["applicationdata"]) || !$this->imageinfo["applicationdata"]) 
    152                         $this->imageinfo["applicationdata"]=chr(0x21).chr(0xff).chr(0x0b)."NETSCAPE2.0".chr(0x03).chr(0x01).chr(0x00).chr(0x00).chr(0x00);
    153                     if(!isset($this->imageinfo["commentdata"]) || !$this->imageinfo["commentdata"])
    154                         $this->imageinfo["commentdata"] = chr(0x21).chr(0xfe).chr(0x10)."PHPGIFRESIZER1.0".chr(0);
    155                     $mystring .= $this->orgvars["gifheader"]. $this->imageinfo["applicationdata"].$this->imageinfo["commentdata"];
    156                     if(isset($this->orgvars["hasgx_type_0"]) && $this->orgvars["hasgx_type_0"]) $mystring .= $this->globaldata["graphicsextension_0"];
    157                     if(isset($this->orgvars["hasgx_type_1"]) && $this->orgvars["hasgx_type_1"]) $mystring .= $this->globaldata["graphicsextension"];
    158                 }
    159  
    160                 $mystring .= $imagedata . $imageblock;
    161                 $k++;
    162                 $this->closefile();
    163             }
    164  
    165             $mystring .= chr(0x3b); 
    166  
    167             //applying new width & height to gif header
    168             $mystring[6] = chr($newwidth % 256);
    169             $mystring[7] = chr(floor($newwidth / 256));
    170             $mystring[8] = chr($newheight % 256);
    171             $mystring[9] = chr(floor($newheight / 256));
    172             $mystring[11]= $this->orgvars["background_color"];
    173             //if(file_exists($new_filename)){unlink($new_filename);}
    174             file_put_contents($new_filename,$mystring);
    175         }
    176  
    177         /** 
    178         * Variable Reset function
    179         * If a instance is used multiple times, it's needed. Trust me.
    180         */
    181         private function clearvariables(){
    182             $this->pointer = 0;
    183             $this->index = 0;
    184             $this->imagedata = array();
    185             $this->imageinfo = array();            
    186             $this->handle = 0;
    187             $this->parsedfiles = array();
    188         }
    189  
    190         /** 
    191         * Clear Frames function
    192         * For deleting the frames after encoding.
    193         */
    194         private function clearframes(){
    195             foreach($this->parsedfiles as $temp_frame){
    196                 unlink($temp_frame);
    197             }
    198         }
    199  
    200         /** 
    201         * Frame Writer
    202         * Writes the GIF frames into files.
    203         */
    204         private function writeframes($prepend){
    205             for($i=0;$i<sizeof($this->imagedata);$i++){
    206                 file_put_contents($this->temp_dir."/frame_".$prepend."_".str_pad($i,2,"0",STR_PAD_LEFT).".gif",$this->imageinfo["gifheader"].$this->imagedata[$i]["graphicsextension"].$this->imagedata[$i]["imagedata"].chr(0x3b));
    207                 $this->parsedfiles[]=$this->temp_dir."/frame_".$prepend."_".str_pad($i,2,"0",STR_PAD_LEFT).".gif";
    208             }
    209         }
    210  
    211         /** 
    212         * Color Palette Transfer Device
    213         * Transferring Global Color Table (GCT) from frames into Local Color Tables in animation.
    214         */
    215         private function transfercolortable($src,&$dst){
    216             //src is gif header,dst is image data block
    217             //if global color table exists,transfer it
    218             if((ord($src[10])&128)==128){
    219                 //Gif Header Global Color Table Length
    220                 $ghctl = pow(2,$this->readbits(ord($src[10]),5,3)+1)*3;
    221                 //cut global color table from gif header
    222                 $ghgct = substr($src,13,$ghctl);
    223                 //check image block color table length
    224                 if((ord($dst[9])&128)==128){
    225                     //Image data contains color table. skip.
    226                 }else{
    227                     //Image data needs a color table.
    228                     //get last color table length so we can truncate the dummy color table
    229                     $idctl = pow(2,$this->readbits(ord($dst[9]),5,3)+1)*3;
    230                     //set color table flag and length   
    231                     $dst[9] = chr(ord($dst[9]) | (0x80 | (log($ghctl/3,2)-1)));
    232                     //inject color table
    233                     $dst = substr($dst,0,10).$ghgct.substr($dst,-1*strlen($dst)+10);
    234                 }
    235             }else{
    236                 //global color table doesn't exist. skip.
    237             }
    238         }
    239  
    240         /** 
    241         * GIF Parser Functions.
    242         * Below functions are the main structure parser components.
    243         */
    244         private function get_gif_header(){
    245             $this->p_forward(10);
    246             if($this->readbits(($mybyte=$this->readbyte_int()),0,1)==1){
    247                 $this->p_forward(2);
    248                 $this->p_forward(pow(2,$this->readbits($mybyte,5,3)+1)*3);
    249             }else{
    250                 $this->p_forward(2);
    251             }
    252  
    253             $this->imageinfo["gifheader"]=$this->datapart(0,$this->pointer);
    254             if($this->decoding){
    255                 $this->orgvars["gifheader"]=$this->imageinfo["gifheader"];
    256                 $this->originalwidth = ord($this->orgvars["gifheader"][7])*256+ord($this->orgvars["gifheader"][6]);
    257                 $this->originalheight = ord($this->orgvars["gifheader"][9])*256+ord($this->orgvars["gifheader"][8]);
    258                 $this->orgvars["background_color"]=$this->orgvars["gifheader"][11];
    259             }
    260  
    261         }
    262         //-------------------------------------------------------
    263         private function get_application_data(){
    264             $startdata = $this->readbyte(2);
    265             if($startdata==chr(0x21).chr(0xff)){
    266                 $start = $this->pointer - 2;
    267                 $this->p_forward($this->readbyte_int());
    268                 $this->read_data_stream($this->readbyte_int());
    269                 $this->imageinfo["applicationdata"] = $this->datapart($start,$this->pointer-$start);
    270             }else{
    271                 $this->p_rewind(2);
    272             }
    273         }
    274         //-------------------------------------------------------
    275         private function get_comment_data(){
    276             $startdata = $this->readbyte(2);
    277             if($startdata==chr(0x21).chr(0xfe)){
    278                 $start = $this->pointer - 2;
    279                 $this->read_data_stream($this->readbyte_int());
    280                 $this->imageinfo["commentdata"] = $this->datapart($start,$this->pointer-$start);
    281             }else{
    282                 $this->p_rewind(2);
    283             }
    284         }
    285         //-------------------------------------------------------
    286         private function get_graphics_extension($type){
    287             $startdata = $this->readbyte(2);
    288             if($startdata==chr(0x21).chr(0xf9)){
    289                 $start = $this->pointer - 2;
    290                 $this->p_forward($this->readbyte_int());
    291                 $this->p_forward(1);
    292                 if($type==2){
    293                     $this->imagedata[$this->index]["graphicsextension"] = $this->datapart($start,$this->pointer-$start);
    294                 }else if($type==1){
    295                     $this->orgvars["hasgx_type_1"] = 1;
    296                     $this->globaldata["graphicsextension"] = $this->datapart($start,$this->pointer-$start);
    297                 }else if($type==0 && $this->decoding==false){
    298                     $this->encdata[$this->index]["graphicsextension"] = $this->datapart($start,$this->pointer-$start);
    299                 }else if($type==0 && $this->decoding==true){
    300                     $this->orgvars["hasgx_type_0"] = 1;
    301                     $this->globaldata["graphicsextension_0"] = $this->datapart($start,$this->pointer-$start);
    302                 }
    303             }else{
    304                 $this->p_rewind(2);
    305             }
    306         }
    307         //-------------------------------------------------------
    308         private function get_image_block($type){
    309             if($this->checkbyte(0x2c)){
    310                 $start = $this->pointer;
    311                 $this->p_forward(9);
    312                 if($this->readbits(($mybyte=$this->readbyte_int()),0,1)==1){
    313                     $this->p_forward(pow(2,$this->readbits($mybyte,5,3)+1)*3);
    314                 }
    315                 $this->p_forward(1);
    316                 $this->read_data_stream($this->readbyte_int());
    317                 $this->imagedata[$this->index]["imagedata"] = $this->datapart($start,$this->pointer-$start);
    318  
    319                 if($type==0){
    320                     $this->orgvars["hasgx_type_0"] = 0;
    321                     if(isset($this->globaldata["graphicsextension_0"]))
    322                         $this->imagedata[$this->index]["graphicsextension"]=$this->globaldata["graphicsextension_0"];
    323                     else
    324                         $this->imagedata[$this->index]["graphicsextension"]=null;
    325                     unset($this->globaldata["graphicsextension_0"]);
    326                 }elseif($type==1){
    327                     if(isset($this->orgvars["hasgx_type_1"]) && $this->orgvars["hasgx_type_1"]==1){
    328                         $this->orgvars["hasgx_type_1"] = 0;
    329                         $this->imagedata[$this->index]["graphicsextension"]=$this->globaldata["graphicsextension"];
    330                         unset($this->globaldata["graphicsextension"]);
    331                     }else{
    332                         $this->orgvars["hasgx_type_0"] = 0;
    333                         $this->imagedata[$this->index]["graphicsextension"]=$this->globaldata["graphicsextension_0"];
    334                         unset($this->globaldata["graphicsextension_0"]);
    335                     }
    336                 }
    337  
    338                 $this->parse_image_data();
    339                 $this->index++;
    340  
    341             }
    342         }
    343         //-------------------------------------------------------
    344         private function parse_image_data(){
    345             $this->imagedata[$this->index]["disposal_method"] = $this->get_imagedata_bit("ext",3,3,3);
    346             $this->imagedata[$this->index]["user_input_flag"] = $this->get_imagedata_bit("ext",3,6,1);
    347             $this->imagedata[$this->index]["transparent_color_flag"] = $this->get_imagedata_bit("ext",3,7,1);
    348             $this->imagedata[$this->index]["delay_time"] = $this->dualbyteval($this->get_imagedata_byte("ext",4,2));
    349             $this->imagedata[$this->index]["transparent_color_index"] = ord($this->get_imagedata_byte("ext",6,1));
    350             $this->imagedata[$this->index]["offset_left"] = $this->dualbyteval($this->get_imagedata_byte("dat",1,2));
    351             $this->imagedata[$this->index]["offset_top"] = $this->dualbyteval($this->get_imagedata_byte("dat",3,2));
    352             $this->imagedata[$this->index]["width"] = $this->dualbyteval($this->get_imagedata_byte("dat",5,2));
    353             $this->imagedata[$this->index]["height"] = $this->dualbyteval($this->get_imagedata_byte("dat",7,2));
    354             $this->imagedata[$this->index]["local_color_table_flag"] = $this->get_imagedata_bit("dat",9,0,1);
    355             $this->imagedata[$this->index]["interlace_flag"] = $this->get_imagedata_bit("dat",9,1,1);
    356             $this->imagedata[$this->index]["sort_flag"] = $this->get_imagedata_bit("dat",9,2,1);
    357             $this->imagedata[$this->index]["color_table_size"] = pow(2,$this->get_imagedata_bit("dat",9,5,3)+1)*3;
    358             $this->imagedata[$this->index]["color_table"] = substr($this->imagedata[$this->index]["imagedata"],10,$this->imagedata[$this->index]["color_table_size"]);
    359             $this->imagedata[$this->index]["lzw_code_size"] = ord($this->get_imagedata_byte("dat",10,1));
    360             if($this->decoding){
    361                 $this->orgvars[$this->index]["transparent_color_flag"] = $this->imagedata[$this->index]["transparent_color_flag"];
    362                 $this->orgvars[$this->index]["transparent_color_index"] = $this->imagedata[$this->index]["transparent_color_index"];
    363                 $this->orgvars[$this->index]["delay_time"] = $this->imagedata[$this->index]["delay_time"];
    364                 $this->orgvars[$this->index]["disposal_method"] = $this->imagedata[$this->index]["disposal_method"];
    365                 $this->orgvars[$this->index]["offset_left"] = $this->imagedata[$this->index]["offset_left"];
    366                 $this->orgvars[$this->index]["offset_top"] = $this->imagedata[$this->index]["offset_top"];
    367             }
    368         }
    369         //-------------------------------------------------------
    370         private function get_imagedata_byte($type,$start,$length){
    371             if($type=="ext")
    372                 return substr($this->imagedata[$this->index]["graphicsextension"],$start,$length);
    373             elseif($type=="dat")
    374                 return substr($this->imagedata[$this->index]["imagedata"],$start,$length);
    375         }
    376         //-------------------------------------------------------
    377         private function get_imagedata_bit($type,$byteindex,$bitstart,$bitlength){
    378             if($type=="ext")
    379                 return $this->readbits(ord(substr($this->imagedata[$this->index]["graphicsextension"],$byteindex,1)),$bitstart,$bitlength);
    380             elseif($type=="dat")
    381                 return $this->readbits(ord(substr($this->imagedata[$this->index]["imagedata"],$byteindex,1)),$bitstart,$bitlength);
    382         }
    383         //-------------------------------------------------------
    384         private function dualbyteval($s){
    385             $i = ord($s[1])*256 + ord($s[0]);
    386             return $i;
    387         }
    388         //------------   Helper Functions ---------------------
    389         private function read_data_stream($first_length){
    390             $this->p_forward($first_length);
    391             $length=$this->readbyte_int();
    392             if($length!=0) {
    393                 while($length!=0){
    394                     $this->p_forward($length);
    395                     $length=$this->readbyte_int();
    396                 }
    397             }
    398             return true;
    399         }
    400         //-------------------------------------------------------
    401         private function loadfile($filename){
    402             $this->handle = fopen($filename,"rb");
    403             $this->pointer = 0;
    404         }
    405         //-------------------------------------------------------
    406         private function closefile(){
    407             fclose($this->handle);
    408             $this->handle=0;
    409         }
    410         //-------------------------------------------------------
    411         private function readbyte($byte_count){
    412             $data = fread($this->handle,$byte_count);
    413             $this->pointer += $byte_count;
    414             return $data;
    415         }
    416         //-------------------------------------------------------
    417         private function readbyte_int(){
    418             $data = fread($this->handle,1);
    419             $this->pointer++;
    420             return ord($data);
    421         }
    422         //-------------------------------------------------------
    423         private function readbits($byte,$start,$length){
    424             $bin = str_pad(decbin($byte),8,"0",STR_PAD_LEFT);
    425             $data = substr($bin,$start,$length);
    426             return bindec($data);
    427         }
    428         //-------------------------------------------------------
    429         private function p_rewind($length){
    430             $this->pointer-=$length;
    431             fseek($this->handle,$this->pointer);
    432         }
    433         //-------------------------------------------------------
    434         private function p_forward($length){
    435             $this->pointer+=$length;
    436             fseek($this->handle,$this->pointer);
    437         }
    438         //-------------------------------------------------------
    439         private function datapart($start,$length){
    440             fseek($this->handle,$start);
    441             $data = fread($this->handle,$length);
    442             fseek($this->handle,$this->pointer);
    443             return $data;
    444         }
    445         //-------------------------------------------------------
    446         private function checkbyte($byte){
    447             if(fgetc($this->handle)==chr($byte)){
    448                 fseek($this->handle,$this->pointer);
    449                 return true;
    450             }else{
    451                 fseek($this->handle,$this->pointer);
    452                 return false;
    453             }
    454         }   
    455         //-------------------------------------------------------
    456         private function checkEOF(){
    457             if(fgetc($this->handle)===false){
    458                 return true;
    459             }else{
    460                 fseek($this->handle,$this->pointer);
    461                 return false;
    462             }
    463         }   
    464         //-------------------------------------------------------
    465         /** 
    466         * Debug Functions.  keleyi.com
    467         * Parses the GIF animation into single frames.
    468         */
    469         private function debug($string){
    470             echo "<pre>";
    471             for($i=0;$i<strlen($string);$i++){
    472                 echo str_pad(dechex(ord($string[$i])),2,"0",STR_PAD_LEFT). " ";
    473             }
    474             echo "</pre>";
    475         }
    476         //-------------------------------------------------------
    477         private function debuglen($var,$len){
    478             echo "<pre>";
    479             for($i=0;$i<$len;$i++){
    480                 echo str_pad(dechex(ord($var[$i])),2,"0",STR_PAD_LEFT). " ";
    481             }
    482             echo "</pre>";
    483         }   
    484         //-------------------------------------------------------
    485         private function debugstream($length){
    486             $this->debug($this->datapart($this->pointer,$length));
    487         }
    488         //-------------------------------------------------------
    489         /** 
    490         * GD Resizer Device
    491         * Resizes the animation frames
    492         */
    493         private function resizeframes(){
    494             $k=0;
    495             foreach($this->parsedfiles as $img){
    496                 $src = imagecreatefromgif($img);
    497                 $sw = $this->imagedata[$k]["width"];
    498                 $sh = $this->imagedata[$k]["height"];
    499                 $nw = round($sw * $this->wr);
    500                 $nh = round($sh * $this->hr);
    501                 $sprite = imagecreatetruecolor($nw,$nh);    
    502                 $trans = imagecolortransparent($sprite);
    503                 imagealphablending($sprite, false);
    504                 imagesavealpha($sprite, true);
    505                 imagepalettecopy($sprite,$src);                 
    506                 imagefill($sprite,0,0,imagecolortransparent($src));
    507                 imagecolortransparent($sprite,imagecolortransparent($src));                     
    508                 imagecopyresized($sprite,$src,0,0,0,0,$nw,$nh,$sw,$sh);     
    509                 imagegif($sprite,$img);
    510                 imagedestroy($sprite);
    511                 imagedestroy($src);
    512                 $k++;
    513             }
    514         }
    515     }
    516  
    517  
    518 ?>
  • 相关阅读:
    达梦常用命令
    sqlserver命令
    db2常用命令
    docker常用命令
    linux常用命令
    vim常用命令
    cpu、内存、io、内存、负载
    3.系统状态监控
    10 innodb之关键特性刷新邻接页
    9 innodb关键特性之自适应哈希索引03
  • 原文地址:https://www.cnblogs.com/roucheng/p/3459271.html
Copyright © 2020-2023  润新知