@玛酷猫9 年前
先说下需求:前段时间神秘花园很火,于是乎客户想要制作神秘花园的网页游戏,涂色盘是圆形的,网页将涂色盘加载进当前的canvas中,当网友涂色完毕提交后,将canvas内容转化为图片数据传送至后台保存。这时接收到的数据包括整个canvas内容,整体是个矩形,除了网友涂色部分,还有整个背景、颜色盘、一些按钮之类的,这就需要把网友涂色的那个圆形部分裁剪下来。
我的思路分成两部分,首先裁剪成正方形,毕竟直接在矩形上面裁出一个圆相对来说麻烦一些。项目使用的thinkphp框架(v3.1),裁正方形还是比较快捷的,直接调用自带的函数即可
import('ORG.Util.Image.ThinkImage'); $image = new ThinkImage(THINKIMAGE_GD, $file); $image->crop(516,516,45,119)->save($file);//crop四个参数分别为长、宽、x偏移、y偏移
下一步就是要裁圆了,网上搜索了下居然没有搜索到多少有用的代码,感觉这个对大家都不是什么难处,看来自己这个半路出家的基础还是不太好呀。幸好在一个很老的帖子(08年的帖子)的回帖里面看到Sunyanzi@phpchina的一个思路,原帖地址传送门,代码如下:
<?php //----------------------------------------------------------- // * Sunyanzi @ phpchina //----------------------------------------------------------- class image_cutter { private $original_image; private $cutted_image; private $diameter; private $radius; public function __construct( $image_path ) { /* load the original image ... */ $image = imagecreatefromjpeg( $image_path ); /* get image size ... */ $x = imagesx( $image ); $y = imagesy( $image ); /* diameter of the circle is always the smaller side ... */ $this->diameter = $x > $y ? $y : $x; /* radius is half a diameter ... am i must explain this ...? */ $this->radius = $this->diameter / 2; /* save the original image ... */ $this->original_image = $image; /* create new canvas to save our work ... */ $this->create_blank_image(); /* PAINTING TIME ... */ $this->read_the_original_image_and_write(); /* i'm positively bursting to see what we have done ... */ return; } private function __destruct() { /* hey my dear brower ... it's not a html page comes ... */ header("Content-type: image/png"); /* show our work ... */ imagepng( $this->cutted_image ); /* we have to cleaned up the mass before left ... */ imagedestroy( $this->original_image ); imagedestroy( $this->cutted_image ); /* so ... how do you think about this ...? */ return; } private function create_blank_image() { /* create a true color square whose side length equal to diameter of the circle ... */ $image = imagecreatetruecolor( $this->diameter,$this->diameter ); /* we also need a transparent background ... */ imagesavealpha($image, true); /* create a transparent color ... */ $color = imagecolorallocatealpha($image, 0, 0, 0, 127); /* ... then fill the image with it ... */ imagefill($image, 0, 0, $color); /* nothing to do then ... just save the new image ... */ $this->cutted_image = $image; /* go back and see what should we do next ..? */ return; } private function read_the_original_image_and_write() { /* actually we need a smooth circle ... */ for ( $x = 0; $x <= $this->radius; $x += 0.01 ) { /* standard form for the equation of a circle ... don't tell me you never knew that ... */ $y = sqrt( $this->diameter * $x - pow( $x , 2 ) ) + $this->radius; /* i think i should call this successive scans ... */ for ( $i = $x; $i < $this->diameter - $x; $i++ ) { /* half of the circle ... */ imagesetpixel ( $this->cutted_image , $i, $y, imagecolorat( $this->original_image, $i, $y ) ); /* the other half of course ... */ imagesetpixel ( $this->cutted_image , $i, $this->diameter - $y, imagecolorat( $this->original_image, $i, $this->diameter - $y ) ); } } /* avoid the white line when the diameter is an even number ... */ if ( ! is_float( $this->radius ) ) for ( $i = 0; $i < $this->diameter; $i++ ) imagesetpixel ( $this->cutted_image , $i, $this->radius - 1, imagecolorat( $this->original_image, $i, $this->radius - 1 ) ); /* woo ... not as difficult as you think ... that's all ... */ return; } } new image_cutter( HERE_COMES_THE_ORIGINAL_IMAGE_PATH ); ?>
读了下代码,大概了解了下原理,以x轴为参考,0.01像素步长,通过圆的公式计算出对应y点的位置,然后将循环将X轴这条线上落在y点外(圆外)的点用透明色填充。实际放到项目中测试,发现个问题,由于项目中圆的半径比较大,516像素,按照0.01步长来计算,在x刚起步时,计算后的y值之间的间隔会大于1个像素,表现在页面上就是在圆的中间出现两条白线,将步长减少到0.004的时候白线消失,但是由于步长变小,整个循环数目变大,耗时严重,一次将近30秒的时间。
既然Sunyanzi提供了一个思路,那我就把它简化下,毕竟图片最小点是像素,那我就以1像素为步长,y轴也不计算了,直接也以1像素为步长,通过圆的方程式算出半径,比较半径的大小,大于我需要的圆的半径的点,把他用透明色填充掉就OK了。思路有了就开始动手。仅修改上面代码中裁圆的那个函数read_the_original_image_and_write()
private function read_the_original_image_and_write(){ for ( $x = 0; $x <= $this->radius; $x++ ) { for ( $y = 0; $y <= $this->radius; $y++ ) { if(sqrt( pow( $x- $this->radius , 2 ) + pow( $y- $this->radius , 2 ))<$this->radius){ imagesetpixel ( $this->cutted_image , $x, $y, imagecolorat( $this->original_image, $x, $y ) ); imagesetpixel ( $this->cutted_image , $this->diameter - $x, $y, imagecolorat( $this->original_image, $this->diameter - $x, $y ) ); imagesetpixel ( $this->cutted_image , $x, $this->diameter - $y, imagecolorat( $this->original_image, $x, $this->diameter - $y ) ); imagesetpixel ( $this->cutted_image ,$this->diameter - $x, $this->diameter - $y, imagecolorat( $this->original_image, $this->diameter - $x, $this->diameter - $y ) ); } } } }
实际效果速度那是没说的,毕竟简化了,基本上秒出,效果相对之前的方法来说差了点,一周的锯齿感强了点,不过由于图片比较大,反倒不是很明显,有需要的话可以将步长调到0.5,效果会好一点,速度不是太影响,图片小的话可以放得更低一点。上面例子是直接输出图片,实际应用为保存成文件,这个就比较简单了,就不在这里贴代码了。