Loading

C#/.net 通过js调用系统相机进行拍照,图片无损压缩后进行二维码识别

这两天撸了一个需求,通过 JS  调用手机后置相机,进行拍照扫码。前台实现调用手机相机,然后截取图片并上传到后台的功能。后台接收传过来的图片后,通过调用开源二维码识别库 ZXing 进行二维码数据解析。刚开始在电脑上截图,上传到后台进行识别,测试了几个没有问题。但是发布外网后,一直解析失败。我把手机拍的照片,放到电脑上也是识别不了。后来通过对比图片,发现手机拍的照片有15M大。怀疑是图片过大导致解析二维码失败,才想着把图片无损压缩后再进行二维码识别,压缩后果然见效。

1.图片无损压缩方法

  1         /// <summary>
  2         /// 无损压缩图片
  3         /// </summary>
  4         /// <param name="sFile">原图片地址</param>
  5         /// <param name="dFile">压缩后保存图片地址</param>
  6         /// <param name="flag">压缩质量(数字越小压缩率越高)1-100</param>
  7         /// <param name="size">压缩后图片的最大大小</param>
  8         /// <param name="sfsc">是否是第一次调用</param>
  9         /// <returns></returns>
 10         public static bool CompressImage(string sFile, string dFile, int flag = 90, int size = 300, bool sfsc = true)
 11         {
 12             Image iSource = Image.FromFile(sFile);
 13             ImageFormat tFormat = iSource.RawFormat;
 14             //如果是第一次调用,原始图像的大小小于要压缩的大小,则直接复制文件,并且返回true
 15             FileInfo firstFileInfo = new FileInfo(sFile);
 16             if (sfsc == true && firstFileInfo.Length < size * 1024)
 17             {
 18                 firstFileInfo.CopyTo(dFile);
 19                 return true;
 20             }
 21 
 22             int dHeight = iSource.Height / 2;
 23             int dWidth = iSource.Width / 2;
 24             int sW = 0, sH = 0;
 25             //按比例缩放
 26             Size tem_size = new Size(iSource.Width, iSource.Height);
 27             if (tem_size.Width > dHeight || tem_size.Width > dWidth)
 28             {
 29                 if ((tem_size.Width * dHeight) > (tem_size.Width * dWidth))
 30                 {
 31                     sW = dWidth;
 32                     sH = (dWidth * tem_size.Height) / tem_size.Width;
 33                 }
 34                 else
 35                 {
 36                     sH = dHeight;
 37                     sW = (tem_size.Width * dHeight) / tem_size.Height;
 38                 }
 39             }
 40             else
 41             {
 42                 sW = tem_size.Width;
 43                 sH = tem_size.Height;
 44             }
 45 
 46             Bitmap ob = new Bitmap(dWidth, dHeight);
 47             Graphics g = Graphics.FromImage(ob);
 48 
 49             g.Clear(Color.WhiteSmoke);
 50             g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
 51             g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
 52             g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
 53 
 54             g.DrawImage(iSource, new Rectangle((dWidth - sW) / 2, (dHeight - sH) / 2, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel);
 55 
 56             g.Dispose();
 57 
 58             //以下代码为保存图片时,设置压缩质量
 59             EncoderParameters ep = new EncoderParameters();
 60             long[] qy = new long[1];
 61             qy[0] = flag;//设置压缩的比例1-100
 62             EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy);
 63             ep.Param[0] = eParam;
 64 
 65             try
 66             {
 67                 ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders();
 68                 ImageCodecInfo jpegICIinfo = null;
 69                 for (int x = 0; x < arrayICI.Length; x++)
 70                 {
 71                     if (arrayICI[x].FormatDescription.Equals("JPEG"))
 72                     {
 73                         jpegICIinfo = arrayICI[x];
 74                         break;
 75                     }
 76                 }
 77                 if (jpegICIinfo != null)
 78                 {
 79                     ob.Save(dFile, jpegICIinfo, ep);//dFile是压缩后的新路径
 80                     FileInfo fi = new FileInfo(dFile);
 81                     if (fi.Length > 1024 * size)
 82                     {
 83                         flag = flag - 10;
 84                         CompressImage(sFile, dFile, flag, size, false);
 85                     }
 86                 }
 87                 else
 88                 {
 89                     ob.Save(dFile, tFormat);
 90                 }
 91                 return true;
 92             }
 93             catch
 94             {
 95                 return false;
 96             }
 97             finally
 98             {
 99 
100                 iSource.Dispose();
101                 ob.Dispose();
102                // System.IO.File.Delete(sFile);
103             }
104         }

2.js调用系统相机(推荐Firefox)

  1 @{
  2     Layout = null;
  3 }
  4 <!DOCTYPE html>
  5 <html>
  6 <head>
  7     <script src="~/Js/photo/jquery-1.11.3.js"></script>
  8     <script src="~/Js/photo/cropper.min.js"></script>
  9     <link href="~/Js/photo/cropper.min.css" rel="stylesheet" />
 10     <link href="~/Js/photo/ImgCropping.css" rel="stylesheet" />
 11     <script src="~/layer/layer.js"></script>
 12     <style>
 13         /*.cropper-crop-box {
 14             width: 400px !important;
 15             height: 400px !important;
 16         }*/
 17     </style>
 18 
 19 </head>
 20 <body>
 21     @*<div style="margin-left:50%;margin-top:25%">
 22             <button id="replaceImg" class="l-btn" style="width:400px;height:100px">更换图片</button>
 23         </div>*@
 24 
 25     <!--图片裁剪框 start-->
 26     <div style="" class="tailoring-container">
 27         @*<div class="black-cloth" onclick="closeTailor(this)"></div>*@
 28         <div class="tailoring-content">
 29             <div class="tailoring-content-one">
 30                 <label title="上传图片" for="chooseImg" class="l2-btn choose-btn">
 31                     <input type="file" accept="image/jpg,image/jpeg,image/png" name="file" id="chooseImg" class="hidden" onchange="selectImg(this)">
 32                     本地上传
 33                 </label>
 34                 @*<label title="拍照" class="l2-btn choose-btn" id='capture' style="margin-left: 2%;">拍照</label>
 35                     <label title="重拍" class="l2-btn choose-btn" id='takeAgain' style="margin-left: 2%;">重拍</label>*@
 36                 @*<div class="close-tailoring" onclick="closeTailor(this)">×</div>*@
 37             </div>
 38             <div class="tailoring-content-two">
 39                 <div class="tailoring-box-parcel">
 40                     <video id="video" width="80%" height="80%" controls style="float: left;"></video>
 41                     <canvas id="canvas" width="482px" height="448px" style="float: left;" hidden="hidden"></canvas>
 42                     <div id="showImg" hidden="hidden" style="width: 80%;height:80%;">
 43                         <img id="tailoringImg">
 44                     </div>
 45                 </div>
 46                 <div class="preview-box-parcel">
 47                     <p>图片预览:</p>
 48                     <div class="square previewImg"></div>
 49                     @*<div class="circular previewImg"></div>*@
 50                 </div>
 51             </div>
 52             <div class="tailoring-content-three">
 53                 @*<button class="l2-btn cropper-reset-btn">复位</button>
 54                     <button class="l2-btn cropper-rotate-btn">旋转</button>
 55                     <button class="l2-btn cropper-scaleX-btn">换向</button>*@
 56                 <button class="l2-btn sureCut" id="sureCut">确定</button>
 57             </div>
 58         </div>
 59     </div>
 60     <!--图片裁剪框 end-->
 61     <script>
 62 
 63         //弹出框水平垂直居中
 64         (window.onresize = function () {
 65             var win_height = $(window).height();
 66             var win_width = $(window).width();
 67             if (win_width <= 768) {
 68                 $(".tailoring-content").css({
 69                     "top": (win_height - $(".tailoring-content").outerHeight()) / 2,
 70                     "left": 0
 71                 });
 72             } else {
 73                 $(".tailoring-content").css({
 74                     "top": (win_height - $(".tailoring-content").outerHeight()) / 2,
 75                     "left": (win_width - $(".tailoring-content").outerWidth()) / 2
 76                 });
 77             }
 78         })();
 79 
 80 
 81         //图像上传
 82         function selectImg(file) {
 83             if (!file.files || !file.files[0]) {
 84                 return;
 85             }
 86             var reader = new FileReader();
 87             reader.onload = function (evt) {
 88                 var replaceSrc = evt.target.result;
 89                 //更换cropper的图片
 90                 $('#tailoringImg').cropper('replace', replaceSrc, false);//默认false,适应高度,不失真
 91 
 92             }
 93             reader.readAsDataURL(file.files[0]);
 94             mediaStreamTrack && mediaStreamTrack.stop();
 95             $("#video").hide();
 96             $("#showImg").show();
 97 
 98         }
 99         //cropper图片裁剪
100         $('#tailoringImg').cropper({
101             aspectRatio: 1 / 1,//默认比例
102             preview: '.previewImg',//预览视图
103             guides: false,  //裁剪框的虚线(九宫格)
104             autoCropArea: 0.5,  //0-1之间的数值,定义自动剪裁区域的大小,默认0.8
105             movable: false, //是否允许移动图片
106             dragCrop: true,  //是否允许移除当前的剪裁框,并通过拖动来新建一个剪裁框区域
107             movable: true,  //是否允许移动剪裁框
108             resizable: true,  //是否允许改变裁剪框的大小
109             zoomable: false,  //是否允许缩放图片大小
110             mouseWheelZoom: false,  //是否允许通过鼠标滚轮来缩放图片
111             touchDragZoom: true,  //是否允许通过触摸移动来缩放图片
112             rotatable: true,  //是否允许旋转图片
113             crop: function (e) {
114                 // 输出结果数据裁剪图像。
115             }
116         });
117 
118         //弹框
119         $("#replaceImg").on("click", function () {
120             takeImg()
121         });
122 
123         //旋转
124         $(".cropper-rotate-btn").on("click", function () {
125             $('#tailoringImg').cropper("rotate", 45);
126         });
127         //复位
128         $(".cropper-reset-btn").on("click", function () {
129             $('#tailoringImg').cropper("reset");
130         });
131         //换向
132         var flagX = true;
133         $(".cropper-scaleX-btn").on("click", function () {
134             if (flagX) {
135                 $('#tailoringImg').cropper("scaleX", -1);
136                 flagX = false;
137             } else {
138                 $('#tailoringImg').cropper("scaleX", 1);
139                 flagX = true;
140             }
141             flagX != flagX;
142         });
143 
144         //裁剪后的处理
145         var shadIndex = 0;
146         $("#sureCut").on("click", function () {
147             var cas = $('#tailoringImg').cropper('getCroppedCanvas');//获取被裁剪后的canvas
148             var base64url = cas.toDataURL('image/png'); //转换为base64地址形式
149             base64url = base64url.replace("\r", "")
150             $.ajax({
151 
152                 url: "/SysPadBindMobile/UploadPhoto",
153                 data: { op: "takePhoto", base64url: base64url },
154                 type: "POST",
155                 dataType: "json",
156                 beforeSend: function () {
157                     shadeIndex = layer.load(2, {
158                         time: 0,
159                         content: '解析中...',
160                         success: function (layero) {
161                             layero.find('.layui-layer-content').css({
162                                 'padding-left': '40px',//图标与样式会重合,这样设置可以错开
163                                 'width': '200px'//文字显示的宽度
164                             });
165                         }
166                     });
167                 },
168                 success: function (data) {
169                     layer.close(shadeIndex);
170                     //var result = parseInt($.trim(data.result));
171                     if (data.code == -1) {
172                         //未找到绑定列表
173                         var index = parent.layer.getFrameIndex(window.name);
174                         parent.layer.close(index);
175                         window.parent.showAccounts(data.msg);
176 
177                     } else if (data.code > 0) {
178                         //存在绑定列表,调用父窗体方法显示
179                         var index = parent.layer.getFrameIndex(window.name);
180                         parent.layer.close(index);
181                         window.parent.showAccounts(data.msg);
182                         //$('#attendance_info').css('color','green').text("已提交");
183                     } else {
184                         //  $.messager.alert("失败提示", "头像更新失败,请稍后重试...", 'error')
185                     }
186                 }
187             });
188             //关闭裁剪框
189             closeTailor();
190         });
191         //关闭裁剪框
192         function closeTailor() {
193             $(".tailoring-container").toggle();
194             mediaStreamTrack && mediaStreamTrack.stop();
195         }
196 
197         //访问用户媒体设备的兼容方法
198         function getUserMedia(constraints, success, error) {
199             if (navigator.mediaDevices.getUserMedia) {
200                 //最新的标准API
201                 navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
202             } else if (navigator.webkitGetUserMedia) {
203                 //webkit核心浏览器
204                 navigator.webkitGetUserMedia(constraints, success, error)
205             } else if (navigator.mozGetUserMedia) {
206                 //firfox浏览器
207                 navigator.mozGetUserMedia(constraints, success, error);
208             } else if (navigator.getUserMedia) {
209                 //旧版API
210                 navigator.getUserMedia(constraints, success, error);
211             }
212         }
213 
214         let video = document.getElementById('video');
215         let canvas = document.getElementById('canvas');
216         let context = canvas.getContext('2d');
217         var mediaStreamTrack
218         function success(stream) {
219             //兼容webkit核心浏览器
220             let CompatibleURL = window.URL || window.webkitURL;
221             //将视频流设置为video元素的源
222             mediaStreamTrack = stream.getTracks()[0];
223             //video.src = CompatibleURL.createObjectURL(stream);
224             video.srcObject = stream;
225             video.play();
226         }
227 
228         function error(error) {
229             alert('访问用户媒体设备失败,请尝试更换浏览器')
230         }
231 
232 
233 
234         document.getElementById('capture').addEventListener('click', function () {
235             context.drawImage(video, 0, 0, 480, 320);
236             mediaStreamTrack && mediaStreamTrack.stop();
237             $('#tailoringImg').cropper('replace', canvas.toDataURL("image/png"), false);//默认false,适应高度,不失真
238             $("#video").hide();//隐藏拍照框
239             $("#showImg").show()//将拍照结果显示
240         })
241 
242         //请求拍照
243         $("#takeAgain").bind("click", function () {
244             takePhoto();
245         });
246 
247         //开始拍照
248         function takeImg() {
249             $(".tailoring-container").toggle();
250             takePhoto();
251         }
252 
253         //请求摄像头
254         function takePhoto() {
255             if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia) {
256                 //调用用户媒体设备, 访问摄像头
257                 getUserMedia({ video: { width: 100, height: 100 } }, success, error);
258                 $("#showImg").hide();//隐藏拍照结果显示框
259                 //$('#showImg').html('<img id="tailoringImg" hidden="hidden">')
260                 $("#video").show();//开启拍照框
261             } else {
262                 alert('不支持访问用户媒体');
263             }
264         }
265     </script>
266 
267 </body>
268 
269 </html>

3.C# 后台接收二维码图片,进行压缩解析的相关代码

 1         [HttpPost]
 2         public ActionResult UploadPhoto(/*[System.Web.Http.FromBody] photoinfo photo*/)
 3         {
 4             try
 5             {
 6                 string base64Str = Request.Form["base64url"];
 7                 base64Str = base64Str.Substring(base64Str.IndexOf("base64,") + 7);
 8 
 9                 byte[] imgBytes = Convert.FromBase64String(base64Str);
10                 Stream stream = new MemoryStream(imgBytes);
11                 var bit = new Bitmap(stream);
12                 string photoBase = $"{AppDomain.CurrentDomain.BaseDirectory}\\photoimg";
13                 if (!Directory.Exists(photoBase))
14                 {
15                     Directory.CreateDirectory(photoBase);
16                 }
17                 string guid = Guid.NewGuid().ToString();
18                 string oldPath = $"{photoBase}\\{guid}.png";
19                 string newPath = $"{photoBase}\\{guid}_1.png";
20                 bit.Save(oldPath);
21 
22                 var compress = CompressImage(oldPath, newPath);
23                 var bindInfo = new QrCodeInfo();
24 
25                 if (!compress)
26                 {
27                     var result = new ZXing.BarcodeReader().Decode(bit);
28                     bit.Dispose();
29                     if (result != null && !string.IsNullOrEmpty(result.Text))
30                     {
31                         bindInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<QrCodeInfo>(result.Text);
32                     }
33                 }
34                 else
35                 {
36 
37                     var f = System.IO.File.Open(newPath, System.IO.FileMode.Open);
38                     var newbit = new Bitmap(f);
39                     var result = new BarcodeReader().Decode(newbit);
40                     f.Dispose();
41                     newbit.Dispose();
42                     if (result != null && !string.IsNullOrEmpty(result.Text))
43                     {
44                         bindInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<QrCodeInfo>(result.Text);
45                     }
46                 }
47                 // bit.Save($"{photoBase}\\{Guid.NewGuid()}.png");
48 
49                 if (bindInfo != null)
50                 {
51                     //传参,调用方法,显示查询查询到的数据  
52                     var accountList = _iSysPadBindMobileCore.GetAccountsByMachineId(bindInfo.MachineId, bindInfo.Model);
53                     if (accountList.Any())
54                     {
55                         return Json(new { code = 1, msg = Newtonsoft.Json.JsonConvert.SerializeObject(accountList) }, JsonRequestBehavior.AllowGet);
56                     }
57                 }
58             }
59             catch (Exception ex)
60             {
61                 return Json(new { code = -1, msg = ex.ToString().Substring(0, 20) }, JsonRequestBehavior.AllowGet);
62             }
63 
64             return Json(new { code = -1, msg = "" }, JsonRequestBehavior.AllowGet);
65         }

PS:另外在上传图片的过程中,可能会因为图片文件过大导致报500错误,只需要在web.config的<system.web></system.web>节点中配置大小即可:   <httpRuntime maxRequestLength="102400" executionTimeout="200" enable="true" />

 

posted @ 2019-11-06 20:34  Yuliang.wang  阅读(1330)  评论(0编辑  收藏  举报