一、簡單說明
本頭像編輯器主要實現了圖片的上傳、顯示(不溢出父窗口)、旋轉、裁剪功能!
- 圖片的上傳用到的是異步上傳,頁面不進行刷新,原理是通過JQuery的異步提交+SpringMVC的上傳
- 上傳完畢后,在顯示的時候,如果圖片的長寬都比父窗口小,則垂直 水平 居中顯示在父窗口中,否則,對圖片按照寬高比進行縮放,直到長寬都不溢出!
- 旋轉功能,這塊有待改進!我在上傳一張圖片的時候,直接在后台將90度,180度,270度的也上傳了,然后,裁剪完成后,保存頭像之后,將不用的都刪除!這兒確實不太現實!這里推薦用HTML5的canvas實現!
- 裁剪功能。用到了JQuery的一個插件。參見區域選擇完成后,會自動返回 x,y,w,h。這里的x,y,w,h還需要經過處理,因為你前面可能進行了縮小顯示,這里需要讓x,y,w,h還原到未縮放前的坐標。然后,在后台中,通過這些坐標,截取圖片中 (x,y) 寬:w 高:h的圖片!
二、效果圖:
- 初始化界面
2.選擇圖片
3.顯示選擇的圖片
4.旋轉圖片
5.拖動鼠標選擇裁剪框
6.保存頭像,並且顯示
三、代碼實現
1.圖片的選擇以及上傳
我們肯定都見過自帶的上傳按鈕,這個按鈕比較難看。
,這個是不是比上一張強多了!其實就是 一個buton按鈕,然后將上面的<input type="file">隱藏了,然后當點擊button按鈕的時候,調用file的事件。這樣就實現了和點擊file一樣的效果。
<input type="button" value="選擇頭像" class="bt"><font color="#999999"><span> 支持jpg、jpeg、gif、png、bmp格式的圖片</span> <input type="file" id="file" name="file"> <!--可以在css中設置隱藏屬性或者在js中設置-->
1 $(".bt").click(function(){ 2 $("#file").click(); 3 });
這樣,就實現了彈出選擇框的功能。接下來需要實現的就是選擇圖片。這里需要注意的是,現在由於瀏覽器的安全機制,默認情況下,你是不能直接訪問本地圖片地址,例如<img src="d:1.png">這種情況下在jsp頁面中完全不能用,除非你對瀏覽器進行設置。但是這么是不合理的。所以,最好能夠上傳圖片到服務器。
SpringMVC中,上傳圖片用到了"commons-fileupload-1.2.2.jar"這個插件,如果要使用圖片上傳,需要注意一下幾點:
- 在SpringMVC的配置文件中,添加一下內容
1 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 2 <property name="defaultEncoding" value="utf-8" /> 3 <property name="maxUploadSize" value="10485760000" /> 4 <property name="maxInMemorySize" value="40960" /> 5</bean>
- 在HTML中,表單的提交中,需要設置'enctype="multipart/form-data" '
上傳完圖片后,這里不像再去跳轉,所以,這里我用到了異步上傳,所謂的異步上傳,其實就是異步提交表單。異步提交用得到了JQuery的異步提交插件:"jquery-form.js";
1 $("#file").change(function(){ 2 3 var options = { 4 url : "upload/image", //這個url是異步提交的路徑,這里對應的是上傳圖片的路徑 5 dataType : 'json', 6 contentType : "application/json; charset=utf-8", 7 success : function(data) { 8 //上傳成功后,需要進行哪些處理,比方說這里實現的是顯示圖片 9 10 }; 11 $("#uploadImgForm").ajaxSubmit(options); 12 });
如上所示,就實現了圖片的異步上傳,那么后台是如何處理?這里 是先上傳原圖,然后再用一個旋轉類,將該圖片90度,180度,270度的圖片保存成功。以供旋轉使用,這里是一個大問題,其實我們可以使用html5的canvas實現,這樣只需要上傳一張圖片呢。
1 @Controller 2 @RequestMapping(value="upload") 3 public class FileUploadController { 4 5 @RequestMapping(value="image") 6 public void fileUpload(MultipartHttpServletRequest request, HttpServletResponse response) { 7 Map<String, Object> resultMap = new HashMap<String, Object>(); 8 String newRealFileName = null; 9 try { 10 MultipartHttpServletRequest multipartRequest = request; 11 CommonsMultipartFile file = (CommonsMultipartFile) multipartRequest.getFile("file"); 12 // 獲得文件名: 13 String realFileName = file.getOriginalFilename(); 14 if(file.getSize()/1024>=5*1024){ 15 resultMap.put("status", 1); 16 resultMap.put("message", "圖片不能大於5M"); 17 }else{ 18 System.out.println("獲得文件名:" + realFileName); 19 newRealFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + realFileName.substring(realFileName.indexOf(".")); 20 // 獲取路徑 21 String ctxPath = request.getSession().getServletContext().getRealPath("//") + "//temp//"; 22 System.out.println(ctxPath); 23 // 創建文件 24 File dirPath = new File(ctxPath); 25 if (!dirPath.exists()) { 26 dirPath.mkdir(); 27 } 28 File uploadFile = new File(ctxPath +"now"+ newRealFileName); 29 FileCopyUtils.copy(file.getBytes(), uploadFile); 30 // request.setAttribute("files", loadFiles(request)); 31 32 //獲取圖片的寬度和高度 33 InputStream is = new FileInputStream(ctxPath +"now"+ newRealFileName); 34 BufferedImage buff = ImageIO.read(is); 35 int realWidth=buff.getWidth(); 36 int realHeight = buff.getHeight(); 37 is.close(); 38 //接下來將圖片的四個旋轉進行保存 39 //1.向左旋轉90度 40 String onenewRealFileName = "one"+newRealFileName; 41 Thumbnails.of(uploadFile).size(realWidth, realHeight).rotate(90).toFile(new File(ctxPath+onenewRealFileName) 42 ); 43 //2.向左旋轉180度 44 String twonewRealFileName="two"+newRealFileName; 45 Thumbnails.of(uploadFile).size(realWidth, realHeight).rotate(180).toFile(new File(ctxPath+twonewRealFileName) 46 ); 47 //3.向左旋轉270度 48 String threenewRealFileName="thr"+newRealFileName; 49 Thumbnails.of(uploadFile).size(realWidth, realHeight).rotate(270).toFile(new File(ctxPath+threenewRealFileName) 50 ); 51 resultMap.put("status", 0); 52 resultMap.put("fileName", "now"+newRealFileName); 53 } 54 } catch (Exception e) { 55 resultMap.put("status", 1); 56 resultMap.put("message", "圖片上傳出錯"); 57 System.out.println(e); 58 } finally { 59 PrintWriter out = null; 60 try { 61 out = response.getWriter(); 62 } catch (IOException e) { 63 e.printStackTrace(); 64 } 65 //必須設置字符編碼,否則返回json會亂碼 66 response.setContentType("text/html;charset=UTF-8"); 67 out.write(JSONSerializer.toJSON(resultMap).toString()); 68 out.flush(); 69 out.close(); 70 } 71 }
80}
2.圖片的展示
圖片上傳成功后,然后講一個json字符串返回到前端,其中包括了上傳完后圖片的路徑。下面就需要展示圖片。想一下,上傳的圖片大小不一,DIV如何才能包住圖片呢?如果在<img>設置圖片的寬度=div寬度,圖片的高度=div高度,這樣的話,對圖片會進行不等比例拉伸或者縮小,這樣圖片就完全變形,看上去肯定很不美觀。我們需要做的就是,讓圖片按照原來的比例進行縮放。這樣,圖片看上去沒有拉伸的那種效果。
這種請款下,就得分四種情況討論:
- 圖片的原始寬和高都比DIV的寬和高小,那么這種情況下,圖片需要在水平和垂直上居中顯示
我是通過margin-top屬性和margin-left屬性來對圖片進行設置,以使其居中。margin-top:(DIV高-圖片高)/2 margin-left:(DIV寬-圖片寬)/2
- 圖片的原始寬<DIV的寬,圖片的原始高>DIV的高,這種情況下,計算圖片的原始寬高比compareImage,然后,讓圖片的高=DIV高度,再根據圖片的原始寬高比,算出現在圖片的寬,也就是圖片縮放的效果;
這種情況下margin-top:0 margin-left:(DIV寬度-圖片現寬)/2讓圖片居中;
- 圖片的原始寬>DIV的寬,圖片的原始高<DIV的高,這種情況下,計算圖片的原始寬高比compareImage,然后,讓圖片的寬=DIV寬,再根據圖片的原始寬高比,算出現在圖片的高,也就是圖片縮放的效果;后面兩種效果圖就不展示了
- 圖片的原始寬>DIV的寬,圖片的原始高>DIV的高,這種情況比較復雜,因為你要比較誰最后溢出,例如,因為圖片的寬和高都溢出,所以,如果圖片你的寬==DIV的寬度,那么按照比例縮放后,你必須保證圖片現高<=DIV的高度。也就是必須保證圖片的寬和高都不溢出。
好,既然長寬都固定好了,肯定圖片不會溢出DIV,那么現在就需要在DIV中展示效果,這里用到了"jquery.Jcrop.min.js"這個插件來實現,圖片加載成功的時候,裁剪框也跟着顯示出來。
也許你不願意去看懂"jquery.Jcrop.min.js"的代碼,說實話,我也不願意去看,用法:
- 初始化一個對象,這個對象在我的"jQuery.UtrialAvatarCutter.js"中有說明,下面有提供下載地址,以及源碼
這里的picture_original指的是圖片外面DIV的id ,這里的resourceImage是圖片的id
這里的bigImage 和 smallImage指的是頭像預覽兩個DIV框
var cutter = new jQuery.UtrialAvatarCutter( { //主圖片所在容器ID content : "picture_original,resourceImage", //縮略圖配置,ID:所在容器ID;width,height:縮略圖大小 purviews : [{id:"bigImage",width:100,height:100},{id:"smallImage",width:50,height:50}], } );
- 在圖片加載完成的時候,這里,也就是異步提交完成的時候,進行的處理,在上面異步提交圖片的時候有說明,success()里面的邏輯代碼沒有寫,下面就是要處理的業務邏輯,也就是設置圖片的寬和高,顯示裁剪框
1 success : function(data) { 2 3 $(".page-left-center").hide(); 4 $(".page-left-center1").show(); 5 var imagepath="temp/"+data.fileName; 6 $("#resourceImage").attr("src", imagepath).load(function(){ 7 //獲取圖片的真實大小: 8 var realWidth; 9 var realHeight; 10 realWidth = parseInt(this.width); 11 realHeight =parseInt(this.height); 12 rWidth=realWidth; 13 rHeight=realHeight; 14 realCompare=parseFloat(realWidth)/parseFloat(realHeight); 15 //讓圖片適應div大小 16 console.info("圖片的真實寬度:"+realWidth); 17 console.info("圖片的真實高度:"+realHeight); 18 if(realWidth<divWidth){ 19 if(realHeight<divHeight){ 20 console.info("進入了寬小,高小"); 21 imageWidthAfter=realWidth; 22 imageHeightAfter=realHeight; 23 shengHeight=parseInt((divHeight-imageHeightAfter)/2); 24 $("#resourceImage").css("margin-top",shengHeight); 25 shengWidth=parseInt((divWidth-imageWidthAfter)/2); 26 $("#resourceImage").css("margin-left",shengWidth); 27 suoCompare=1; 28 }else{ 29 console.info("進入了寬小,高大"); 30 imageHeightAfter=divHeight; 31 imageWidthAfter=imageHeightAfter*realCompare; 32 shengWidth=parseInt((divWidth-imageWidthAfter)/2); 33 $("#resourceImage").css("margin-left",shengWidth); 34 shengHeight=0; 35 suoCompare=parseFloat(realHeight)/parseFloat(divHeight); 36 } 37 }else{ 38 if(realHeight<divHeight){ 39 console.info("進入了寬大,高小"); 40 imageWidthAfter=divWidth; 41 imageHeightAfter=parseFloat(divWidth/parseFloat(realCompare)); 42 shengHeight=parseInt((divHeight-imageHeightAfter)/2); 43 $("#resourceImage").css("margin-top",shengHeight); 44 shengWidth=0; 45 suoCompare=parseFloat(realWidth)/parseFloat(divWidth); 46 }else{ 47 console.info("進入了高大,寬大但處理后不滿"); 48 //剛開始假如高滿寬不滿,那么,根據高得出寬 49 imageHeightAfter=divHeight; 50 imageWidthAfter=imageHeightAfter*realCompare; 51 //處理完后,寬還是溢出的話,說明以寬為基准 52 console.info("處理后的寬度:"+imageWidthAfter); 53 if(imageWidthAfter>divWidth){ 54 console.info("進入到了寬大,高大但高不滿"); 55 imageWidthAfter=divWidth; 56 imageHeightAfter=parseFloat(divWidth/parseFloat(realCompare)); 57 shengHeight=parseInt((divHeight-imageHeightAfter)/2); 58 $("#resourceImage").css("margin-top",shengHeight); 59 shengWidth=0; 60 suoCompare=parseFloat(realWidth)/parseFloat(divWidth); 61 }else{ 62 shengWidth=parseInt((divWidth-imageWidthAfter)/2); 63 $("#resourceImage").css("margin-left",shengWidth); 64 shengHeight=0; 65 suoCompare=parseFloat(realHeight)/parseFloat(divHeight); 66 } 67 } 68 } 69 70 $("#resourceImage").show(); 71 $(".jcrop-holder").remove(); 72 console.info("shengHeight:"+shengHeight); 73 $("#resourceImage").width(imageWidthAfter); 74 $("#resourceImage").height(imageHeightAfter); 75 cutter.init(); 76 $(".jcrop-holder").css("margin-top",shengHeight); 77 $(".jcrop-holder").css("margin-left",shengWidth); 78 $(".jcrop-holder img").css("margin-top","0px"); 79 $(".jcrop-holder img").css("margin-left","0px"); 80 }); 81 $("#rechose").show(); 82 $(".btsave").attr("disabled",false); 83 $(".btsave").addClass("btsaveclick"); 84 } 85 };
3.圖片的旋轉
這塊實現的不是特別理想,前面也說過了,就是剛開始上傳的時候,上傳四張,0度,90度,180度,270度。最近查看了HTML5的canvas,覺得這個挺好的。以后會試着通過HTML5來實現。旋轉的實現很簡單,每點擊的時候,設置一個表示step 然后,通過switch進行判斷,如果switch=0,則設置image的src=0度的圖片,一次往后推。設置完成后,然后通過 cutter.init();重新顯示裁剪框以及圖片!
4.圖片的裁剪
圖片的裁剪原理,其實就是在原有圖片的基礎上,從(x,y)點開始,截取長度為寬度為w,高度為h的區域。
注意:由於顯示的時候,不讓圖片超出DIV,所以,對圖片進行了縮放,所以一這里你的x,y,w,h是還原后的。
1 $(".btsave").click(function(){ 2 var data = cutter.submit(); 3 var trueX=parseInt(parseFloat(data.x)*suoCompare); 4 var trueY=parseInt(parseFloat(data.y)*suoCompare); 5 var trueW=parseInt(parseFloat(data.w)*suoCompare); 6 var trueH=parseInt(parseFloat(data.h)*suoCompare); 7 var message=trueX+","+trueY+","+trueW+","+trueH+","+data.s; 8 console.info(message); 9 $("#cutHeader").val(message); 10 $("#formSubmit").submit(); 11});
如上所示,提交到后台,在后台進行處理,后台邏輯如下:
1 @RequestMapping(value="cutImage") 2 public ModelAndView cutImage(HttpServletRequest request, HttpServletResponse response) throws IOException { 3 String message=request.getParameter("header"); 4 int x=Integer.parseInt(message.split(",")[0]); 5 int y=Integer.parseInt(message.split(",")[1]); 6 int w=Integer.parseInt(message.split(",")[2]); 7 int h=Integer.parseInt(message.split(",")[3]); 8 String src=message.split(",")[4].split("\\?")[0]; 9 // 10 // 獲取路徑 11 String ctxPath = request.getSession().getServletContext().getRealPath("//") ; 12 System.out.println(ctxPath); 13 // 創建文件 14 src =ctxPath+"\\"+src; 15 String last=src.substring(src.lastIndexOf (".")); 16 File dirPath = new File(ctxPath+"\\header"); 17 if (!dirPath.exists()) { 18 dirPath.mkdir(); 19 } 20 String dest = ctxPath+"\\header\\"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+last; 21 String headerurl="header/"+new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+last; 22 boolean isSuccess=cutImageHeader(src,dest,x,y,w,h); 23 if(isSuccess){ 24 return new ModelAndView("showHeader","image",headerurl); 25 }else{ 26 return new ModelAndView("changeHeader"); 27 } 28 29 } 30 31 public static boolean cutImageHeader(String src,String dest,int x,int y,int w,int h) throws IOException{ 32 String last=src.substring(src.lastIndexOf (".")+1); 33 System.out.println("*****"+last); 34 Iterator iterator = ImageIO.getImageReadersByFormatName(last); 35 ImageReader reader = (ImageReader)iterator.next(); 36 InputStream in=new FileInputStream(src); 37 ImageInputStream iis = ImageIO.createImageInputStream(in); 38 reader.setInput(iis, true); 39 ImageReadParam param = reader.getDefaultReadParam(); 40 Rectangle rect = new Rectangle(x, y, w,h); 41 System.out.println("x:"+x+";y:"+y+";w:"+w+";h:"+h); 42 param.setSourceRegion(rect); 43 BufferedImage bi = reader.read(0,param); 44 boolean isSuccess=ImageIO.write(bi, last, new File(dest)); 45 return isSuccess; 46 }