说明:是借鉴别人的文章思路,整合在yii上的单个图片裁剪功能。注意插件版本可能有不同。
借鉴以下两篇文章:
2,PHP Uploadify+jQuery.imgAreaSelect插件+AJAX 实现图片上传裁剪 仿微博头像上传功能
要用的插件:
jQuery:jquery-1.10.2.min.js(版本可自己选);
uploadify:文件上传功能;
jquery.imgareaselect:对图片进行自定义区域选择;
客户端与服务器交互图
yii部分: (用到bootstrap,JS模块)
yii部署:
CSS:
JS:
- require.min.js (JS模块化开发要用的插件)
- main.js (入口JS 载入 jquery-1.10.2.min.js jquery.imgareaselect.minjs jquery.uploadify.min.js json2.js)
extensions(扩展): 处理上传图像和图像裁剪
代码:
- 视图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate">
<meta http-equiv="expires" content="0">
<title>图片上传裁剪</title>
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->homeUrl;?>css/whg/bootstrap.min.css" media="all">
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->homeUrl;?>js/whg/jquery.imgareaselect-0.9.10/css/imgareaselect-default.css" media="all" >
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->homeUrl;?>js/whg/uploadify/uploadify.css" media="all" >
<script data-main="<?php echo Yii::app()->homeUrl;?>js/whg/main" type="text/javascript" src="<?php echo Yii::app()->homeUrl;?>js/whg/require.min.js"></script>
<style type="text/css">
#image-uploaded,
#image-cuted{
position:relative;
max-width:100%;
}
#cut-preview-wrap{
position:relative;
display:block;
padding:0;
margin:0;
border:0;
width:100%;
overflow:hidden;
}
#cut-preview{
position:absolute;
padding:0;
margin:0;
border:0;
top:0;
left:0;
}
</style>
</head>
<body>
<div class="container">
<div class="page-header">
<h1>
图片上传裁剪
</h1>
</div>
<div class="row" id="cutone">
<div class="col-xs-3">
<label for="">图片上传</label>
<div id="upload-wrap" style="margin-top:40px;">
<input type="file" id="file" name="file" />
<span class="help-block">选择图片(1M以内);</span>
<span class="help-block">图片格式必须为:png, jpeg, jpg, gif;</span>
<span class="help-block">图片不允许涉及政治敏感与色情;</span>
<p>
<a id="upload" class="btn btn-sm btn-success" style="display:none;" href="#">上传</a>
</p>
<br>
<div class="col-xs-12" id="ratio-wrap" style="margin-top:30px;display:none;">
<div id="ratio-input" class="input-group">
<span class="input-group-addon">裁剪宽高比例</span>
<input type="text" id="ratio" class="form-control" placeholder="Ratio" value="1.1">
</div>
<span id="cut-help" class="help-block">输入宽高比进行裁剪初始化。例如1.3</span>
<p>
<a id="cutInit" class="btn btn-success" href="#">下一步</a>
<a id="cut" style="display:none;" class="btn btn-success" href="#">确定裁剪</a>
</p>
</div>
</div>
</div>
<div class="col-xs-4">
<label for="">裁剪区域</label>
<div class="row">
<div class="col-xs-12" id="uploaded-wrap" style="display:none;">
</div>
</div>
</div>
<div class="col-xs-4" id="preview-wrap" style="display:none;">
<label for="">裁剪预览</label>
<div id="cut-preview-wrap">
<img id="cut-preview" src="" alt="">
</div>
<p>
<small id="log"></small>
</p>
</div>
</div>
<div class="row" id="cuted-wrap" style="display:none;">
<div class="col-xs-offset-2 col-xs-8 text-center">
<div class="page-header">
<h4>成功预览</h4>
</div>
<p>
<img id="image-cuted" src="" alt="">
</p>
<form method="post" action="<?php echo $this->createUrl('indexcut');?>">
<p>
<!--<a id="download" style="display:none;" class="btn btn-block btn-danger" href="#">下载成品</a>-->
<input type="hidden" id="hide-cuted" name="hide-cuted" value="" />
<input class="btn btn-success btn-lg btn-block" type="submit" value="保存图片">
</p>
</form>
</div>
</div>
</div>
</body>
</html>
主入口main.js
requirejs.config({
baseUrl: '/js/whg/',
paths: {
jquery:'jquery-1.10.2.min',
imgareaselect:'jquery.imgareaselect-0.9.10/jquery.imgareaselect.min',
uploadify:'uploadify/jquery.uploadify.min'
},
shim:{
'imgareaselect':['jquery'],
'uploadify':['jquery']
}
});
var requireApp = ["jquery","imgareaselect","uploadify"];
//JSON检测
if( typeof JSON === 'undefined' ){
requireApp.push('json2');
}
//调用依赖
require(requireApp, function($) {
var $ = $.noConflict();
var $field = $("input[type='file']");
//Uploadify上传插件初始化
//$("#init").click(function(e){
// e.preventDefault();
//$(this).remove();
// $("#upload-wrap").show();
$(function(e){
//e.preventDefault();
$field.uploadify({
'buttonText': '选择图片'
,'swf': '/js/whg/uploadify/uploadify.swf?v=' + ( parseInt(Math.random()*1000) )
,'uploader' : "/admin/emcee/emcee/shoot"
,'auto' : false
,'multi' : false
,'method' : 'post'
,'fileObjName' : 'upload'
,'queueSizeLimit' : 1
,'fileSizeLimit' : '2000KB'
,'fileTypeExts': '*.gif; *.jpg; *.png; *.jpeg'
,'fileTypeDesc': '只允许.gif .jpg .png .jpeg 图片!'
,'onSelect': function(file) {//选择文件后的触发事件
//$("#upload").show();
//修改为选择图片后自动上传
$field.uploadify('upload','*')
}
,'onUploadSuccess' : function(file, data, response){ //上传成功后的触发事件
$field.uploadify('disable', true);
//$("#upload").remove();
var rst =JSON.parse(data);
//document.write(rst.data.path);
if( rst.status == 0 ){
alert('上传失败:'+rst.info);
}else{
var imageData = rst.data;
var $image = $("<img src='"+imageData.path+"' id='image-uploaded' data-width='"+imageData.width+"' data-height='"+imageData.height+"' data-name='"+imageData.name+"' />");
$("#uploaded-wrap").append( $image ).show();
$("#ratio-wrap").show();
//$image.bind('click',function(e){
// e.preventDefault();
// alert('请先设置裁剪宽高比例!');
// });
}
}
,'onUploadError' : function(file, errorCode, errorMsg, errorString){
alert('进入失败');
alert(errorString);
}
});
});
//点击上传
//$("#upload").click(function(e){
// e.preventDefault();
// $field.uploadify('upload','*');
// });
//注释以上代码,不用点击接钮上传,让其选择图片后自动上传
//点击裁剪初始化时
$("#cutInit").click(function(e){
e.preventDefault();
//确定裁剪宽高比
var ratio = parseFloat($("#ratio").val());
if( isNaN(ratio) ){
alert("请输入正确的宽高比,必须为数字,例如0.6或1.3");
return ;
}
//相关元素
var $uploaded = $("#image-uploaded"),
$previewWrap = $("#cut-preview-wrap"),
$preview = $("#cut-preview");
//图片宽高参数
var realWidth = $uploaded.data('width'),
realHeight = $uploaded.data('height'),
uploadedWidth = $uploaded.outerWidth(),
uploadedHeight = $uploaded.outerHeight(),
uploadedRate = uploadedWidth/realWidth; //缩放比例
//其他操作
$(this).hide();
$("#ratio-input").hide();
$("#cut-help").html('原图宽:'+realWidth+' 高:'+realHeight+'<strong style="color:red;"> 在图片上进行拖拽确定裁剪区域!</strong>');
$("#preview-wrap").show();
$uploaded.unbind('click');
//预览框宽高参数
var previewWrapWidth = $previewWrap.outerWidth();
previewWrapHeight = Math.round(previewWrapWidth/ratio);
//初始化预览框
$previewWrap.css( {
width:previewWrapWidth+'px',
height:previewWrapHeight+'px'
} );
//初始化预览图
$preview.prop( 'src',$uploaded.attr('src') );
//构造AreaSelect选择器
$(document).ready(function () {
var imgArea = $uploaded.imgAreaSelect({
x1: 0, y1: 0, x2: 200, y2: 200,
instance: true,
handles: true,
fadeSpeed: 300,
aspectRatio:'1:'+(1/ratio),
onSelectChange: function(img,selection){//选区改变时的触发事件
//selection包括x1,y1,x2,y2,width,height,分别为选区的偏移和高宽。
//console.log(selection);
var rate = previewWrapWidth/selection.width;//预览区相对于选择区的倍数
$preview.css({
width: Math.round(uploadedWidth*rate)+'px',
height: Math.round(uploadedHeight*rate)+'px',
"left": Math.round(selection.x1*rate*-1),
"top": Math.round(selection.y1*rate*-1)
});
//换算后的真实参数
var realSize = {
width: Math.round(selection.width/uploadedRate),
height: Math.round(selection.height/uploadedRate),
offsetLeft:Math.round(selection.x1/uploadedRate),
offsetTop: Math.round(selection.y1/uploadedRate)
}
$("#log").text('实际裁剪参数 - 宽:'+realSize.width+
' 高:'+realSize.height
//' 左偏移:'+realSize.offsetLeft+
//' 上偏移:'+realSize.offsetTop
);
$preview.data( realSize );
}
});
});
//点击确认裁剪时
$("#cut").show().click(function(e){
e.preventDefault();
var $this = $(this);
var data = $preview.data();
if( typeof data['width'] === 'undefined' ||
data['width'] == ''||
data['width'] == 0 ||
data['height'] == '' ||
data['height'] == 0 ){
alert('请先选择裁剪区域!');
return ;
}
$this.addClass('active').text('裁剪中...');
data['name'] = $uploaded.data('name');
$.ajax({
url:'/admin/emcee/emcee/cutimg',
type:'POST',
data:data,
success: function(data){
//console.log(data);
var rst = JSON.parse(data);
if( rst.status == 0 ){
alert('失败!'+rst.info);
}else{
$this.hide();
$("#download").show().prop('href',rst.url).prop('target','_blank');
$("#cuted-wrap").show();
$("#image-cuted").prop('src',rst.path);
$("#hide-cuted").prop('value',rst.path);
imgArea.setOptions({'disable':true,'hide':true});//去掉选区功能
alert('图片已裁剪!点击\'下载成品\'可下载!');
}
}
});
});
});
});
控制器:
public function actionShoot(){
if($_FILES){
$data = yii::app()->UploadReceive->receive($_FILES['upload'],'/upload/cutimg/');
echo json_encode($data);
}else{
$this->render('shoot');
}
}
public function actionCutimg(){
$filename = $_POST['name'];
$file = $_SERVER['DOCUMENT_ROOT'].'upload/cutimg/'.$filename;
//裁剪后的图片路径
$cutPicfolder = '/upload/cutimg/';
$cutPicPath = $_SERVER['DOCUMENT_ROOT'].$cutPicfolder;
$urlPath = yii::app()->UploadReceive->get_current_url();
//$urlPath = self::_get_current_url();
$urlPath = rtrim($urlPath,'/').'/';
$x1 = $_POST['offsetLeft'];
$y1 = $_POST['offsetTop'];
$width = $_POST['width'];
$height = $_POST['height'];
$type = exif_imagetype($file);
$support_type=array(IMAGETYPE_JPEG , IMAGETYPE_PNG , IMAGETYPE_GIF);
if(!in_array($type, $support_type,true)) {
$data['status'] = 0;
$data['info'] = "不支持的格式!";
echo json_encode($data);
exit;
}else{
switch($type) {
case IMAGETYPE_JPEG :
$image = imagecreatefromjpeg($file);
break;
case IMAGETYPE_PNG :
$image = imagecreatefrompng($file);
break;
case IMAGETYPE_GIF :
$image = imagecreatefromgif($file);
break;
default:
$data['status'] = 0;
$data['info'] = "不支持的格式!";
echo json_encode($data);
exit;
}
//图片裁剪
$copy = yii::app()->UploadReceive->PIPHP_ImageCrop($image, $x1, $y1, $width, $height);
//$copy = self::_PIPHP_ImageCrop($image, $x1, $y1, $width, $height);
$newName = 'cut_'.$filename;
$targetPic = $cutPicPath.$newName;
//TODO 目录与写文件检测
if(false === imagejpeg($copy, $targetPic) ){
$data['status'] = 0;
$data['info'] = "生成裁剪图片失败!请确认保存路径存在且可写!";
echo json_encode($data);
exit;
}
@unlink($file);
$data['status'] = 1;
$data['path'] = $cutPicfolder.$newName;
$data['name'] = $newName;
$data['url'] = $urlPath.$data['path'];
echo json_encode($data);
exit;
}
}
扩展应用:(extentions/UploadReceive)
备注:为了yii::app() 全局使用,在main.php配置如下
// application components
'components'=>array(
'UploadReceive'=>array(
'class'=>'application.extensions.thumb.UploadReceive',
),
<?php
class UploadReceive extends CApplicationComponent{
public function get_current_url($strip = true){
// filter function
$filter = function($input, $strip) {
$input = urldecode($input);
$input = str_ireplace(array("\0", '%00', "\x0a", '%0a', "\x1a", '%1a'), '', $input);
if ($strip) {
$input = strip_tags($input);
}
$input = htmlentities($input, ENT_QUOTES, 'UTF-8'); // or whatever encoding you use...
return trim($input);
};
$url = array();
// set protocol
$url['protocol'] = 'http://';
if (isset($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) === 'on' || $_SERVER['HTTPS'] == 1)) {
$url['protocol'] = 'https://';
} elseif (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) {
$url['protocol'] = 'https://';
}
// set host
$url['host'] = $_SERVER['HTTP_HOST'];
// set request uri in a secure way
$url['request_uri'] = $filter( dirname($_SERVER['REQUEST_URI']), $strip);
return join('', $url);
}
//裁剪图片
public function PIPHP_ImageCrop($image, $x, $y, $w, $h)
{
// Plug-in 15: Image Crop
//
// This plug-in takes a GD image and returns a cropped
// version of it. If any arguments are out of the
// image bounds then FALSE is returned. The arguments
// required are:
//
// $image: The image source
// $x & $y: The top-left corner
// $w & $h : The width and height
$tw = imagesx($image);
$th = imagesy($image);
if ($x > $tw || $y > $th || $w > $tw || $h > $th)
return FALSE;
$temp = imagecreatetruecolor($w, $h);
imagecopyresampled($temp, $image, 0, 0, $x, $y,
$w, $h, $w, $h);
return $temp;
}
public function receive($file,$path){
//存储相对地址
$path = trim($path,'/').'/';
//存储绝对地址
$savepath = $_SERVER['DOCUMENT_ROOT'].'/'.$path;
//url
//$rootUrl = 'http://'.$_SERVER['SERVER_NAME'].'/';
//初始检测
if($file['error'] > 0){
$data['status'] = 0;
switch($file['error']){
case 1: $data['info'] = '文件大小超过服务器限制';
break;
case 2: $data['info'] = '文件太大!';
break;
case 3: $data['info'] = '文件只加载了一部分!';
break;
case 4: $data['info'] = '文件加载失败!';
break;
}
return $data;
}
//大小检测
if($file['size'] > 1024*1024){
$data['status'] = 0;
$data['info'] = '文件过大!';
return $data;
}
//类型检测
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
//$ext = substr(strrchr($file['name'],"."),1);
$typeAllow = array('jpg', 'jpeg', 'gif', 'png');
if( in_array($ext, $typeAllow) ){
//严格检测
$imginfo = getimagesize($file['tmp_name']);
if(empty($imginfo) || ($ext == 'gif' && empty($imginfo['bits']))){
$data['status'] = 0;
$data['info'] = '非法图像文件!';
return $data;
}
}else{
$data['status'] = 0;
$data['info'] = '文件类型不符!只接受'.implode(',',$typeAllow).'类型图片!';
return $data;
}
//存储
$time = uniqid('upload_');
if( !is_dir($savepath) ){
if( !mkdir($savepath, 0777, true) ){
$data['status'] = 0;
$data['info'] = '上传目录不存在或不可写!请尝试手动创建:'.$savepath;
return $data;
}
}else{
if( !is_writable($savepath) ){
$data['status'] = 0;
$data['info'] = '上传目录不可写!:'.$savepath;
return $data;
}
}
$filename = $time .'.'. $ext;
$upfile = $savepath . $filename;
if(is_uploaded_file($file['tmp_name'])){
if(!move_uploaded_file($file['tmp_name'], $upfile)){
$data['status'] = 0;
$data['info'] = '移动文件失败!';
return $data;
}else{
$data['status'] = 1;
$data['info'] = '成功';
$arr = getimagesize( $upfile );
$strarr = explode("\"",$arr[3]);//分析图片宽高
$data['data'] = array(
'path'=> '/'.$path.$filename,
'name'=>$filename,
'width'=>$strarr[1],
'height'=>$strarr[3]
//'url'=>$rootUrl.$path.$filename
);
return $data;
}
}else{
$data['status'] = 0;
$data['info'] = '文件丢失或不存在';
return $data;
}
}
}