PHPでQRをBASE64でやるために

2018年3月27日

PHP テクノロジー プログラミング

PHPでQRコードを表示するモジュールはかなり昔から存在するし、そんなに機能要望もいらない。 でも、今回組み込み関数として使いたいという事と、base64で扱いたいという2つの要望に対応するために、元々のライブラリーをラップするclassを作ったので、共有する。 ちなみに、最初に断っておくが、QRコードを出力するライブラリーを便利に使うコードを書いただけなので、新たなQRコードライブラリを作ったわけではない事を理解した人だけ、この先の記事を読み進めてよし。

PHPのQRコードライブラリ

PHPでQRコードを作成し表示するには、かなり昔から存在するMa href="http://www.swetake.com/qrcode/" target="_blank">「QRcode Perl/CGI & PHP scripts」がやはり便利です。 ライセンスを確認してみたところ、「添付ドキュメント」に記載がありました。
6、著作権・配布など これらのプログラムの著作権は作者であるY.Swetakeにあります。 これらのプログラムはフリーウエアです。もとの著作権表示を変更しなければ自由に再配布・改造してもかまいません。
著作権表示をいじらなければ改造可能とのことで、安心してソース公開まで行えます。

base64対応に改造

QRcode Perl CGI & PHP scripts ver. 0.50j ( 2013/5/18) まず最新ソースコードをダウンロードして、回答すると、以下のような階層構造になっています。 この中の"php/qr_img.php"ファイルをコピーして、改造していきます。

初期設定

public static $path = "./qr_img0.50j/data"; public static $image_path = "./qr_img0.50j/image"; このフォルダのdataフォルダとimageフォルダを絶対パスまたは相対パスで指定します。

Program wrap

function getBase64($d="",$e="",$s="",$v="",$t="",$n="",$m="",$p="",$o="") 引数をそのまま使えるように受け取り値として設定した関数を作成。 $qrcode_data_string = $d; $qrcode_error_correct = $e; $qrcode_module_size = $s; $qrcode_version = $v; $qrcode_image_type = $t; $qrcode_structureappend_n = $n; $qrcode_structureappend_m = $m; $qrcode_structureappend_parity = $p; $qrcode_structureappend_originaldata = $o; 関数の引数に合わて「@GET[**]」とクエリ受け取り値になっていた箇所を引数に置き換えます。 ※わかりやすいようにGET-keyと変数名を同じにしています。

Image表示->Base64Returnに置き換え

/* #--- output image # */ Header("Content-type: image/".$qrcode_image_type); ImageCopyResized($output_image,$base_image,0,0,0,0,$qrcode_image_size,$qrcode_image_size,$mib,$mib); if ($qrcode_image_type == "jpeg"){ ImageJpeg($output_image); } else { ImagePng($output_image); } Header("Content-type: image/".$qrcode_image_type); ImageCopyResized($output_image,$base_image,0,0,0,0,$qrcode_image_size,$qrcode_image_size,$mib,$mib); if ($qrcode_image_type == "jpeg"){ // ImageJpeg($output_image); $base64 = base64_encode($output_image); return "data:image/jpeg;base64,".$base64; } else { // ImagePng($output_image); // echo "ImagePng : ".ImagePng($output_image); // // print_r($output_image); // echo "OUT : ".$output_image; ob_start(); ImagePng($output_image); $data = ob_get_contents(); ob_end_clean(); $base64 = base64_encode($data); return "data:image/png;base64,".$base64; } PHPファイルの一番下にある、Image表示処理の箇所をコメントアウトして、以下のように書き換えました。 /* #--- output image # */ ImageCopyResized($output_image,$base_image,0,0,0,0,$qrcode_image_size,$qrcode_image_size,$mib,$mib); ob_start(); ImagePng($output_image); $data = ob_get_contents(); ob_end_clean(); $base64 = base64_encode($data); return $base64; image-headerで直接画像出力していたところを、base64の文字列を返すようにしています。 注意点は、PHPのGDライブラリを事前にセットしておかないといけません。 phpinfo()で以下のような表示がされていることを確認しておきましょう。

組み込み関数を作る

ファイル構造

ダウンロードしたフォルダの横にwrap用のフォルダを作ってその中にPHPを設置しています。

ソースコード

<?php class QR{ public static $path = "../qr_img0.50j/data"; public static $image_path = "../qr_img0.50j/image"; public static function getBase64ImgTag($d="",$e="",$s="",$v="",$t="",$n="",$m="",$p="",$o=""){ if($d === "" || !$d){return;} $base64 = self::getBase64($d,$e,$s,$v,$t,$n,$m,$p,$o); if(!$base64){ return ""; } else{ $qrcode_image_type = "png"; return "<img src='data:image/".$qrcode_image_type.";base64,".$base64."' data-url='".$d."'>"; } } public static function getBase64($d="",$e="",$s="",$v="",$t="",$n="",$m="",$p="",$o=""){ if($d === "" || !$d){return;} /* ------ setting area ------ */ $path = self::$path; /* You must set path to data files. */ $image_path = self::$image_path; /* You must set path to QRcode frame images. */ $version_ul=40; /* upper limit for version */ /* ------ setting area end ------ */ $qrcode_data_string = $d; $qrcode_error_correct = $e; $qrcode_module_size = $s; $qrcode_version = $v; $qrcode_image_type = $t; $qrcode_structureappend_n = $n; $qrcode_structureappend_m = $m; $qrcode_structureappend_parity = $p; $qrcode_structureappend_originaldata = $o; $qrcode_image_type = ($qrcode_image_type === "J" || $qrcode_image_type=="j")? "jpeg" : "png"; if (!$qrcode_module_size>0){ $qrcode_module_size = ($qrcode_image_type=="jpeg")? 8 : 4; } $qrcode_data_string = rawurldecode($qrcode_data_string); $data_length = strlen($qrcode_data_string); if ($data_length <= 0) { trigger_error("QRcode : Data do not exist.",E_USER_ERROR); // exit; return; } $data_counter = 0; if ($qrcode_structureappend_n > 1 && $qrcode_structureappend_n <= 16 && $qrcode_structureappend_m > 0 && $qrcode_structureqppend_m <= 16){ $data_value[0] = 3; $data_bits[0] = 4; $data_value[1] = $qrcode_structureappend_m-1; $data_bits[1] = 4; $data_value[2] = $qrcode_structureappend_n-1; $data_bits[2] = 4; $originaldata_length = strlen($qrcode_structureappend_originaldata); if ($originaldata_length > 1){ $qrcode_structureappend_parity=0; $i=0; while ($i<$originaldata_length){ $qrcode_structureappend_parity=($qrcode_structureappend_parity ^ ord(substr($qrcode_structureappend_originaldata,$i,1))); $i++; } } $data_value[3] = $qrcode_structureappend_parity; $data_bits[3] = 8; $data_counter = 4; } $data_bits[$data_counter] = 4; /* --- determine encode mode */ if (preg_match("/[^0-9]/" , $qrcode_data_string) != 0){ if (preg_match("/[^0-9A-Z \$\*\%\+\.\/\:\-]/" , $qrcode_data_string) != 0) { /* --- 8bit byte mode */ $codeword_num_plus=array( 0,0,0,0,0,0,0,0,0,0, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8); $data_value[$data_counter] = 4; $data_counter++; $data_value[$data_counter] = $data_length; $data_bits[$data_counter] = 8; /* #version 1-9 */ $codeword_num_counter_value = $data_counter; $data_counter++; $i = 0; while ($i<$data_length){ $data_value[$data_counter] = ord(substr($qrcode_data_string,$i,1)); $data_bits[$data_counter] = 8; $data_counter++; $i++; } } else { /* ---- alphanumeric mode */ $codeword_num_plus=array( 0,0,0,0,0,0,0,0,0,0, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 4,4,4,4,4,4,4,4,4,4,4,4,4,4); $data_value[$data_counter] = 2; $data_counter++; $data_value[$data_counter] = $data_length; $data_bits[$data_counter] = 9; /* #version 1-9 */ $codeword_num_counter_value = $data_counter; $alphanumeric_character_hash=array( "0"=>0,"1"=>1,"2"=>2,"3"=>3,"4"=>4, "5"=>5,"6"=>6,"7"=>7,"8"=>8,"9"=>9,"A"=>10,"B"=>11,"C"=>12,"D"=>13,"E"=>14, "F"=>15,"G"=>16,"H"=>17,"I"=>18,"J"=>19,"K"=>20,"L"=>21,"M"=>22,"N"=>23, "O"=>24,"P"=>25,"Q"=>26,"R"=>27,"S"=>28,"T"=>29,"U"=>30,"V"=>31, "W"=>32,"X"=>33,"Y"=>34,"Z"=>35," "=>36,"$"=>37,"%"=>38,"*"=>39, "+"=>40,"-"=>41,"."=>42,"/"=>43,":"=>44); $i=0; $data_counter++; while ($i<$data_length){ if (($i %2)==0){ $data_value[$data_counter] = $alphanumeric_character_hash[substr($qrcode_data_string,$i,1)]; $data_bits[$data_counter] = 6; } else { $data_value[$data_counter] = $data_value[$data_counter]*45+$alphanumeric_character_hash[substr($qrcode_data_string,$i,1)]; $data_bits[$data_counter] = 11; $data_counter++; } $i++; } } } else { /* ---- numeric mode */ $codeword_num_plus=array( 0,0,0,0,0,0,0,0,0,0, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 4,4,4,4,4,4,4,4,4,4,4,4,4,4); $data_value[$data_counter] = 1; $data_counter++; $data_value[$data_counter] = $data_length; $data_bits[$data_counter] = 10; /* #version 1-9 */ $codeword_num_counter_value = $data_counter; $i=0; $data_counter++; while ($i<$data_length){ if (($i % 3)==0){ $data_value[$data_counter] = substr($qrcode_data_string,$i,1); $data_bits[$data_counter] = 4; } else { $data_value[$data_counter] = $data_value[$data_counter]*10+substr($qrcode_data_string,$i,1); if (($i % 3)==1){ $data_bits[$data_counter] = 7; } else { $data_bits[$data_counter] = 10; $data_counter++; } } $i++; } } if (@$data_bits[$data_counter] > 0) { $data_counter++; } $i=0; $total_data_bits=0; while($i<$data_counter){ $total_data_bits+=$data_bits[$i]; $i++; } $ecc_character_hash=array( "L"=>"1", "l"=>"1", "M"=>"0", "m"=>"0", "Q"=>"3", "q"=>"3", "H"=>"2", "h"=>"2"); $ec = @$ecc_character_hash[$qrcode_error_correct]; if (!$ec){$ec=0;} $max_data_bits_array=array( 0,128,224,352,512,688,864,992,1232,1456,1728, 2032,2320,2672,2920,3320,3624,4056,4504,5016,5352, 5712,6256,6880,7312,8000,8496,9024,9544,10136,10984, 11640,12328,13048,13800,14496,15312,15936,16816,17728,18672, 152,272,440,640,864,1088,1248,1552,1856,2192, 2592,2960,3424,3688,4184,4712,5176,5768,6360,6888, 7456,8048,8752,9392,10208,10960,11744,12248,13048,13880, 14744,15640,16568,17528,18448,19472,20528,21616,22496,23648, 72,128,208,288,368,480,528,688,800,976, 1120,1264,1440,1576,1784,2024,2264,2504,2728,3080, 3248,3536,3712,4112,4304,4768,5024,5288,5608,5960, 6344,6760,7208,7688,7888,8432,8768,9136,9776,10208, 104,176,272,384,496,608,704,880,1056,1232, 1440,1648,1952,2088,2360,2600,2936,3176,3560,3880, 4096,4544,4912,5312,5744,6032,6464,6968,7288,7880, 8264,8920,9368,9848,10288,10832,11408,12016,12656,13328 ); if (!is_numeric($qrcode_version)){ $qrcode_version=0; } if (!$qrcode_version){ /* #--- auto version select */ $i=1+40*$ec; $j=$i+39; $qrcode_version=1; while ($i<=$j){ if (($max_data_bits_array[$i])>=$total_data_bits+$codeword_num_plus[$qrcode_version] ){ $max_data_bits=$max_data_bits_array[$i]; break; } $i++; $qrcode_version++; } } else { $max_data_bits=$max_data_bits_array[$qrcode_version+40*$ec]; } if ($qrcode_version>$version_ul){ trigger_error("QRcode : too large version.",E_USER_ERROR); } $total_data_bits+=$codeword_num_plus[$qrcode_version]; $data_bits[$codeword_num_counter_value]+=$codeword_num_plus[$qrcode_version]; $max_codewords_array=array( 0,26,44,70,100,134,172,196,242, 292,346,404,466,532,581,655,733,815,901,991,1085,1156, 1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465, 2611,2761,2876,3034,3196,3362,3532,3706); $max_codewords = $max_codewords_array[$qrcode_version]; $max_modules_1side = 17+($qrcode_version <<2); $matrix_remain_bit=array( 0,0,7,7,7,7,7,0,0,0,0,0,0,0,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,3,3,3,3,3,3,3,0,0,0,0,0,0); /* ---- read version ECC data file */ $byte_num=$matrix_remain_bit[$qrcode_version]+($max_codewords << 3); $filename=$path."/qrv".$qrcode_version."_".$ec.".dat"; $fp1 = fopen ($filename, "rb"); $matx = fread($fp1,$byte_num); $maty = fread($fp1,$byte_num); $masks = fread($fp1,$byte_num); $fi_x = fread($fp1,15); $fi_y = fread($fp1,15); $rs_ecc_codewords=ord(fread($fp1,1)); $rso = fread($fp1,128); fclose($fp1); $matrix_x_array = unpack("C*",$matx); $matrix_y_array = unpack("C*",$maty); $mask_array = unpack("C*",$masks); $rs_block_order = unpack("C*",$rso); $format_information_x2 = unpack("C*",$fi_x); $format_information_y2 = unpack("C*",$fi_y); $format_information_x1 = array(0,1,2,3,4,5,7,8,8,8,8,8,8,8,8); $format_information_y1 = array(8,8,8,8,8,8,8,8,7,5,4,3,2,1,0); $max_data_codewords = ($max_data_bits >>3); $filename = $path."/rsc".$rs_ecc_codewords.".dat"; $fp0 = fopen ($filename, "rb"); $i=0; while ($i<256) { $rs_cal_table_array[$i]=fread ($fp0,$rs_ecc_codewords); $i++; } fclose ($fp0); /* --- set terminator */ if ($total_data_bits<=$max_data_bits-4){ $data_value[$data_counter]=0; $data_bits[$data_counter]=4; } else { if ($total_data_bits<$max_data_bits){ $data_value[$data_counter]=0; $data_bits[$data_counter]=$max_data_bits-$total_data_bits; } else { if ($total_data_bits>$max_data_bits){ trigger_error("QRcode : Overflow error",E_USER_ERROR); // exit; return; } } } /* ----divide data by 8bit */ $i = 0; $codewords_counter = 0; $codewords[0] = 0; $remaining_bits = 8; while ($i<=$data_counter) { $buffer=@$data_value[$i]; $buffer_bits=@$data_bits[$i]; $flag=1; while ($flag) { if ($remaining_bits>$buffer_bits){ $codewords[$codewords_counter]=((@$codewords[$codewords_counter]<<$buffer_bits) | $buffer); $remaining_bits-=$buffer_bits; $flag=0; } else { $buffer_bits-=$remaining_bits; $codewords[$codewords_counter]=(($codewords[$codewords_counter] << $remaining_bits) | ($buffer >> $buffer_bits)); if ($buffer_bits==0) { $flag=0; } else { $buffer= ($buffer & ((1 << $buffer_bits)-1) ); $flag=1; } $codewords_counter++; if ($codewords_counter<$max_data_codewords-1){ $codewords[$codewords_counter]=0; } $remaining_bits=8; } } $i++; } if ($remaining_bits!=8) { $codewords[$codewords_counter]=$codewords[$codewords_counter] << $remaining_bits; } else { $codewords_counter--; } /* ---- set padding character */ if ($codewords_counter<$max_data_codewords-1){ $flag=1; while ($codewords_counter<$max_data_codewords-1){ $codewords_counter++; if ($flag==1) { $codewords[$codewords_counter]=236; } else { $codewords[$codewords_counter]=17; } $flag=$flag*(-1); } } /* ---- RS-ECC prepare */ $i=0; $j=0; $rs_block_number=0; $rs_temp[0]=""; while($i<$max_data_codewords){ $rs_temp[$rs_block_number].=chr($codewords[$i]); $j++; if ($j>=$rs_block_order[$rs_block_number+1]-$rs_ecc_codewords){ $j=0; $rs_block_number++; $rs_temp[$rs_block_number]=""; } $i++; } /* # # RS-ECC main # */ $rs_block_number = 0; $rs_block_order_num = count($rs_block_order); while ($rs_block_number<$rs_block_order_num){ $rs_codewords=$rs_block_order[$rs_block_number+1]; $rs_data_codewords=$rs_codewords-$rs_ecc_codewords; $rstemp=$rs_temp[$rs_block_number].str_repeat(chr(0),$rs_ecc_codewords); $padding_data=str_repeat(chr(0),$rs_data_codewords); $j=$rs_data_codewords; while($j>0){ $first=ord(substr($rstemp,0,1)); if ($first){ $left_chr=substr($rstemp,1); $cal=$rs_cal_table_array[$first].$padding_data; $rstemp=$left_chr ^ $cal; } else { $rstemp=substr($rstemp,1); } $j--; } $codewords=array_merge($codewords,unpack("C*",$rstemp)); $rs_block_number++; } /* ---- flash matrix */ $i=0; while ($i<$max_modules_1side){ $j=0; while ($j<$max_modules_1side){ $matrix_content[$j][$i]=0; $j++; } $i++; } /* --- attach data */ $i=0; while ($i<$max_codewords){ $codeword_i=$codewords[$i]; $j=8; while ($j>=1){ $codeword_bits_number=($i << 3) + $j; $matrix_content[ $matrix_x_array[$codeword_bits_number] ][ $matrix_y_array[$codeword_bits_number] ]=((255*($codeword_i & 1)) ^ $mask_array[$codeword_bits_number] ); $codeword_i= $codeword_i >> 1; $j--; } $i++; } $matrix_remain=$matrix_remain_bit[$qrcode_version]; while ($matrix_remain){ $remain_bit_temp = $matrix_remain + ( $max_codewords <<3); $matrix_content[ $matrix_x_array[$remain_bit_temp] ][ $matrix_y_array[$remain_bit_temp] ] = ( 0 ^ $mask_array[$remain_bit_temp] ); $matrix_remain--; } #--- mask select $min_demerit_score=0; $hor_master = ""; $ver_master = ""; $k = 0; while($k < $max_modules_1side){ $l = 0; while($l<$max_modules_1side){ $hor_master = $hor_master.chr($matrix_content[$l][$k]); $ver_master = $ver_master.chr($matrix_content[$k][$l]); $l++; } $k++; } $i=0; $all_matrix = $max_modules_1side * $max_modules_1side; while ($i<8){ $demerit_n1=0; $ptn_temp=array(); $bit= 1<< $i; $bit_r=(~$bit)&255; $bit_mask=str_repeat(chr($bit),$all_matrix); $hor = $hor_master & $bit_mask; $ver = $ver_master & $bit_mask; $ver_shift1=$ver.str_repeat(chr(170),$max_modules_1side); $ver_shift2=str_repeat(chr(170),$max_modules_1side).$ver; $ver_shift1_0=$ver.str_repeat(chr(0),$max_modules_1side); $ver_shift2_0=str_repeat(chr(0),$max_modules_1side).$ver; $ver_or=chunk_split(~($ver_shift1 | $ver_shift2),$max_modules_1side,chr(170)); $ver_and=chunk_split(~($ver_shift1_0 & $ver_shift2_0),$max_modules_1side,chr(170)); $hor=chunk_split(~$hor,$max_modules_1side,chr(170)); $ver=chunk_split(~$ver,$max_modules_1side,chr(170)); $hor=$hor.chr(170).$ver; $n1_search="/".str_repeat(chr(255),5)."+|".str_repeat(chr($bit_r),5)."+/"; $n3_search=chr($bit_r).chr(255).chr($bit_r).chr($bit_r).chr($bit_r).chr(255).chr($bit_r); $demerit_n3=substr_count($hor,$n3_search)*40; $demerit_n4=floor(abs(( (100* (substr_count($ver,chr($bit_r))/($byte_num)) )-50)/5))*10; $n2_search1="/".chr($bit_r).chr($bit_r)."+/"; $n2_search2="/".chr(255).chr(255)."+/"; $demerit_n2=0; preg_match_all($n2_search1,$ver_and,$ptn_temp); foreach($ptn_temp[0] as $str_temp){ $demerit_n2+=(strlen($str_temp)-1); } $ptn_temp=array(); preg_match_all($n2_search2,$ver_or,$ptn_temp); foreach($ptn_temp[0] as $str_temp){ $demerit_n2+=(strlen($str_temp)-1); } $demerit_n2*=3; $ptn_temp=array(); preg_match_all($n1_search,$hor,$ptn_temp); foreach($ptn_temp[0] as $str_temp){ $demerit_n1+=(strlen($str_temp)-2); } $demerit_score=$demerit_n1+$demerit_n2+$demerit_n3+$demerit_n4; if ($demerit_score<=$min_demerit_score || $i==0){ $mask_number=$i; $min_demerit_score=$demerit_score; } $i++; } $mask_content=1 << $mask_number; # --- format information $format_information_value=(($ec << 3) | $mask_number); $format_information_array=array( "101010000010010","101000100100101", "101111001111100","101101101001011","100010111111001","100000011001110", "100111110010111","100101010100000","111011111000100","111001011110011", "111110110101010","111100010011101","110011000101111","110001100011000", "110110001000001","110100101110110","001011010001001","001001110111110", "001110011100111","001100111010000","000011101100010","000001001010101", "000110100001100","000100000111011","011010101011111","011000001101000", "011111100110001","011101000000110","010010010110100","010000110000011", "010111011011010","010101111101101"); $i=0; while ($i<15){ $content=substr($format_information_array[$format_information_value],$i,1); $matrix_content[$format_information_x1[$i]][$format_information_y1[$i]]=$content * 255; $matrix_content[$format_information_x2[$i+1]][$format_information_y2[$i+1]]=$content * 255; $i++; } $mib=$max_modules_1side+8; $qrcode_image_size=$mib*$qrcode_module_size; if ($qrcode_image_size>1480){ trigger_error("QRcode : Too large image size",E_USER_ERROR); } $output_image =ImageCreate($qrcode_image_size,$qrcode_image_size); $image_path=$image_path."/qrv".$qrcode_version.".png"; $base_image=ImageCreateFromPNG($image_path); $col[1]=ImageColorAllocate($base_image,0,0,0); $col[0]=ImageColorAllocate($base_image,255,255,255); $i=4; $mxe=4+$max_modules_1side; $ii=0; while ($i<$mxe){ $j=4; $jj=0; while ($j<$mxe){ if ($matrix_content[$ii][$jj] & $mask_content){ ImageSetPixel($base_image,$i,$j,$col[1]); } $j++; $jj++; } $i++; $ii++; } /* #--- output image # */ ImageCopyResized($output_image,$base_image,0,0,0,0,$qrcode_image_size,$qrcode_image_size,$mib,$mib); ob_start(); ImagePng($output_image); $data = ob_get_contents(); ob_end_clean(); $base64 = base64_encode($data); return $base64; } }

使い方

QR::getBase64ImgTag("http://example.com"); > <img src='data:image/".$qrcode_image_type.";base64,****' data-url='http://example.com'> こんな感じでURLを渡すだけで、QRコードをbase64を含んだIMGタグを返すようにしています。 このプログラムは、外部からクエリで受け取ってそのまま表示する仕様のため、ホームページに脆弱性などの表示がされていますが、関数化して、サイト内部だけが呼び出しできるようにすることで、かなりの不正アクセスを防げるでしょうね。

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。