Jcrop插件本身並不含有圖片截取功能,它僅僅是在前端層面構建一套截取動畫效果並產生4個坐標點,插件使用者將這4個坐標點傳回至服務器接口上進行截取操作。其優點是具有較高的通用性、瀏覽器兼容性(IE6+)及穩定性高,缺點是無法適用於手機H5開發(無圖片縮放、拖拽效果)。
最新版的Jcrop已更新到v3.0+了,本文基於 v0.9.12,由於這版本之間API及使用方式差異非常大,故本文不具備 Jcrop v3.0+ 的使用參考價值,請讀者悉知。
Jcrop V0.9+ 下載地址:http://deepliquid.com/content/Jcrop.html
Jcrop V0.9+ API中文說明:https://blog.csdn.net/xht555/article/details/43407141?utm_source=blogxgwz6
Jcrop V3.0+ 下載地址: https://github.com/tapmodo/Jcrop
html代碼,這里沒放出Jcrop.js、jcrop.css的引用,請讀者不要漏了這關鍵的兩步。
<div class="item"> <h3> 5、上傳頭像實例(預覽\截取) </h3> <!--圖片上傳表單,讀者也可以選擇其他的上傳方式--> <form id="form_upload" enctype="multipart/form-data" action="./UploadPhoto" method="post"> <input id="File5" name="UserPhoto" type="file" value="" /> <input id="btnUpload5" type="submit" value="提交上傳" /> </form> <!--截取主要內容區--> <div id="photoWrap"> <!--截取區--> <div id="crop_area"> <img id="crop_img" alt="頭像" style="display: none"/> </div> <!--預覽區,我這里做了兩個規格的預覽 48*48 180*180 --> <div id="preview_area"> <div id="preview_title">當前頭像</div> <div id="preview_small_txt">48px × 48px</div> <div id="preview_small_wrap"> <img id="preview_small_img" alt="小預覽圖" src="~/Content/default_48.png"/> </div> <div id="preview_large_txt">180px × 180px</div> <div id="preview_large_wrap"> <img id="preview_large_img" alt="大預覽圖" src="~/Content/default_180.png"/> </div> </div> </div> <!--截取框坐標點數據保存區,同時也是請求后台截取接口的表單--> <div id="crop_operation"> <form id="form_crop" action="./CropPhoto" method="post"> <input id="url" type="hidden" name="url" /> <input id="x" type="hidden" name="x" /> <input id="y" type="hidden" name="y" /> <input id="w" type="hidden" name="w" /> <input id="h" type="hidden" name="h" /> <input id="btn_crop" type="submit" value="裁剪並保存" /> </form> </div> </div>
JS代碼,所需參數
//jcrop 所需參數 var jcrop_api; var boundx; var boundy; //上方是必選參數 //下方是用於截取預覽圖的參數,可選的 //我這里設置了48*48 180*180 兩種規格的預覽效果 var $pimg_small; var $pimg_large; var xsize_small = 48; var ysize_small = 48; var xsize_large = 180; var ysize_large = 180; $(function() { $pimg_small = $('#preview_small_img'); $pimg_large = $('#preview_large_img'); });
JS代碼,主體邏輯
//上傳圖片 $('#form_upload').ajaxForm({ dataType: "json", data:$('#form_upload').serialize(), success: function (data, textStatus, jqXHR) { if (data.Status == 'success') { //上傳成功后,為主截取區、截取預覽區的img賦值 $("#crop_img,#preview_small_img,#preview_large_img").attr('src', data.ImgUrl); //用input hidden保存返回值,以便截取時使用 $("#url").val(data.ImgUrl); //if (jcrop_api != null) { // jcrop_api.setImage(data.ImgUrl, function () { // var bounds = jcrop_api.getBounds(); // boundx = bounds[0]; // boundy = bounds[1]; // var size = Math.min(boundx, boundy); // jcrop_api.setSelect([0,0,size,size]); // }); //} return; } if (data.Status == 'error') { showMessage(data.Msg); } }, error: function (jqXHR, textStatus, errorThrown) { showMessage('上傳失敗'); console.error(textStatus); } }); //當圖片加載完畢時,調用Jcrop初始化函數 //因為存在多次上傳圖片的情況,所以此處用<img/>標簽的load事件,每加載完畢一次就初始化一次 $("#crop_img").load(function () {
if (jcrop_api != null) {
jcrop_api.destroy();
}
$("#crop_img").Jcrop({ onChange: updatePreview,//截取框變化事件,主要用於實時更新預覽圖 onSelect: updateCropData,//截取框選定事件,主要用於獲得截取框的4個坐標點 aspectRatio: 1 //截取框的比例,1則是正方形 }, function () { //$().Jcrop()初始化后的回調函數; //這里為提高用戶體驗而設置了一個可選范圍內最大的截取框,以告訴用戶“你可以進行截取了”。 jcrop_api = this; var bounds = this.getBounds();//獲取圖片實際尺寸,格式為:[w,h] boundx = bounds[0]; boundy = bounds[1]; var size = Math.min(boundx, boundy); jcrop_api.setSelect([0, 0, size, size]);//4個坐標點設定一個截取框 }); }); //更新預覽圖,這函數是官方demo給出的代碼,各位可以直接copy不必深究 function updatePreview(c) { if (parseInt(c.w) > 0) { var rx_large = xsize_large / c.w; var ry_large = ysize_large / c.h; $pimg_large.css({ width: Math.round(rx_large * boundx) + 'px', height: Math.round(ry_large * boundy) + 'px', marginLeft: '-' + Math.round(rx_large * c.x) + 'px', marginTop: '-' + Math.round(ry_large * c.y) + 'px' }); var rx_small = xsize_small / c.w; var ry_small = ysize_small / c.h; $pimg_small.css({ width: Math.round(rx_small * boundx) + 'px', height: Math.round(ry_small * boundy) + 'px', marginLeft: '-' + Math.round(rx_small * c.x) + 'px', marginTop: '-' + Math.round(ry_small * c.y) + 'px' }); } }; //截取框選定事件的處理函數,用於獲取最左上角的坐標點(x、y),截取框的寬高(w、h) function updateCropData(c) { jQuery("#x").val(c.x); jQuery("#y").val(c.y); jQuery("#w").val(c.w); jQuery("#h").val(c.h) console.group('updateCropData'); console.info(c.x) console.info(c.y) console.info(c.w) console.info(c.h) console.groupEnd('updateCropData'); } //請求后台接口,進行真正的圖片截取 //因為4個坐標點是由updateCropData()函數實時更新至<form id="form_crop"/>的隱藏域里的, //所以這里只要提交表單即可,這里是使用了jquery.form.js插件,我上一篇文章中有提到jquery.form.js的使用 $('#form_crop').ajaxForm({ dataType: "json", success: function (data, textStatus, jqXHR) { if (data.Status == 'success') { showMessage('保存成功'); jcrop_api.destroy();//上傳成功后釋放jcrop return; } if (data.Status == 'error') { showMessage(data.Msg); } }, error: function (jqXHR, textStatus, errorThrown) { showMessage('請求錯誤'); console.error(textStatus); } });
后台代碼。
[HttpPost] public ActionResult UploadPhoto() { HttpPostedFileBase file = Request.Files["UserPhoto"]; string contentType = file.ContentType; string extension = Path.GetExtension(file.FileName); //檢查圖片格式 if (!IsAllowImg(contentType, extension)) { return Json(new { Status = "error", Msg = "上傳文件格式不符合要求。" }); } //保存形成保存路徑 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); string newFileName = Convert.ToInt64(ts.TotalMilliseconds).ToString(CultureInfo.InvariantCulture) + new Random().Next(100, 999); string path = Server.MapPath("~/Upload/") + newFileName + ".jpeg"; //壓縮圖片、轉換格式(jpeg) Image ResourceImage = Image.FromStream(file.InputStream); if(ResourceImage.Width>300) { int dWidth = 300; int dHeight = 300 * ResourceImage.Height / ResourceImage.Width; CompressPicture(file.InputStream, path, dWidth, dHeight,100); } else { CompressPicture(file.InputStream, path, ResourceImage.Width, ResourceImage.Height, 100); } file.InputStream.Close(); string url = Request.ApplicationPath + "Upload/" + newFileName + ".jpeg"; return Json(new { Status = "success", Msg = "上傳成功",ImgUrl= url }); } #region 壓縮圖像 /// <summary> /// 無損壓縮圖片 /// </summary> /// <param name="inputStream">原圖片</param> /// <param name="dFile">壓縮后保存位置</param> /// <param name="dWidth">寬度</param> /// <param name="dHeight">高度</param> /// <param name="flag">壓縮質量 1-100</param> /// <returns></returns> public bool CompressPicture(Stream inputStream, string dFile, int dWidth, int dHeight, int flag) { System.Drawing.Image iSource = System.Drawing.Image.FromStream(inputStream); ImageFormat tFormat = iSource.RawFormat; int sW = 0, sH = 0; //按比例縮放 Size tem_size = new Size(iSource.Width, iSource.Height); if (tem_size.Width > dWidth || tem_size.Height > dHeight) { if ((tem_size.Width * dHeight) > (tem_size.Height * dWidth)) { sW = dWidth; sH = (dWidth * tem_size.Height) / tem_size.Width; } else { sH = dHeight; sW = (tem_size.Width * dHeight) / tem_size.Height; } } else { sW = tem_size.Width; sH = tem_size.Height; } Bitmap ob = new Bitmap(dWidth, dHeight); Graphics g = Graphics.FromImage(ob); g.Clear(Color.WhiteSmoke); g.CompositingQuality = CompositingQuality.HighQuality; g.SmoothingMode = SmoothingMode.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(iSource, new Rectangle((dWidth - sW) / 2, (dHeight - sH) / 2, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel); g.Dispose(); //以下代碼為保存圖片時,設置壓縮質量 EncoderParameters ep = new EncoderParameters(); long[] qy = new long[1]; qy[0] = flag;//設置壓縮的比例1-100 EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy); ep.Param[0] = eParam; try { ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo jpegICIinfo = null; for (int x = 0; x < arrayICI.Length; x++) { if (arrayICI[x].FormatDescription.Equals("JPEG")) { jpegICIinfo = arrayICI[x]; break; } } if (jpegICIinfo != null) { ob.Save(dFile, jpegICIinfo, ep);//dFile是壓縮后的新路徑 } else { ob.Save(dFile, tFormat); } return true; } catch { return false; } finally { iSource.Dispose(); ob.Dispose(); } } /// <summary> /// 無損壓縮圖片 /// </summary> /// <param name="sFile">原圖片</param> /// <param name="dFile">壓縮后保存位置</param> /// <param name="dWidth">寬度</param> /// <param name="dHeight">高度</param> /// <param name="flag">壓縮質量 1-100</param> /// <returns></returns> public bool CompressPicture(string sFile, string dFile, int dWidth, int dHeight, int flag) { System.Drawing.Image iSource = System.Drawing.Image.FromFile(sFile); ImageFormat tFormat = iSource.RawFormat; int sW = 0, sH = 0; //按比例縮放 Size tem_size = new Size(iSource.Width, iSource.Height); if (tem_size.Width > dWidth || tem_size.Height > dHeight) { if ((tem_size.Width * dHeight) > (tem_size.Height * dWidth)) { sW = dWidth; sH = (dWidth * tem_size.Height) / tem_size.Width; } else { sH = dHeight; sW = (tem_size.Width * dHeight) / tem_size.Height; } } else { sW = tem_size.Width; sH = tem_size.Height; } Bitmap ob = new Bitmap(dWidth, dHeight); Graphics g = Graphics.FromImage(ob); g.Clear(Color.WhiteSmoke); g.CompositingQuality = CompositingQuality.HighQuality; g.SmoothingMode = SmoothingMode.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(iSource, new Rectangle((dWidth - sW) / 2, (dHeight - sH) / 2, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel); g.Dispose(); //以下代碼為保存圖片時,設置壓縮質量 EncoderParameters ep = new EncoderParameters(); long[] qy = new long[1]; qy[0] = flag;//設置壓縮的比例1-100 EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy); ep.Param[0] = eParam; try { ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo jpegICIinfo = null; for (int x = 0; x < arrayICI.Length; x++) { if (arrayICI[x].FormatDescription.Equals("JPEG")) { jpegICIinfo = arrayICI[x]; break; } } if (jpegICIinfo != null) { ob.Save(dFile, jpegICIinfo, ep);//dFile是壓縮后的新路徑 } else { ob.Save(dFile, tFormat); } return true; } catch { return false; } finally { iSource.Dispose(); ob.Dispose(); } } /// <summary> /// 按指定規格裁剪圖片 /// </summary> /// <param name="sFile">源文件路徑</param> /// <param name="dFile">輸出文件路徑</param> /// <param name="originPoint">裁剪區域的起點</param> /// <param name="sSize">裁剪區域的大小</param> /// <param name="dSize">生成的規格</param> /// <param name="flag">壓縮質量 1-100</param> /// <returns></returns> public bool CropPicture(string sFile, string dFile, Point originPoint , Size sSize, Size dSize, int flag) { System.Drawing.Image iSource = System.Drawing.Image.FromFile(sFile); ImageFormat tFormat = iSource.RawFormat; Bitmap ob = new Bitmap(dSize.Width, dSize.Height); Graphics g = Graphics.FromImage(ob); g.Clear(Color.WhiteSmoke); g.CompositingQuality = CompositingQuality.HighQuality; g.SmoothingMode = SmoothingMode.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(iSource, new Rectangle(0, 0, dSize.Width, dSize.Height), originPoint.X, originPoint.Y, sSize.Width, sSize.Height, GraphicsUnit.Pixel); g.Dispose(); //以下代碼為保存圖片時,設置壓縮質量 EncoderParameters ep = new EncoderParameters(); long[] qy = new long[1]; qy[0] = flag;//設置壓縮的比例1-100 EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy); ep.Param[0] = eParam; try { ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo jpegICIinfo = null; for (int i = 0; i < arrayICI.Length; i++) { if (arrayICI[i].FormatDescription.Equals("JPEG")) { jpegICIinfo = arrayICI[i]; break; } } if(string.IsNullOrEmpty(dFile)) { string[] temp = sFile.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); string fileName = temp[temp.Length - 1]; fileName = fileName.Insert(fileName.IndexOf('.'), '_'+ dSize.Width.ToString() + '_' + dSize.Height.ToString()); temp[temp.Length - 1] = fileName; dFile = string.Empty; foreach(string item in temp) { dFile += item + "\\"; } dFile=dFile.TrimEnd('\\'); } if (jpegICIinfo != null) { ob.Save(dFile, jpegICIinfo, ep); } else { ob.Save(dFile, tFormat); } return true; } catch { return false; } finally { iSource.Dispose(); ob.Dispose(); } } #endregion #region 判斷圖片格式 public bool IsAllowImg(string contentType, string fileExtension) { contentType = contentType.ToLower(); if(!contentType.Contains("image")) { return false; } fileExtension = fileExtension.ToLower(); string[] allowExtension = { ".bmp", ".gif", ".jpeg", ".jpg", ".png" }; foreach (string item in allowExtension) { if (fileExtension == item) { return true; } } return false; } public static bool IsAllowedExtension(HttpPostedFileBase file) { System.IO.Stream stream = file.InputStream; System.IO.BinaryReader reader = new System.IO.BinaryReader(stream); string fileclass = ""; //這里的位長要具體判斷. byte buffer; try { //buffer = r.ReadByte(); //fileclass = buffer.ToString(); //buffer = r.ReadByte(); //fileclass += buffer.ToString(); for (int i = 0; i < 2; i++) { fileclass += reader.ReadByte().ToString(); } } catch { } reader.Close(); stream.Close(); if (fileclass == "255216" || fileclass == "7173")//說明255216是jpg;7173是gif;6677是BMP,13780是PNG;7790是exe,8297是rar { return true; } else { return false; } } #endregion [HttpPost] public JsonResult CropPhoto(string url,int x,int y,int w,int h) { if (string.IsNullOrEmpty(url) || w == 0 || h == 0) { return Json(new { Status = "error", Msg = "參數錯誤" }); } Point origin = new Point(x, y); Size source = new Size(w, h); Size destSmall = new Size(48, 48); Size destLarge = new Size(180, 180); bool result1 =CropPicture(Server.MapPath(url), null, origin, source, destSmall, 100); bool result2 =CropPicture(Server.MapPath(url), null, origin, source, destLarge, 100); var jsonResult = result1 && result2 ? new { Status = "success", Msg = "操作成功" } : new { Status = "error", Msg = "裁剪圖片出現錯誤" }; return Json(jsonResult); } }
在上傳圖片時,我對寬度大於300px的圖片進行了等比壓縮,目的是控制圖片在瀏覽器截取區內顯示的比較友好。
這里也可以直接返回源圖,在 <img/> 標簽內使用 width屬性控制圖片寬度,再計算出縮放比例並將此數值存儲於 <form id="form_crop" />表單內,待請求截取圖片接口時將此縮放比例一並發送至后台,此時再按照縮放比例對坐標點及寬高做等比縮放計算即可截取出正確的圖片。這里就提供思路,源碼里我就不做這塊了。