上周需要做一個圖片上傳並且將上傳的圖片在線可以裁剪展示,覺得這個功能很有用,但是找參考資料的時候卻並不是很多,因此來將我用到的總結總結,也讓有需要的博友們直接借鑒。
首先環境介紹:
1、asp.net mvc網站,用到的前端插件是JCrop和Bootstrap-fileinput,在后端用框架自帶的一些類庫進行處理即可。
JCrop插件是用來裁剪圖片的,頁面上裁剪就是保存裁剪的位置信息,然后將位置信息轉給后台在后台進行實際圖片裁剪功能。
插件地址:http://code.ciaoca.com/jquery/jcrop/demo/
Bootstrap-fileinput插件是Bootstrap下的文件上傳的插件,功能強大,我將依靠這個完成文件的上傳,當然也能夠使用其他的文件上傳工具。
插件地址:http://plugins.krajee.com/file-input
文件上傳后頁面上展示的圖片是以Data URI Scheme方式進行展示的。
Data URI Scheme知識點:https://blog.csdn.net/aoshilang2249/article/details/51009947
2、asp.net core mvc網站,前端插件不變,但是在后端不能夠使用自帶類庫了,core下面的圖片處理相關的類庫還沒有完全移植過來,只能夠借用第三方類庫SixLabors.ImageSharp。
快速瀏覽

@{ ViewBag.Title = "文件上傳"; } <link href="@Url.Content("~/Content/Jcrop/jquery.Jcrop.css")" rel="stylesheet" /> <link href="@Url.Content("~/Content/bootstrap-fileinput/fileinput.css")" rel="stylesheet" /> <br /> <button class="btn btn-primary" data-toggle="modal" data-target="#myModal">頭像</button> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title" id="myModalLabel">圖片上傳</h4> </div> <div class="modal-body"> <div class="row"> <div class="col-md-6" style="width: 300px;"> <img id="cut-img" class="thumbnail" style="width: 300px;height:300px;" src="~/Content/defaultAvatar.jpg"><br /> </div> <div class="col-md-5"> <input type="file" name="txt_file" id="txt_file" multiple class="file-loading" /><br /> <h4>圖片說明:</h4> <p>1、圖片格式需要jpg、gif、png為后綴名.</p> <p>2、圖片可以在線裁剪大小,以裁剪后為最終結果.</p> <p>3、圖片上傳完畢即可關閉窗口.</p> </div> </div> </div> </div> </div> </div> @section scripts{ <script src="@Url.Content("~/Scripts/Jcrop/jquery.Jcrop.js")"></script> <script src="@Url.Content("~/Scripts/bootstrap-fileinput/fileinput.js")"></script> <script src="@Url.Content("~/Scripts/bootstrap-fileinput/zh.js")"></script> <script type="text/javascript"> //http://code.ciaoca.com/jquery/jcrop/ //http://code.ciaoca.com/jquery/jcrop/demo/animation //http://plugins.krajee.com/file-input //http://plugins.krajee.com/file-advanced-usage-demo#advanced-example-5 var tailorInfo = ""; //初始化fileinput function FileInput() { var oFile = new Object(); oFile.Init = function (ctrlName, uploadUrl) { var control = $('#' + ctrlName); //初始化上傳控件的樣式 control.fileinput({ language: 'zh', //設置語言 browseLabel: '選擇', browseIcon: "<i class=\"glyphicon glyphicon-picture\"></i> ", browseClass: "btn btn-primary", //按鈕樣式 uploadUrl: uploadUrl, //上傳的地址 allowedFileExtensions: ['jpg', 'gif', 'png'],//接收的文件后綴 showUpload: true, //是否顯示上傳按鈕 showCaption: false,//是否顯示標題 showPreview: false,//隱藏預覽 dropZoneEnabled: false,//是否顯示拖拽區域 uploadAsync: true,//采用異步 autoReplace: true, //minImageWidth: 50, //minImageHeight: 50, //maxImageWidth: 1000, //maxImageHeight: 1000, //maxFileSize: 0,//單位為kb,如果為0表示不限制文件大小 //minFileCount: 0, maxFileCount: 1, //表示允許同時上傳的最大文件個數 enctype: 'multipart/form-data', validateInitialCount: true, previewFileIcon: "<i class='glyphicon glyphicon-king'></i>", msgFilesTooMany: "選擇上傳的文件數量({n}) 超過允許的最大數值{m}!", uploadExtraData: function () { return { "tailorInfo": tailorInfo } } }); } return oFile; }; function PageInit() { var jcorp = null; var _this = this; var fileInput = new FileInput(); fileInput.Init("txt_file", "@Url.Action("UpLoadFile")"); var input = $('#txt_file'); //圖片上傳完成后 input.on("fileuploaded", function (event, data, previewId, index) { if (data.response.success) { jcorp.destroy(); $('#cut-img').attr('src', data.response.newImage);//Data URI Scheme形式 //$('#cut-img').attr('src', data.response.newImage + "?t=" + Math.random());//加尾巴解決緩存問題 } alert(data.response.message); }); //選擇圖片后觸發 input.on('change', function (event, data, previewId, index) { var img = $('#cut-img'); if (input[0].files && input[0].files[0]) { var reader = new FileReader(); reader.readAsDataURL(input[0].files[0]); reader.onload = function (e) { img.removeAttr('src'); img.attr('src', e.target.result); img.Jcrop({ setSelect: [0, 0, 260, 290], handleSize: 10, aspectRatio: 1,//選框寬高比 bgFade: false, bgColor: 'black', bgOpacity: 0.3, onSelect: updateCords }, function () { jcorp = this; }); }; if (jcorp != undefined) { jcorp.destroy(); } } function updateCords(obj) { tailorInfo = JSON.stringify({ "PictureWidth": $('.jcrop-holder').css('width'), "PictureHeight": $('.jcrop-holder').css('height'), "CoordinateX": obj.x, "CoordinateY": obj.y, "CoordinateWidth": obj.w, "CoordinateHeight": obj.h }); console.log(tailorInfo); } }); //上傳出現錯誤 input.on('fileuploaderror', function (event, data, msg) { alert(msg); //jcorp.destroy(); //$('#cut-img').attr('src', '/Content/defaultAvatar.jpg'); return false; }); //移除圖片 input.on('fileclear', function (event) { console.log("fileclear"); jcorp.destroy(); $('#cut-img').attr('src', '/Content/defaultAvatar.jpg'); }); }; $(function () { PageInit(); }); </script> }

public class FileInputController : Controller { [HttpGet] public ActionResult UpLoadFile() { return View(); } [HttpPost] public ActionResult UpLoadFile(string tailorInfo) { var success = false; var message = string.Empty; var newImage = string.Empty; try { var tailorInfoEntity = JsonConvert.DeserializeObject<TailorInfo>(tailorInfo); tailorInfoEntity.PictureWidth = tailorInfoEntity.PictureWidth.Replace("px", ""); tailorInfoEntity.PictureHeight = tailorInfoEntity.PictureHeight.Replace("px", ""); var file = HttpContext.Request.Files[0]; if (file != null && file.ContentLength != 0) { newImage = ImageHelper.TailorImage(file.InputStream, tailorInfoEntity); success = true; message = "保存成功"; } } catch (Exception ex) { message = "保存失敗" + ex.Message; } return Json(new { success = success, message = message, newImage = newImage }); } }

/// <summary> /// 前端裁剪信息及前端圖片展示規格 /// </summary> public class TailorInfo { public string PictureWidth { get; set; } public string PictureHeight { get; set; } public int CoordinateX { get; set; } public int CoordinateY { get; set; } public int CoordinateWidth { get; set; } public int CoordinateHeight { get; set; } }

/// <summary> /// 圖片處理 /// </summary> public static class ImageHelper { /// <summary> /// 圖片按照實際比例放大 /// </summary> /// <param name="content"></param> /// <param name="tailorInfo"></param> /// <returns></returns> public static string TailorImage(Stream content, TailorInfo tailorInfo) { var scaleWidth = Convert.ToInt16(tailorInfo.PictureWidth); var scaleHeight = Convert.ToInt16(tailorInfo.PictureHeight); Bitmap sourceBitmap = new Bitmap(content); double scaleWidthPercent = Convert.ToDouble(sourceBitmap.Width) / scaleWidth; double scaleHeightPercent = Convert.ToDouble(sourceBitmap.Height) / scaleHeight; double realX = scaleWidthPercent * tailorInfo.CoordinateX; double realY = scaleHeightPercent * tailorInfo.CoordinateY; double realWidth = scaleWidthPercent * tailorInfo.CoordinateWidth; double realHeight = scaleHeightPercent * tailorInfo.CoordinateHeight; return CropImage(content, (int)realX, (int)realY, (int)realWidth, (int)realHeight); } /// <summary> /// 生成新圖片 /// </summary> /// <param name="content"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="cropWidth"></param> /// <param name="cropHeight"></param> /// <returns></returns> public static string CropImage(byte[] content, int x, int y, int cropWidth, int cropHeight) { using (MemoryStream stream = new MemoryStream(content)) { return CropImage(stream, x, y, cropWidth, cropHeight); } } /// <summary> /// 生成新圖片 /// </summary> /// <param name="content"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="cropWidth"></param> /// <param name="cropHeight"></param> /// <returns></returns> public static string CropImage(Stream content, int x, int y, int cropWidth, int cropHeight) { using (Bitmap sourceBitmap = new Bitmap(content)) { Bitmap bitSource = new Bitmap(sourceBitmap, sourceBitmap.Width, sourceBitmap.Height); Rectangle cropRect = new Rectangle(x, y, cropWidth, cropHeight); using (Bitmap newBitMap = new Bitmap(cropWidth, cropHeight)) { newBitMap.SetResolution(sourceBitmap.HorizontalResolution, sourceBitmap.VerticalResolution); using (Graphics g = Graphics.FromImage(newBitMap)) { g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.CompositingQuality = CompositingQuality.HighQuality; g.DrawImage(bitSource, new Rectangle(0, 0, newBitMap.Width, newBitMap.Height), cropRect, GraphicsUnit.Pixel); return BitmapToBytes(newBitMap); } } } } /// <summary> /// 圖片轉到byte數組 /// </summary> /// <param name="source"></param> /// <returns></returns> public static string BitmapToBytes(Bitmap source) { ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders()[4]; EncoderParameters parameters = new EncoderParameters(1); parameters.Param[0] = new EncoderParameter(Encoder.Quality, 100L); using (MemoryStream tmpStream = new MemoryStream()) { source.Save(tmpStream, codec, parameters); byte[] data = new byte[tmpStream.Length]; tmpStream.Seek(0, SeekOrigin.Begin); tmpStream.Read(data, 0, (int)tmpStream.Length); return SaveImageToLocal(data); } } /// <summary> /// 存儲圖片到本地文件系統 /// </summary> /// <param name="binary"></param> public static string SaveImageToLocal(byte[] binary) { //根據用戶信息生成圖片名 todo var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Content\avatar.png"); File.WriteAllBytes(path, binary); return "data:images/png;base64," + Convert.ToBase64String(binary);//Data URI Scheme形式 //return @"/Content/avatar.png";//Url形式 } }
git地址:https://gitee.com/530521314/TailorImage.git
步驟:
一、引用前端插件
加入JCrop插件,在官網下載或是git上下載均可,引用這兩個文件即可,注意引用地址正確哈。
<link href="@Url.Content("~/Content/Jcrop/jquery.Jcrop.css")" rel="stylesheet" /> <script src="@Url.Content("~/Scripts/Jcrop/jquery.Jcrop.js")"></script>
加入Bootstrap插件,同樣下載或是git上下載,不建議通過nuget或是bower下載,會下載一大堆的東西,很多用不到的。引用如下文件。
<link href="@Url.Content("~/Content/bootstrap-fileinput/fileinput.css")" rel="stylesheet" /> <script src="@Url.Content("~/Scripts/bootstrap-fileinput/fileinput.js")"></script> <script src="@Url.Content("~/Scripts/bootstrap-fileinput/zh.js")"></script>
我是直接在頁面上引用的並沒有將其移動到模板上出於一些因素,暫時可以不討論這里,頁面引用結構:
二、前端文件上傳頁面設計
加入文件上傳元素,一行代碼即可,沒錯就一行:
<input type="file" name="txt_file" id="txt_file" multiple class="file-loading" /><br />
加入文件上傳相關的js代碼:
//初始化fileinput function FileInput() { var oFile = new Object(); oFile.Init = function (ctrlName, uploadUrl) { var control = $('#' + ctrlName); //初始化上傳控件的樣式 control.fileinput({ language: 'zh', //設置語言 browseLabel: '選擇', browseIcon: "<i class=\"glyphicon glyphicon-picture\"></i> ", browseClass: "btn btn-primary", //按鈕樣式 uploadUrl: uploadUrl, //上傳的地址 allowedFileExtensions: ['jpg', 'gif', 'png'],//接收的文件后綴 showUpload: true, //是否顯示上傳按鈕 showCaption: false,//是否顯示標題 showPreview: false,//隱藏預覽 dropZoneEnabled: false,//是否顯示拖拽區域 uploadAsync: true,//采用異步 autoReplace: true, //minImageWidth: 50, //minImageHeight: 50, //maxImageWidth: 1000, //maxImageHeight: 1000, //maxFileSize: 0,//單位為kb,如果為0表示不限制文件大小 //minFileCount: 0, maxFileCount: 1, //表示允許同時上傳的最大文件個數 enctype: 'multipart/form-data', validateInitialCount: true, previewFileIcon: "<i class='glyphicon glyphicon-king'></i>", msgFilesTooMany: "選擇上傳的文件數量({n}) 超過允許的最大數值{m}!", uploadExtraData: function () { return { "tailorInfo": tailorInfo } } }); } return oFile; };
然后實例化一個fileinput
var fileInput = new FileInput(); fileInput.Init("txt_file", "@Url.Action("UpLoadFile")");
至此文件上傳前端頁面已經OK了,接下來加入圖片裁剪功能。
三、前端圖片裁剪頁面設計
裁剪的話不能夠在文件上傳的框中進行直接裁剪,而是通過一個圖片標簽進行裁剪,因此加入一個圖片標簽
<img id="cut-img" class="thumbnail" style="width: 300px;height:300px;" src="~/Content/defaultAvatar.jpg"><br />
裁剪的js代碼
var input = $('#txt_file'); //選擇圖片后觸發 input.on('change', function (event, data, previewId, index) { var img = $('#cut-img'); if (input[0].files && input[0].files[0]) { var reader = new FileReader(); reader.readAsDataURL(input[0].files[0]); reader.onload = function (e) { img.removeAttr('src'); img.attr('src', e.target.result);
//關鍵在這里 img.Jcrop({ setSelect: [0, 0, 260, 290], handleSize: 10, aspectRatio: 1,//選框寬高比 bgFade: false, bgColor: 'black', bgOpacity: 0.3, onSelect: updateCords }, function () { jcorp = this; }); }; if (jcorp != undefined) { jcorp.destroy(); } } function updateCords(obj) { tailorInfo = JSON.stringify({ "PictureWidth": $('.jcrop-holder').css('width'), "PictureHeight": $('.jcrop-holder').css('height'), "CoordinateX": obj.x, "CoordinateY": obj.y, "CoordinateWidth": obj.w, "CoordinateHeight": obj.h }); console.log(tailorInfo); } });
至此前端頁面文件上傳和裁剪可以直接使用了。
四、后端文件上傳和裁剪設計
將裁剪信息傳遞到后端,前端可以使用到Bootstrap-fileinput的一個屬性uploadExtraData,可以傳遞除文件外的一些信息過來。當然如果看了Bootstrap-fileinput,其實是有兩種模式,模式一是直接表單提交,額外屬性直接用相關字段即可保存提交,我用的是模式二,異步上傳,額外的信息只能通過屬性uploadExtraData來保存提交。
獲得上傳過來的圖片信息,通過圖片輔助類的處理得到圖片的的地址,這個是用的DataURI Scheme協議,可以直接在圖片上展示圖片,而不需要用相對/絕對路徑的形式,某寶好像就是這樣。這樣一來,小圖片可以直接轉換成Base64的形式保存在數據庫,而不需要依賴本地的文件系統了,不錯額。
[HttpPost] public ActionResult UpLoadFile(string tailorInfo) { var success = false; var message = string.Empty; var newImage = string.Empty; try { var tailorInfoEntity = JsonConvert.DeserializeObject<TailorInfo>(tailorInfo); tailorInfoEntity.PictureWidth = tailorInfoEntity.PictureWidth.Replace("px", ""); tailorInfoEntity.PictureHeight = tailorInfoEntity.PictureHeight.Replace("px", ""); var file = HttpContext.Request.Files[0]; if (file != null && file.ContentLength != 0) { newImage = ImageHelper.TailorImage(file.InputStream, tailorInfoEntity); success = true; message = "保存成功"; } } catch (Exception ex) { message = "保存失敗" + ex.Message; } return Json(new { success = success, message = message, newImage = newImage }); }
圖片輔助類
/// <summary> /// 圖片處理 /// </summary> public static class ImageHelper { /// <summary> /// 圖片按照實際比例放大 /// </summary> /// <param name="content"></param> /// <param name="tailorInfo"></param> /// <returns></returns> public static string TailorImage(Stream content, TailorInfo tailorInfo) { var scaleWidth = Convert.ToInt16(tailorInfo.PictureWidth); var scaleHeight = Convert.ToInt16(tailorInfo.PictureHeight); Bitmap sourceBitmap = new Bitmap(content); double scaleWidthPercent = Convert.ToDouble(sourceBitmap.Width) / scaleWidth; double scaleHeightPercent = Convert.ToDouble(sourceBitmap.Height) / scaleHeight; double realX = scaleWidthPercent * tailorInfo.CoordinateX; double realY = scaleHeightPercent * tailorInfo.CoordinateY; double realWidth = scaleWidthPercent * tailorInfo.CoordinateWidth; double realHeight = scaleHeightPercent * tailorInfo.CoordinateHeight; return CropImage(content, (int)realX, (int)realY, (int)realWidth, (int)realHeight); } /// <summary> /// 生成新圖片 /// </summary> /// <param name="content"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="cropWidth"></param> /// <param name="cropHeight"></param> /// <returns></returns> public static string CropImage(byte[] content, int x, int y, int cropWidth, int cropHeight) { using (MemoryStream stream = new MemoryStream(content)) { return CropImage(stream, x, y, cropWidth, cropHeight); } } /// <summary> /// 生成新圖片 /// </summary> /// <param name="content"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="cropWidth"></param> /// <param name="cropHeight"></param> /// <returns></returns> public static string CropImage(Stream content, int x, int y, int cropWidth, int cropHeight) { using (Bitmap sourceBitmap = new Bitmap(content)) { Bitmap bitSource = new Bitmap(sourceBitmap, sourceBitmap.Width, sourceBitmap.Height); Rectangle cropRect = new Rectangle(x, y, cropWidth, cropHeight); using (Bitmap newBitMap = new Bitmap(cropWidth, cropHeight)) { newBitMap.SetResolution(sourceBitmap.HorizontalResolution, sourceBitmap.VerticalResolution); using (Graphics g = Graphics.FromImage(newBitMap)) { g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.HighQuality; g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.CompositingQuality = CompositingQuality.HighQuality; g.DrawImage(bitSource, new Rectangle(0, 0, newBitMap.Width, newBitMap.Height), cropRect, GraphicsUnit.Pixel); return BitmapToBytes(newBitMap); } } } } /// <summary> /// 圖片轉到byte數組 /// </summary> /// <param name="source"></param> /// <returns></returns> public static string BitmapToBytes(Bitmap source) { ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders()[4]; EncoderParameters parameters = new EncoderParameters(1); parameters.Param[0] = new EncoderParameter(Encoder.Quality, 100L); using (MemoryStream tmpStream = new MemoryStream()) { source.Save(tmpStream, codec, parameters); byte[] data = new byte[tmpStream.Length]; tmpStream.Seek(0, SeekOrigin.Begin); tmpStream.Read(data, 0, (int)tmpStream.Length); return SaveImageToLocal(data); } } /// <summary> /// 存儲圖片到本地文件系統 /// </summary> /// <param name="binary"></param> public static string SaveImageToLocal(byte[] binary) { //根據用戶信息生成圖片名 todo var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Content\avatar.png"); File.WriteAllBytes(path, binary); return "data:images/png;base64," + Convert.ToBase64String(binary);//Data URI Scheme形式 //return @"/Content/avatar.png";//Url形式 } }
最后這一步可以控制你需要的形式,是要使用路徑形式還是Data URI Scheme形式。
第一個方法將圖片根據獲得的位置信息,進行比例縮放,然后一系列處理得到新圖片。再將圖片返回給前端展示。
裁剪信息的類我是這樣設計的,但是里面的內容可以全部自定義,是由前端傳遞過來的信息決定的,應該屬於ViewModel這一類的。
/// <summary> /// 前端裁剪信息及前端圖片展示規格 /// </summary> public class TailorInfo { public string PictureWidth { get; set; } public string PictureHeight { get; set; } public int CoordinateX { get; set; } public int CoordinateY { get; set; } public int CoordinateWidth { get; set; } public int CoordinateHeight { get; set; } }
五、成果展示
至此圖片的在線裁剪搞定了。
六、 asp.net core下的后端設計
由於core並還沒有將圖片相關的類庫移植到core下,但是社區上有大神們已經完成了core下的圖片處理的類庫,我使用的是SixLabors.ImageSharp
地址:https://www.nuget.org/packages/SixLabors.ImageSharp其中介紹了怎么樣安裝的方法,不再陳述。
在asp.net core下,我只實現了一個圖片縮放的代碼,如有更多需要,可以聯系我,我將嘗試嘗試。
前端頁面無需做改動,后端代碼我們需要改變了,需要用到ImageSharp中的一些方法和屬性。
asp.net core取文件的方式變了。
var file = HttpContext.Request.Form.Files["txt_file"];
pictureUrl = "data:images/png;base64," + ImageHelper.ImageCompress(file.OpenReadStream());//圖片縮放到一定規格
在圖片輔助類中,通過ImageSharp進行處理即可將圖片縮放,當然也可以裁剪,但是我沒有去設計了:
public static string ImageCompress(Stream content) { var imageString = string.Empty; using (Image<Rgba32> image = Image.Load(content)) { image.Mutate(x => x .Resize(image.Width / 5, image.Height / 5) .Grayscale()); imageString = image.ToBase64String(ImageFormats.Bmp); } return imageString; }
在此,感謝幾位博友的文章:
@夢游的龍貓 https://www.cnblogs.com/wuxinzhe/p/6198506.html
https://yq.aliyun.com/ziliao/1140
2018-3-31,望技術有成后能回來看見自己的腳步