asp .net MVC 圖片文件上傳--關于限制圖片數量、自動壓縮等

在最近一個項目里,要求在上傳圖片的時候需要要求顯示圖片縮略圖、上傳圖片數量限制、圖片超過一定尺寸、一定的大小就自動進行壓縮,最后要求同一個頁面上傳的文件必須擁有統一的GUID,以此來識別是同一個產品的所有圖片。

MVC在上傳圖片這方面上做得非常的現代,可以通過用極簡單的代碼進行上傳圖片。圖片壓縮我是參考了網上的例程。該代碼的原理是將大圖片顯示在canvas里面,再采用canvas.toDataURL的方法得到base64字符串來實現壓縮。

弄清了原理以后,就可以進行js代碼進行采樣。這里我是參考了這篇博客,但我在實際使用中發現了一個BUG,導致上傳時顯示“參數無效”。而且由于控制器那里使用了try導致我一時半會不知錯誤是在那里,延誤了開發進程,后來我根據我的需求重新寫了這個js。

; (function () {
    function detectSubsampling(img) {
        var iw = img.naturalWidth, ih = img.naturalHeight;
        if (iw * ih > 1024 * 1024) { //大于百萬像素的就會進行抽樣
            var canvas = document.createElement('canvas');
            canvas.width = canvas.height = 1;
            var ctx = canvas.getContext('2d');
            ctx.drawImage(img, -iw + 1, 0);
            // subsampled image becomes half smaller in rendering size.
            // check alpha channel value to confirm image is covering edge pixel or not.
            // if alpha value is 0 image is not covering, hence subsampled.
            return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
        } else {
            return false;
        }
    }

    /**
     * Detecting vertical squash in loaded image.
     * Fixes a bug which squash image vertically while drawing into canvas for some images.
     */
    function detectVerticalSquash(img, iw, ih) {
        var canvas = document.createElement('canvas');
        canvas.width = 1;
        canvas.height = ih;
        var ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0);
        var data = ctx.getImageData(0, 0, 1, ih).data;
        // search image edge pixel position in case it is squashed vertically.
        var sy = 0;
        var ey = ih;
        var py = ih;
        while (py > sy) {
            var alpha = data[(py - 1) * 4 + 3];
            if (alpha === 0) {
                ey = py;
            } else {
                sy = py;
            }
            py = (ey + sy) >> 1;
        }
        var ratio = (py / ih);
        return (ratio === 0) ? 1 : ratio;
    }

    /**
     * Rendering image element (with resizing) and get its data URL
     */
    function renderImageToDataURL(img, options, doSquash) {
        var canvas = document.createElement('canvas');
        renderImageToCanvas(img, canvas, options, doSquash);
        return canvas.toDataURL("image/jpeg", options.quality || 0.8);
    }

    /**
     * Rendering image element (with resizing) into the canvas element
     */
    function renderImageToCanvas(img, canvas, options, doSquash) {
        var iw = img.naturalWidth, ih = img.naturalHeight;
        if (!(iw + ih)) return;
        var width = options.width, height = options.height;
        var ctx = canvas.getContext('2d');
        ctx.save();
        transformCoordinate(canvas, ctx, width, height, options.orientation);
        var subsampled = detectSubsampling(img);
        if (subsampled) {
            iw /= 2;
            ih /= 2;
        }
        var d = 1024; // size of tiling canvas
        var tmpCanvas = document.createElement('canvas');
        tmpCanvas.width = tmpCanvas.height = d;
        var tmpCtx = tmpCanvas.getContext('2d');
        var vertSquashRatio = doSquash ? detectVerticalSquash(img, iw, ih) : 1;
        var dw = Math.ceil(d * width / iw);
        var dh = Math.ceil(d * height / ih / vertSquashRatio);
        var sy = 0;
        var dy = 0;
        while (sy < ih) {
            var sx = 0;
            var dx = 0;
            while (sx < iw) {
                tmpCtx.clearRect(0, 0, d, d);
                tmpCtx.drawImage(img, -sx, -sy);
                ctx.drawImage(tmpCanvas, 0, 0, d, d, dx, dy, dw, dh);
                sx += d;
                dx += dw;
            }
            sy += d;
            dy += dh;
        }
        ctx.restore();
        tmpCanvas = tmpCtx = null;
    }

    /**
     * Transform canvas coordination according to specified frame size and orientation
     * 根據幀的大小和方向調整canvas
     * 方向值是來自EXIF標簽
     */
    function transformCoordinate(canvas, ctx, width, height, orientation) {
        switch (orientation) {
            case 5:
            case 6:
            case 7:
            case 8:
                canvas.width = height;
                canvas.height = width;
                break;
            default:
                canvas.width = width;
                canvas.height = height;
        }
        switch (orientation) {
            case 2:
                // horizontal flip
                ctx.translate(width, 0);
                ctx.scale(-1, 1);
                break;
            case 3:
                // 180 rotate left
                ctx.translate(width, height);
                ctx.rotate(Math.PI);
                break;
            case 4:
                // vertical flip
                ctx.translate(0, height);
                ctx.scale(1, -1);
                break;
            case 5:
                // vertical flip + 90 rotate right
                ctx.rotate(0.5 * Math.PI);
                ctx.scale(1, -1);
                break;
            case 6:
                // 90 rotate right
                ctx.rotate(0.5 * Math.PI);
                ctx.translate(0, -height);
                break;
            case 7:
                // horizontal flip + 90 rotate right
                ctx.rotate(0.5 * Math.PI);
                ctx.translate(width, -height);
                ctx.scale(-1, 1);
                break;
            case 8:
                // 90 rotate left
                ctx.rotate(-0.5 * Math.PI);
                ctx.translate(-width, 0);
                break;
            default:
                break;
        }
    }

    var URL = window.URL && window.URL.createObjectURL ? window.URL :
              window.webkitURL && window.webkitURL.createObjectURL ? window.webkitURL :
              null;

    /**
     * MegaPixImage class
     */
    function MegaPixImage(srcImage) {
        if (window.Blob && srcImage instanceof Blob) {
            if (!URL) { throw Error("No createObjectURL function found to create blob url"); }
            var img = new Image();
            img.src = URL.createObjectURL(srcImage);
            this.blob = srcImage;
            srcImage = img;
        }
        if (!srcImage.naturalWidth && !srcImage.naturalHeight) {
            var _this = this;
            srcImage.onload = srcImage.onerror = function () {
                var listeners = _this.imageLoadListeners;
                if (listeners) {
                    _this.imageLoadListeners = null;
                    for (var i = 0, len = listeners.length; i < len; i++) {
                        listeners[i]();
                    }
                }
            };
            this.imageLoadListeners = [];
        }
        this.srcImage = srcImage;
    }

    /**
     * Rendering megapix image into specified target element
     */
    MegaPixImage.prototype.render = function (target, options, callback) {
        if (this.imageLoadListeners) {
            var _this = this;
            this.imageLoadListeners.push(function () { _this.render(target, options, callback); });
            return;
        }
        options = options || {};
        var imgWidth = this.srcImage.naturalWidth, imgHeight = this.srcImage.naturalHeight,
            width = options.width, height = options.height,
            maxWidth = options.maxWidth, maxHeight = options.maxHeight,
            doSquash = !this.blob || this.blob.type === 'image/jpeg';
        if (width && !height) {
            height = (imgHeight * width / imgWidth) << 0;
        } else if (height && !width) {
            width = (imgWidth * height / imgHeight) << 0;
        } else {
            width = imgWidth;
            height = imgHeight;
        }
        if (maxWidth && width > maxWidth) {
            width = maxWidth;
            height = (imgHeight * width / imgWidth) << 0;
        }
        if (maxHeight && height > maxHeight) {
            height = maxHeight;
            width = (imgWidth * height / imgHeight) << 0;
        }
        var opt = { width: width, height: height };
        for (var k in options) opt[k] = options[k];
        target.tagName = target.tagName || "IMG";
        var tagName = target.tagName.toLowerCase();
        if (tagName === 'img') {
            target.src = renderImageToDataURL(this.srcImage, opt, doSquash);
        } else if (tagName === 'canvas') {
            renderImageToCanvas(this.srcImage, target, opt, doSquash);
        }
        if (typeof this.onrender === 'function') {
            this.onrender(target);
        }
        if (callback) {
            callback();
        }
        if (this.blob) {
            this.blob = null;
            URL.revokeObjectURL(this.srcImage.src);
        }
    };

    /**
     * Export class to global
     */
    if (typeof define === 'function' && define.amd) {
        define([], function () { return MegaPixImage; }); // for AMD loader
    } else if (typeof exports === 'object') {
        module.exports = MegaPixImage; // for CommonJS
    } else {
        this.MegaPixImage = MegaPixImage;
    }

})();

; (function ($) {
    $.extend($, {
        fileUpload: function (options) {
            var para = {
                multiple: true,
                filebutton: ".filePicker",
                uploadButton: null,
                url: "/me/MUploadImg",
                base64strUrl: "/me/MUploadImgBase64Str",
                filebase: "mfile",//mvc后臺需要對應的名稱
                auto: true,
                previewZoom: null,
                uploadComplete: function (res) {
                    console.log("uploadComplete", res);
                    uploadCount++;
                    core.checkComplete();
                },
                uploadError: function (err) {
                    console.log("uploadError", err);
                },
                onProgress: function (percent) {  // 提供給外部獲取單個文件的上傳進度,供外部實現上傳進度效果
                    console.log(percent);
                },
            };
            para = $.extend(para, options);

            var $self = $(para.filebutton);
            //先加入一個file元素
            var multiple = "";  // 設置多選的參數
            para.multiple ? multiple = "multiple" : multiple = "";
            $self.css('position', 'relative');
            $self.append('<input id="fileImage"  accept="image/*"  type="file" size="30" name="fileselect[]" ' + multiple + '>');

            var doms = {
                "fileToUpload": $self.find("#fileImage"),
                // "thumb": $self.find(".thumb"),
                // "progress": $self.find(".upload-progress")
            };

            function getBase64Image(img) {
                var canvas = document.createElement("canvas");
                canvas.width = img.width;
                canvas.height = img.height;

                var ctx = canvas.getContext("2d");
                ctx.drawImage(img, 0, 0, img.width, img.height);

                var dataURL = canvas.toDataURL("image/jpeg");
                return dataURL;

                // return dataURL.replace("data:image/png;base64,", "");
            }
            function simpleSize(size) {
                if (!size) return "0";
                if (size < 1024) {
                    return size;
                }
                var kb = size / 1024;
                if (kb < 1024) {
                    return kb.toFixed(2) + "K";
                }
                var mb = kb / 1024;
                if (mb < 1024) {
                    return mb.toFixed(2) + "M";

                }
                var gb = mb / 1024;
                return gb.toFixed(2) + "G";
            };
            //Convert DataURL to Blob to send over Ajax
            function dataURItoBlob(dataUrl) {
                // convert base64 to raw binary data held in a string
                // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
                var byteString = atob(dataUrl.split(',')[1]);

                // separate out the mime component
                var mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];

                // write the bytes of the string to an ArrayBuffer
                var ab = new ArrayBuffer(byteString.length);
                var ia = new Uint8Array(ab);
                for (var i = 0; i < byteString.length; i++) {
                    ia[i] = byteString.charCodeAt(i);
                }
                return new Blob([ab], { type: 'image/jpeg' });
            }

            var uploadCount = 0;
            var core = {
                fileSelected: function () {
                    var files = $("#fileImage")[0].files;
                    var count = files.length;
                    
                    console.log("共有" + count + "個文件");
                    for (var i = 0; i < count; i++) {
                        if (i >= para.limitCount) {
                             imClient.customerSay("最多只能選擇"+para.limitCount+"張圖片!");
                            break;
                        }
                        var item = files[i];
                        console.log("原圖片大小", item.size);

                        if (item.size > 1024 * 1024 * 1) {
                            console.log("圖片大于1M,開始進行壓縮...");

                            (function(img) {
                                var mpImg = new MegaPixImage(img);
                                var resImg = document.getElementById("resultImage");
                                resImg.file = img;
                                mpImg.render(resImg, { maxWidth: 500, maxHeight: 500, quality: 1 }, function() {
                                    var base64 = getBase64Image(resImg);
                                    var base641 = resImg.src;
                                    console.log("base641", base64.length, simpleSize(base64.length), base641.length, simpleSize(base641.length));
                                    if (para.auto) core.uploadBase64str(base641);
                                });
                            })(item);

                        } else {
                            if (para.auto) core.uploadFile(item);
                        }
                        core.previewImage(item);
                    }
                },
                uploadBase64str: function (base64Str) {
                    var guid = $("#guid0").val();
                    //var blob = dataURItoBlob(base64Str);
                    //console.log("壓縮后的文件大小", blob.size);
                    //core.uploadFile(blob);
                    var formdata = new FormData();
                    formdata.append("base64str", base64Str);
                    formdata.append("guid", guid);
                    var xhr = new XMLHttpRequest();
                    xhr.upload.addEventListener("progress", function (e) {
                        var percentComplete = Math.round(e.loaded * 100 / e.total);
                        para.onProgress(percentComplete.toString() + '%');
                    });
                    xhr.addEventListener("load", function (e) {
                        para.uploadComplete(xhr.responseText);
                    });
                    xhr.addEventListener("error", function (e) {
                        para.uploadError(e);
                    });

                    xhr.open("post", para.base64strUrl, true);
                    xhr.send(formdata);
                },
                uploadFile: function (file) {
                    console.log("開始上傳");
                    var guid = $("#guid0").val();
                    var formdata = new FormData();

                    formdata.append(para.filebase, file);//這個名字要和mvc后臺配合
                    formdata.append("guid", guid);
                    var xhr = new XMLHttpRequest();
                    xhr.upload.addEventListener("progress", function (e) {

                        var percentComplete = Math.round(e.loaded * 100 / e.total);
                        para.onProgress(percentComplete.toString() + '%');
                    });
                    xhr.addEventListener("load", function (e) {
                        para.uploadComplete(xhr.responseText);
                    });
                    xhr.addEventListener("error", function (e) {
                        para.uploadError(e);
                    });

                    xhr.open("post", para.url, true);
                    xhr.send(formdata);
                },
                checkComplete:function() {
                    var all = (doms.fileToUpload)[0].files.length;
                    if (all == uploadCount) {
                        console.log(all + "個文件上傳完畢");
                        doms.fileToUpload.remove();
                        //input有一個問題就是選擇重復的文件不會觸發change事件,所以做了一個處理,再每次上傳完之后刪掉這個元素再新增一個input。
                        $self.append('<input id="fileImage"  style="opacity:0;position:absolute;top: 0;left: 0;width:100%;height:100%" accept="image/*"  type="file" size="30" name="fileselect[]" ' + multiple + '>');

                    }
                },
                uploadFiles: function () {
                    var files = (doms.fileToUpload)[0].files;
                    for (var i = 0; i < files.length; i++) {
                        core.uploadFile(files[i]);
                    }
                },
                previewImage: function (file) {
                    if (!para.previewZoom) return;
                    var img = document.createElement("img");
                    img.file = file;
                    $(para.previewZoom).append(img);
                    // 使用FileReader方法顯示圖片內容
                    var reader = new FileReader();
                    reader.onload = (function (aImg) {
                        return function (e) {
                            aImg.src = e.target.result;
                        };
                    })(img);
                    reader.readAsDataURL(file);
                }
            }
            $(document).on("change", "#fileImage", function () {
                core.fileSelected();
            });

            $(document).on("click", para.filebutton, function () {
                console.log("clicked");
            });
            if (para.uploadButton) {
                $(document).on("click", para.uploadButton, function () {
                    core.uploadFiles();
                });
            }
        }
    });
})(jQuery);

在視圖中,我用了最簡單的頁面進行調試:


@{
    Layout = null;
}

<html>
<head>
    <meta charset="utf-8">


    <title>h5圖片壓縮與上傳</title>
    <style>
        

        #preview {
            margin-top: 30px;
        }

            #preview img {
                max-width: 30%;
            }
    </style>
</head>
<body>
<div class="page" id="upload">
    <h2>UploadImg</h2>
    <div id="dd" class="filePicker">點擊選擇文件</div>
    <input type="text" id="guid0" readonly value="@ViewBag.uid" />
    <img id="resultImage" />
    <div id="preview"></div>
</div>
    <script src="/Scripts/jquery-1.10.2.js"></script>

<script src="/Scripts/h5upload.js"></script>
<script>

    $.fileUpload({ filebutton: "#dd", previewZoom: "#preview" });
</script>

</body>
</html>

ViewBag.uid是在控制器里面直接生成的,這里我為了調試方便,直接采用type="text" 進行賦值,在實際應用可以采用hidden。

下面是控制器的內容:

 public ActionResult Uptest()
        {
            ViewBag.uid = Guid.NewGuid();
            return View();
        }

        [HttpPost]
        public ActionResult MUploadImg(HttpPostedFileBase mfile,string guid)
        {
            return UploadImg(mfile,guid);
        }

        [HttpPost]
        public ActionResult UploadImg(HttpPostedFileBase file,string guid)
        {

            if (file != null)
            {
                var path = "~/uploads/";
                var uploadpath = Server.MapPath(path);
                if (!Directory.Exists(uploadpath))
                {
                    Directory.CreateDirectory(uploadpath);
                }
                string fileName = Path.GetFileName(file.FileName);// 原始文件名稱
                string fileExtension = Path.GetExtension(fileName); // 文件擴展名
                string saveName = Guid.NewGuid() + fileExtension; // 保存文件名稱 這是個好方法。
                //string saveName = Encrypt.GenerateOrderNumber() + fileExtension; // 保存文件名稱 這是個好方法。
                file.SaveAs(uploadpath + saveName);

                return Json(new { Success = true, SaveName = path + saveName+' '+guid });
            }
            return Json(new { Success = false, Message = "請選擇要上傳的文件!" }, JsonRequestBehavior.AllowGet);

        }

        [HttpPost]
        public ActionResult MUploadImgBase64Str(string base64str, string guid)
        {
            
            var imgData = base64str.Split(',')[1];
            //過濾特殊字符即可   
            string dummyData = imgData.Trim().Replace("%", "").Replace(",", "").Replace(" ", "+");
            if (dummyData.Length % 4 > 0)
            {
                dummyData = dummyData.PadRight(dummyData.Length + 4 - dummyData.Length % 4, '=');
            }
            byte[] byteArray = Convert.FromBase64String(dummyData);
            using (System.IO.MemoryStream ms = new System.IO.MemoryStream(byteArray))
            {
                Image img = Image.FromStream(ms);
                var path = "~/uploads/";
                var uploadpath = Server.MapPath(path);

                var saveName = uploadpath + "stoneniqiu" + ".jpg";
                img.Save(saveName);
                return Json(new { Success = true, SaveName = saveName+' '+guid });
            }

            }

在控制器中,我首先傳遞一個Guid 到視圖,然后用AJAX提交,又回到控制器獲取,這是為了生成一個唯一而又統一的Guid 。在真實應用中,這里是上傳文件成功之后做一個寫入數據庫的動作,這里是為了測試,我僅僅讓它在成功處輸出,再到js用console.log 輸出在瀏覽器中顯示。

js文件的思路非常不錯,提交文件以后,通過判斷圖像的大小進行處理,文件大于某個字節以后就采樣在canvas里面顯示,再通過canvas.toDataURL的方法得到base64字符串來實現壓縮。前端壓縮的好處是速度快。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,670評論 25 708
  • 參考1-HTML5實現圖片壓縮上傳功能參考2-移動前端—圖片壓縮上傳實踐參考3-移動端H5圖片壓縮上傳 大體步驟 ...
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,154評論 4 61
  • 農歷六月十六是老家地方的節日,家家戶戶都有親戚朋友從遠處而來做客,非常熱鬧,村里還有個古戲臺,所以每年過節也會請當...
    紀念逝去的日子閱讀 223評論 0 0
  • 站在臺上的魔術師很尷尬 為只能變沒東西不斷道歉 憤怒的觀眾在討要已不存在了的手...
    扎實的小伙丶閱讀 178評論 0 1