通过Canvas及File API缩放并上传图片完整演示样例
Canvas简单介绍
canvas 是一个HTML5新增的DOM元素。同意用户在页面上直接地绘制图形,一般是使用JavaScript.而不同的格式标准也是不同的,比方SVG是光栅API(raster API) 而VML却是向量API(vector API).能够考虑使用Adobe Illustrator(矢量图)作图与使用 Adobe Photoshop (光栅图)作图的差别。
在canvas(画布)上能做的事情就是读取和渲染图像,而且同意你通过JavaScript操纵图像数据。已经有非常多现存的文章来为你演示主要的图像处理——主要关注与各种不同的图像过滤技术( image filtering techniques)——但我们须要的不过缩放图片并转换到特定的文件格式,而canvas全然能够做到这些事情。
我们假定的需求。比方图像高度不超过100像素,无论原始图像有多高。主要的代码例如以下所看到的:
// 參数,最大高度 var MAX_HEIGHT = 100; // 渲染 function render(src){ // 创建一个 Image 对象 var image = new Image(); // 绑定 load 事件处理器,载入完毕后运行 image.onload = function(){ // 获取 canvas DOM 对象 var canvas = document.getElementById("myCanvas"); // 假设高度超标 if(image.height > MAX_HEIGHT) { // 宽度等比例缩放 *= image.width *= MAX_HEIGHT / image.height; image.height = MAX_HEIGHT; } // 获取 canvas的 2d 环境对象, // 能够理解Context是管理员。canvas是房子 var ctx = canvas.getContext("2d"); // canvas清屏 ctx.clearRect(0, 0, canvas.width, canvas.height); // 重置canvas宽高 canvas.width = image.width; canvas.height = image.height; // 将图像绘制到canvas上 ctx.drawImage(image, 0, 0, image.width, image.height); // !!! 注意,image 没有增加到 dom之中 }; // 设置src属性。浏览器会自己主动载入。 // 记住必须先绑定事件。才干设置src属性。否则会出同步问题。在上面的样例中。你能够使用canvas 的 toDataURL() 方法获取图像的 Base64编码的值(能够类似理解为16进制字符串,或者二进制数据流).image.src = src; };
注意: canvas 的 toDataURL() 获取的URL以字符串开头,有22个没用的数据 "data:image/png;base64,",须要在client或者服务端进行过滤.
原则上仅仅要浏览器支持。URL地址的长度是没有限制的,而1024的长度限制,是老一代IE所独有的。
请问,怎样获取我们须要的图像呢?
好孩子,非常高兴你能这么问。你并不能通过File 输入框来直接处理,你从这个文件输入框元素所能获取的不过用户所选择文件的path路径。
依照常规想象,你能够通过这个path路径信息来载入图像,可是,在浏览器里面这是不现实的。(译者注:浏览器厂商必须保证自己的浏览器绝对安全,才干获得市场,至少避免媒体的攻击,假设同意这样做,那恶意网址能够通过拼凑文件路径来尝试获取某些敏感信息).
为了实现这个需求,我们能够使用HTML5的File API 来读取用户磁盘上的文件。并用这个file来作为图像的源(src,source).
File API简单介绍
新的File API接口是在不违背不论什么安全沙盒规则下,读取和列出用户文件文件夹的一个途径—— 通过沙盒(sandbox)限制,恶意站点并不能将病毒写入用户磁盘,当然更不能运行。
我们要使用的文件读取对象叫做 FileReader,FileReader同意开发人员读取文件的内容(详细浏览器的实现方式可能大不同样)。
如果我们已经获取了图像文件的path路径,那么依赖前面的代码,使用FileReader来载入和渲染图像就变得非常easy了:
// 载入 图像文件(url路径) function loadImage(src){ // 过滤掉 非 image 类型的文件 if(!src.type.match(/image.*/)){ if(window.console){ console.log("选择的文件类型不是图片: ", src.type); } else { window.confirm("仅仅能选择图片文件"); } return; } // 创建 FileReader 对象 并调用 render 函数来完毕渲染. var reader = new FileReader(); // 绑定load事件自己主动回调函数 reader.onload = function(e){ // 调用前面的 render 函数 render(e.target.result); }; // 读取文件内容 reader.readAsDataURL(src); };请问,怎样获取文件呢?
小白兔,要有耐心!我们的下一步就是获取文件。当然有好多方法能够实现啦。
比如:你能够用文本框让用户输入文件路径,但非常显然大多数用户都不是开发人员。对输入什么值根本就不了解.
为了用户使用方便,我们採用 Drag and Drop API接口。
使用 Drag and Drop API
拖拽接口(Drag and Drop)很easy——在大多数的DOM元素上,你都能够通过绑定事件处理器来实现. 仅仅要用户从磁盘上拖动一个文件到dom对象上并放开鼠标,那我们就能够读取这个文件。代码例如以下:
function init(){ // 获取DOM元素对象 var target = document.getElementById("drop-target"); // 阻止 dragover(拖到DOM元素上方) 事件传递 target.addEventListener("dragover", function(e){e.preventDefault();}, true); // 拖动并放开鼠标的事件 target.addEventListener("drop", function(e){ // 阻止默认事件。以及事件传播 e.preventDefault(); // 调用前面的载入图像 函数,參数为dataTransfer对象的第一个文件 loadImage(e.dataTransfer.files[0]); }, true); var setheight = document.getElementById("setheight"); var maxheight = document.getElementById("maxheight"); setheight.addEventListener("click", function(e){ // var value = maxheight.value; if(/^\d+$/.test(value)){ MAX_HEIGHT = parseInt(value); } e.preventDefault(); },true); var btnsend = document.getElementById("btnsend"); btnsend.addEventListener("click", function(e){ // sendImage(); },true); };我们还能够做一些其它的处理,比方显示预览图。
但假设不想压缩图片的话,那非常可能没什么用。我们将採用Ajax通过HTTP 的post方式上传图片数据。以下的样例是使用Dojo框架来完毕请求的,当然你也能够採用其它的Ajax技术来实现.
Dojo 代码例如以下:
// 译者并不懂Dojo,所以将在后面附上jQuery的实现 // Remember that DTK 1.7+ is AMD! require(["dojo/request"], function(request){ // 设置请求URL,參数。以及回调。 request.post("image-handler.php", { data: { imageName: "myImage.png", imageData: encodeURIComponent(document.getElementById("canvas").toDataURL("image/png")) } }).then(function(text){ console.log("The server returned: ", text); }); });jQuery 实现例如以下:
// 上传图片,jQuery版 function sendImage(){ // 获取 canvas DOM 对象 var canvas = document.getElementById("myCanvas"); // 获取Base64编码后的图像数据,格式是字符串 // "data:image/png;base64,"开头,须要在client或者server端将其去掉。后面的部分能够直接写入文件。var dataurl = canvas.toDataURL("image/png"); // 为安全 对URI进行编码 // data%3Aimage%2Fpng%3Bbase64%2C 开头 var imagedata = encodeURIComponent(dataurl); //var url = $("#form").attr("action"); // 1. 假设form表单不优点理,能够使用某个hidden隐藏域来设置请求地址 // <input type="hidden" name="action" value="receive.jsp" /> var url = $("input[name='action']").val(); // 2. 也能够直接用某个dom对象的属性来获取 // <input id="imageaction" type="hidden" action="receive.jsp"> // var url = $("#imageaction").attr("action"); // 由于是string,所以server须要对数据进行转码,写文件操作等。
// 个人约定,所有http參数名字所有小写 console.log(dataurl); //console.log(imagedata); var data = { imagename: "myImage.png", imagedata: imagedata }; jQuery.ajax( { url : url, data : data, type : "POST", // 期待的返回值类型 dataType: "json", complete : function(xhr,result) { //console.log(xhr.responseText); var $tip2 = $("#tip2"); if(!xhr){ $tip2.text('网络连接失败!'); return false; } var text = xhr.responseText; if(!text){ $tip2.text('网络错误!'); return false; } var json = eval("("+text+")"); if(!json){ $tip2.text('解析错误!'); return false; } else { $tip2.text(json.message); } //console.dir(json); //console.log(xhr.responseText); } }); };
OK,搞定!你还须要做的,就是创建一个只管的用户界面,并同意你控制图片的大小。
上传到server端的数据,并不须要处理enctype为 multi-part/form-data 的情况,只一个简单的POST表单处理程序就能够了.
好了,以下附上完整的代码演示样例:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE html> <html> <head> <title>通过Canvas及File API缩放并上传图片</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="Canvas,File,Image"> <meta http-equiv="description" content="2013年8月8日,renfufei@qq.com"> <script src="http://code.jquery.com/jquery-1.7.1.min.js"></script> <script> // 參数,最大高度 var MAX_HEIGHT = 100; // 渲染 function render(src){ // 创建一个 Image 对象 var image = new Image(); // 绑定 load 事件处理器,载入完毕后运行 image.onload = function(){ // 获取 canvas DOM 对象 var canvas = document.getElementById("myCanvas"); // 假设高度超标 if(image.height > MAX_HEIGHT) { // 宽度等比例缩放 *= image.width *= MAX_HEIGHT / image.height; image.height = MAX_HEIGHT; } // 获取 canvas的 2d 环境对象, // 能够理解Context是管理员,canvas是房子 var ctx = canvas.getContext("2d"); // canvas清屏 ctx.clearRect(0, 0, canvas.width, canvas.height); // 重置canvas宽高 canvas.width = image.width; canvas.height = image.height; // 将图像绘制到canvas上 ctx.drawImage(image, 0, 0, image.width, image.height); // !!! 注意,image 没有增加到 dom之中 }; // 设置src属性。浏览器会自己主动载入。 // 记住必须先绑定事件,才干设置src属性,否则会出同步问题。 image.src = src; }; // 载入 图像文件(url路径) function loadImage(src){ // 过滤掉 非 image 类型的文件 if(!src.type.match(/image.*/)){ if(window.console){ console.log("选择的文件类型不是图片: ", src.type); } else { window.confirm("仅仅能选择图片文件"); } return; } // 创建 FileReader 对象 并调用 render 函数来完毕渲染. var reader = new FileReader(); // 绑定load事件自己主动回调函数 reader.onload = function(e){ // 调用前面的 render 函数 render(e.target.result); }; // 读取文件内容 reader.readAsDataURL(src); }; // 上传图片,jQuery版 function sendImage(){ // 获取 canvas DOM 对象 var canvas = document.getElementById("myCanvas"); // 获取Base64编码后的图像数据,格式是字符串 // "data:image/png;base64,"开头,须要在client或者server端将其去掉,后面的部分能够直接写入文件。服务端页面,receive.jspvar dataurl = canvas.toDataURL("image/png"); // 为安全 对URI进行编码 // data%3Aimage%2Fpng%3Bbase64%2C 开头 var imagedata = encodeURIComponent(dataurl); //var url = $("#form").attr("action"); // 1. 假设form表单不优点理,能够使用某个hidden隐藏域来设置请求地址 // <input type="hidden" name="action" value="receive.jsp" /> var url = $("input[name='action']").val(); // 2. 也能够直接用某个dom对象的属性来获取 // <input id="imageaction" type="hidden" action="receive.jsp"> // var url = $("#imageaction").attr("action"); // 由于是string,所以server须要对数据进行转码,写文件操作等。 // 个人约定。所有http參数名字所有小写 console.log(dataurl); //console.log(imagedata); var data = { imagename: "myImage.png", imagedata: imagedata }; jQuery.ajax( { url : url, data : data, type : "POST", // 期待的返回值类型 dataType: "json", complete : function(xhr,result) { //console.log(xhr.responseText); var $tip2 = $("#tip2"); if(!xhr){ $tip2.text('网络连接失败!'); return false; } var text = xhr.responseText; if(!text){ $tip2.text('网络错误!'); return false; } var json = eval("("+text+")"); if(!json){ $tip2.text('解析错误!'); return false; } else { $tip2.text(json.message); } //console.dir(json); //console.log(xhr.responseText); } }); }; function init(){ // 获取DOM元素对象 var target = document.getElementById("drop-target"); // 阻止 dragover(拖到DOM元素上方) 事件传递 target.addEventListener("dragover", function(e){e.preventDefault();}, true); // 拖动并放开鼠标的事件 target.addEventListener("drop", function(e){ // 阻止默认事件,以及事件传播 e.preventDefault(); // 调用前面的载入图像 函数,參数为dataTransfer对象的第一个文件 loadImage(e.dataTransfer.files[0]); }, true); var setheight = document.getElementById("setheight"); var maxheight = document.getElementById("maxheight"); setheight.addEventListener("click", function(e){ // var value = maxheight.value; if(/^\d+$/.test(value)){ MAX_HEIGHT = parseInt(value); } e.preventDefault(); },true); var btnsend = document.getElementById("btnsend"); btnsend.addEventListener("click", function(e){ // sendImage(); },true); }; window.addEventListener("DOMContentLoaded", function() { // init(); },false); </script> </head> <body> <div> <h1>通过Canvas及File API缩放并上传图片</h1> <p>从目录拖动一张照片到下方的盒子里, canvas 和 JavaScript将会自己主动的进行缩放.</p> <div> <input type="text" id="maxheight" value="100"/> <button id="setheight">设置图片最大高度</button> <input type="hidden" name="action" value="receive.jsp" /> </div> <div id="preview-row"> <div id="drop-target" style="width:400px;height:200px;min-height:100px;min-width:200px;background:#eee;cursor:pointer;">拖动图片文件到这里...</div> <div> <div> <button id="btnsend"> 上 传 </button> <span id="tip2" style="padding:8px 0;color:#f00;"></span> </div> </div> <div><h4>缩略图:</h4></div> <div id="preview" style="background:#f4f4f4;width:400px;height:200px;min-height:100px;min-width:200px;"> <canvas id="myCanvas"></canvas> </div> </div> </div> </body> </html>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@page import="sun.misc.BASE64Decoder"%> <%@page import="java.io.*"%> <%@page import="org.springframework.web.util.UriComponents"%> <%@page import="java.net.URLDecoder"%> <%! // 本文件:/receive.jsp // 图片存放路径 String photoPath = "D:/blog/upload/photo/"; File photoPathFile = new File(photoPath); // references: http://blog.csdn.net/remote_roamer/article/details/2979822 private boolean saveImageToDisk(byte[] data,String imageName) throws IOException{ int len = data.length; // // 写入到文件 FileOutputStream outputStream = new FileOutputStream(new File(photoPathFile,imageName)); outputStream.write(data); outputStream.flush(); outputStream.close(); // return true; } private byte[] decode(String imageData) throws IOException{ BASE64Decoder decoder = new BASE64Decoder(); byte[] data = decoder.decodeBuffer(imageData); for(int i=0;i<data.length;++i) { if(data[i]<0) { //调整异常数据 data[i]+=256; } } // return data; } %> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <% //假设是IE,那么须要设置为text/html,否则会弹框下载 //response.setContentType("text/html;charset=UTF-8"); response.setContentType("application/json;charset=UTF-8"); // String imageName = request.getParameter("imagename"); String imageData = request.getParameter("imagedata"); int success = 0; String message = ""; if(null == imageData || imageData.length() < 100){ // 数据太短,明显不合理 message = "上传失败,数据太短或不存在"; } else { // 去除开头不合理的数据 imageData = imageData.substring(30); imageData = URLDecoder.decode(imageData,"UTF-8"); //System.out.println(imageData); byte[] data = decode(imageData); int len = data.length; int len2 = imageData.length(); if(null == imageName || imageName.length() < 1){ imageName = System.currentTimeMillis()+".png"; } saveImageToDisk(data,imageName); // success = 1; message = "上传成功,參数长度:"+len2+"字符。解析文件大小:"+len+"字节"; } // 后台打印 System.out.println("message="+message); %> { "message": "<%=message %>", "success": <%=success %> }