保留输出到前端、写入文件,去掉了写入文件同时输出到前端,增加贴Logo并且可以设置横向比例,增加返回GdImage对象可直接用于海报生成等场景,完善了jpg格式的输出。
调用示例 :
$logoUri='./path/to/logo.png'; //支持本地和网络资源,如需支持其他图片格式需要在相应位置作调整
$qr=new QRcode('这里是二维码内容');
$qr->png(NULL,$logoUri,20); //第一个参数是要写入的文件名(不含后缀),第三个参数是logo宽占二维码宽的比例
exit;
注意事项:
- 如果可以,请使用PHP8,否则请删掉png和jpg方法的返回值类型约束
- 建议PHP7以上,否则你将不能在png和jpg方法中使用??运算,需要改成三目运算
- 实例化对象时只有一个必要参数$text-二维码内容
- 获得结果的方法有三个:image、png、jpg
- image方法返回GdImage对象,可以直接用于GD库生成海报等场景
- png和jpg方法通过类的path属性和方法的filename参数区分输出到前端还是写入文件
- 只有当path为空字符串且filename为NULL时才会输出到前端
- filename不要传入空字符串,除非你想用空字符串取代NULL判断是否需要写入文件,这样的话你将不能使用??运算
- 否则都会写入文件并返回文件名
- 如果filename为NULL,将用微秒时间戳代替之;filename不要后缀(传了也没用)
- 然后会把path属性和filename拼接起来并补上后缀,将数据写入文件
- 需要注意的是,此时的返回值是完整路径而非文件名
使用方法:找到phpqrcode.php源码,删除QRcode、QRencode和QRtools三个类,替换为下面的带码段。
当然如果你愿意支持一下博主,也可以直接去用C币下载成品:面向对象风格的phpqrcode
class QRcode {
private bool $casesensitive = true;
private bool $eightbit = false;
private int $hint = QR_MODE_8;
private int $size; //设置单点像素数
private int $margin; //设置边框点数
private int $level; //设置纠错等级,1/2/3/4
private string $path=''; //设置写入文件夹,空字符串时不写入
private string $text; //设置内容
private $version = 0;
public int $points; //结果横边点数
public int $w; //结果像素宽
public int $h; //结果像素高
public $data;
public function __construct(string $text,int $level=1,int $size=5,int $margin=4,string $path='')
{
$this->setLevel($level);
$this->setText($text); //setText应当在setLevel之后调用,否则$level属性不会生效
$this->setSize($size);
$this->setMargin($margin);
$this->setPath($path);
}
public function image(string $logo=NULL,int $ratio=5) : \GdImage
{
$h = count($this->data);
$w = strlen($this->data[0]);
$imgW = $w + 2*$this->margin;
$imgH = $h + 2*$this->margin;
$base =ImageCreate($imgW, $imgH);
$col[0] = ImageColorAllocate($base,255,255,255);
$col[1] = ImageColorAllocate($base,0,0,0);
imagefill($base, 0, 0, $col[0]);
for($y=0; $y<$h; $y++) {
for($x=0; $x<$w; $x++) {
if($this->data[$y][$x]=='1') ImageSetPixel($base,$x+$this->margin,$y+$this->margin,$col[1]);
}
}
$pixelPerPoint=min($this->size,intval(QR_PNG_MAXIMUM_SIZE/(count($this->data)+2*$this->margin)));
$this->w=$imgW*$pixelPerPoint;
$this->h=$imgH*$pixelPerPoint;
$target=ImageCreate($this->w,$this->h);
ImageCopyResized($target,$base,0,0,0,0,$this->w,$this->h,$imgW,$imgH);
ImageDestroy($base);
if(isset($logo)&&$ratio>1&&file_exists($logo)){
// 因为可以用透明色使边角圆润,所以我的logo都是PNG格式的,因此这里就懒得判断了,如果想支持其他格式可以在这里判断文件类型
$lg=imagecreatefrompng($logo);
// 一般情况下二维码和logo都是正方形的,但为保证兼容和美观,这里只有宽度是按输入比例调整的,高度则是根据logo原比例自适应
$lgSize=getimagesize($logo);
$lgW=(int)round($this->w*$ratio/100);
$lgH=$lgSize[1]*$lgW/$lgSize[0];
imagecopyresampled($target,$lg,intval(($this->w-$lgW)/2),intval(($this->h-$lgH)/2),0,0,$lgW,$lgH,$lgSize[0],$lgSize[1]);
}
return $target;
}
/*下面两个方法用到了php8的联合类型约束,不重要,低版本删掉约束即可*/
public function png(string $filename = NULL,string $logo=NULL,int $ratio=20) : NULL|string
{
$resource=$this->image($logo,$ratio);
if($this->path===''&&!isset($filename)) Header("Content-type: image/png");
else $filename=$this->path.($filename??microtime(TRUE)).'.png';
ImagePng($resource,$filename);
ImageDestroy($resource);
$resource=NULL;
return $filename;
}
public function jpg(int $q=85,string $filename = NULL,string $logo=NULL,int $ratio=20) : NULL|string
{
$resource=$this->image($logo,$ratio);
if($this->path===''&&!isset($filename)) Header("Content-type: image/jpeg");
else $filename=$this->path.($filename??microtime(TRUE)).'.jpeg';
ImageJpeg($resource, $filename, $q);
ImageDestroy($resource);
$resource=NULL;
return $filename;
}
public function setText(string $text) : self
{
$input = new QRinput($this->version, $this->level);
if($input == NULL) return $this;
if($this->eightbit) {
if($text == NULL) throw new \Exception('empty string!');
$ret = $input->append($input,QR_MODE_8,strlen($text),str_split($text));
} else {
if($this->hint!==QR_MODE_8&&$this->hint!=QR_MODE_KANJI) throw new \Exception('bad hint');
$ret = QRsplit::splitStringToQRinput($text,$input,$this->hint,$this->casesensitive);
}
if($ret<0){
unset($input);
return $this;
}
$mask=-1;
if($input->getVersion()<0||$input->getVersion()>QRSPEC_VERSION_MAX) throw new \Exception('wrong version');
if($input->getErrorCorrectionLevel() > QR_ECLEVEL_H) throw new \Exception('wrong level');
$raw = new QRrawcode($input);
$version = $raw->version;
$width = QRspec::getWidth($version);
$frame = QRspec::newFrame($version);
$filler = new FrameFiller($width, $frame);
if(is_null($filler)) {
return $this;
}
// inteleaved data and ecc codes
for($i=0; $i<$raw->dataLength + $raw->eccLength; $i++) {
$code = $raw->getCode();
$bit = 0x80;
for($j=0; $j<8; $j++) {
$addr = $filler->next();
$filler->setFrameAt($addr, 0x02 | (($bit & $code) != 0));
$bit = $bit >> 1;
}
}
unset($raw);
// remainder bits
$j = QRspec::getRemainder($version);
for($i=0; $i<$j; $i++) {
$addr = $filler->next();
$filler->setFrameAt($addr, 0x02);
}
$frame = $filler->frame;
unset($filler);
// masking
$maskObj = new QRmask();
if($mask < 0) {
if (QR_FIND_BEST_MASK) {
$masked = $maskObj->mask($width, $frame, $input->getErrorCorrectionLevel());
} else {
$masked = $maskObj->makeMask($width, $frame, (intval(QR_DEFAULT_MASK) % 8), $input->getErrorCorrectionLevel());
}
} else {
$masked = $maskObj->makeMask($width, $frame, $mask, $input->getErrorCorrectionLevel());
}
if($masked == NULL) return $this;
// 原本有个raw方法,返回的是此时的$masked值
$this->version = $version;
$this->points = $width;
$len = count($masked);
foreach ($masked as &$frameLine) {
for($i=0; $i<$len; $i++) {
$frameLine[$i] = (ord($frameLine[$i])&1)?'1':'0';
}
}
// 原本有个text方法,传入$text和$outfile参数
// 如果$outfile值有效,返回值是这里的$masked值
// 否则则写入文件file_put_contents($outfile,join("\n",$masked));
// 现在$text已经拆到setText方法中了
$this->data = $masked;
return $this;
}
public function setPath(string $path) : self
{
if($this->path===$path) return $this;
if(!file_exists($path)) mkdir($path,0777,TRUE);
$this->path=$path;
return $this;
}
public function setLevel(int $level) : self
{
if($level<QR_ECLEVEL_L) $this->level=QR_ECLEVEL_L;
elseif($level>QR_ECLEVEL_H) $this->level=QR_ECLEVEL_H;
else $this->level=$level;
return $this;
}
public function setSize(int $size) : self
{
$this->size=$size>1?$size:1;
return $this;
}
public function setMargin(int $margin) : self
{
$this->margin=$margin>0?($margin<10?$margin:10):0;
return $this;
}
}