Web Widget - Make a basic crop image function

之前工作有幸接觸到關於image crop的處理,這個功能可以運用到供使用者在線上上傳圖片,

並且針對此圖片選取crop的區塊,再將crop後的圖片鑲嵌到商品上來製作屬於自己客制化的

商品。由於當時功能早已建構出,因此現在是嘗試去建構屬於自己的crop function。

本篇主要說明,自行去製作一個簡易版的crop功能,會分成幾個部分:

一、建構image crop的模式

二、選取image crop的區塊

三、進行image crop的處理

簡易操作流程如下:

一開始直接針對一張圖片由點選button來進入crop模式,經由一個可調整大小的小區塊,利用

滑鼠點選並拖移小區塊右上、右下、左上、左下的方框進而調整crop大小。最後,點選button

傳送小區塊此時的x, y position與長、寬給server端的PHP做crop,處理完後在client端顯示crop的

結果。

以下圖片來源為Window 7 (我的圖片),以此程式作為示範之用


一、建構image crop的模式



起始畫面如上圖,相關程式碼如下:

一開始的圖片大小由HTML載入後,利用JavaScript來計算圖片適當的大小
  1. <img id="activate_img"></img><br />
  2. <input type="button" id="exec_crop_preview" value="crop image" />
  3. <input type="button" id="clear_result" value="clear" />
  4. <img src="images/ajax-loader.gif" id="process_img_crop" style="display:none" />
  5. <div id="output_result"></div>

這個部分的JavaScript code如下,activate_w, activate_h變數主要是設定一張圖的寬與高限制,

假如您的原圖大小為:1952x3264,那麼經由下面的判斷式會進入以height為主的部分,

再以比例來比較,若原圖的比例大於縮圖比例,則新圖的高會等於縮圖,如此寬就會較小

,若是以寬為主(512),那麼縮圖後的高會很長。由於一般螢幕都是寬大於高的比例,因此

在這邊的考量是當原圖比例大於縮圖比例時就會以高為縮圖大小,以避免過長!

  1. var img_obj = new Image();
  2. var activate_w = 512;
  3. var activate_h = 384;
  4. img_obj.src = "Hydrangeas.jpg";
  5. img_obj.onload = function(){
  6. var ori_aspect;
  7. var thumb_aspect;
  8. var new_height;
  9. var new_width;
  10. if(img_obj.width > img_obj.height){
  11. ori_aspect = img_obj.width/img_obj.height;
  12. thumb_aspect = activate_w/activate_h;
  13. } else{
  14. ori_aspect = img_obj.height/img_obj.width;
  15. thumb_aspect = activate_h/activate_w;
  16. }
  17. if(ori_aspect >= thumb_aspect){
  18. new_height = activate_h;
  19. new_width = img_obj.width/(img_obj.height/activate_h);
  20. } else{
  21. new_width = activate_w;
  22. new_height = img_obj.height/(img_obj.width/activate_w);
  23. }
  24. img_obj.width = new_width;
  25. img_obj.height = new_height;
  26. $("#activate_img").attr({
  27. "src": img_obj.src,
  28. "width" : img_obj.width,
  29. "height": img_obj.height
  30. });
  31. };

再來是點選crop image button進入編輯模式,如下圖:


HTML語法如下:
  1. <div class="crop_block_background">
  2. <div class="crop_block">
  3. <div id="crop_img"></div>
  4. <img id="crop_img_opacity"></img>
  5. <div class="exec_crop_region">
  6. <div id="adjudge_east_north" class="adjudge_style en"></div>
  7. <div id="adjudge_east_south" class="adjudge_style es"></div>
  8. <div id="adjudge_west_north" class="adjudge_style wn"></div>
  9. <div id="adjudge_west_south" class="adjudge_style ws"></div>
  10. </div>
  11. <div style="margin-top:20px">
  12. <input type="button" id="exec_crop" value="exec crop" />
  13. <input type="button" id="reset_crop" value="cancel" /><br />
  14. Drag Box Offset : <span id="reg_pos"></span><br />
  15. </div>
  16. </div>
  17. </div>

CSS語法如下:
  1. .crop_block{
  2. position: absolute;
  3. background-color: #FFFFFF;
  4. border:1px solid #00F;
  5. z-index: 2;
  6. }
  7. .crop_block_background{
  8. background-image: url(images/background.png);
  9. position: absolute;
  10. top: 0px;
  11. left: 0px;
  12. width:100%;
  13. height:100%;
  14. z-index:1;
  15. display:none;
  16. }
  17. .adjudge_style{
  18. background-image:url(images/adjudge_region_direction.png);
  19. width: 10px;
  20. height: 10px;
  21. z-index: 4;
  22. position: absolute;
  23. }
  24. .exec_crop_region{
  25. border:1px dashed #000;
  26. width:125px;
  27. height:125px;
  28. position: absolute;
  29. cursor: move;
  30. z-index: 3;
  31. }
  32. #crop_img{
  33. position:absolute;
  34. }
  35. .en{
  36. top:-5px; right:-5px;
  37. cursor: ne-resize;
  38. }
  39. .es{
  40. bottom:-5px; right:-5px;
  41. cursor: se-resize;
  42. }
  43. .wn{
  44. top:-5px; left:-5px;
  45. cursor: nw-resize;
  46. }
  47. .ws{
  48. bottom:-5px; left:-5px;
  49. cursor: sw-resize;
  50. }

此部分JavaScript Code如下:

  1. var adjust_ini_l = 20;
  2. var adjust_ini_t = 20;
  3. var drag_box = {
  4. state : false,
  5. min_w : 125,
  6. min_h : 125,
  7. w : 125,
  8. h : 125,
  9. ori_x : 0,
  10. ori_y : 0,
  11. m_x : 0,
  12. m_y : 0,
  13. e_mousedown : null
  14. };
  15. $("#exec_crop_preview").click(function(){
  16. $(".crop_block").css({
  17. "left" : Math.floor($(window).width()/3),
  18. "top" : 0,
  19. "width": img_obj.width,
  20. "height": img_obj.height+100,
  21. "padding": adjust_ini_l+"px "+adjust_ini_t+"px"
  22. });
  23. $("#crop_img_opacity").attr({
  24. "src": img_obj.src,
  25. "width" : img_obj.width,
  26. "height": img_obj.height
  27. });
  28. $("#crop_img_opacity").css("opacity", "0.3");
  29. $("#crop_img").css({
  30. "background-image": "url("+img_obj.src+")",
  31. "background-size" : img_obj.width+"px "+img_obj.height+"px",
  32. "width" : drag_box.min_w,
  33. "height": drag_box.min_h,
  34. "left" : adjust_ini_l,
  35. "top" : adjust_ini_t
  36. });
  37. $(".exec_crop_region").css({
  38. "left" : adjust_ini_l,
  39. "top" : adjust_ini_t
  40. });
  41. $(".crop_block_background").show();
  42. drag_box.ori_x = $(".exec_crop_region").offset().left;
  43. drag_box.ori_y = $(".exec_crop_region").offset().top;
  44. offset_img.x = $("#crop_img").offset().left;
  45. offset_img.y = $("#crop_img").offset().top;
  46. });

最外層的div為crop_block_background,主要是建立一個編輯模式的感覺,此底圖為一張半透

明圖,再來上一層為crop_block區塊,此區塊的position為JavaScript計算其值,預設是取螢幕

寬度的1/3值(左移多少)。再來是<div id="crop_img"></div>指的是圖片區塊,預設呈現的部分

為125x125,到時會因應拉大crop區塊而調整,但最小仍為125x125,皆由JavaScript控制。

接著是<img id="crop_img_opacity"></img>,這個區塊為透明圖部分,主要目的是呈現尚未

含括到的部分,因此在這裡主要有兩張圖重疊!

<div class="exec_crop_region"> ...  </div>這個部分為虛線區塊及四個控制鈕,包住目前的

crop_image呈現,也是控制可視大小的控制區塊。


二、選取image crop的區塊

這個部分是比較複雜的部分,主要功能分成:

1. 拖移區塊


相關JavaScript code如下:

  1. var drag_box = {
  2. state : false,
  3. min_w : 125,
  4. min_h : 125,
  5. w : 125,
  6. h : 125,
  7. ori_x : 0,
  8. ori_y : 0,
  9. m_x : 0,
  10. m_y : 0,
  11. e_mousedown : null
  12. };
  13. var crop_box = {w : 0, h : 0, x : 0, y : 0};
  14. var offset_img = {x : 0 , y : 0};
  15. var delta = {x : 0, y : 0};
  16. $(".exec_crop_region").mousedown(function(e){
  17. console.log("exec_crop_region mousedown");
  18. if(!drag_box.state){
  19. drag_box.state = true;
  20. drag_box.m_x = e.pageX;
  21. drag_box.m_y = e.pageY;
  22. drag_box.e_mousedown = this;
  23. }
  24. return false;
  25. });
  26. $(document).mouseup(function(e){
  27. if(drag_box.state){
  28. drag_box.state = false;
  29. drag_box.e_mousedown = null;
  30. }
  31. });
  32. $(".crop_block").mouseout(function(){
  33. if(drag_box.state){
  34. $(".exec_crop_region").offset({
  35. "left" : $("#crop_img").offset().left,
  36. "top" : $("#crop_img").offset().top
  37. });
  38. drag_box.state = false;
  39. drag_box.e_mousedown = null;
  40. }
  41. );
  42. $(document).mousemove(function(e){
  43. if(drag_box.state){
  44. delta.x = e.pageX - drag_box.m_x;
  45. delta.y = e.pageY - drag_box.m_y;
  46. d_box_offset = $(".exec_crop_region").offset();
  47. $("#reg_pos").text(
  48. "box position:("+d_box_offset.left+","+d_box_offset.top+")");
  49. crop_box.x = outside_check(d_box_offset.left + delta.x,
  50. drag_box.ori_x, img_obj.width - drag_box.w + drag_box.ori_x);
  51. crop_box.y = outside_check(d_box_offset.top + delta.y,
  52. drag_box.ori_y, img_obj.height - drag_box.h + drag_box.ori_y);
  53. $(".exec_crop_region").offset({
  54. "left" : crop_box.x,
  55. "top" : crop_box.y
  56. });
  57. img_ori_x = outside_check(d_box_offset.left - offset_img.x, 0,
  58. img_obj.width - drag_box.w);
  59. img_ori_y = outside_check(d_box_offset.top - offset_img.y, 0,
  60. img_obj.height - drag_box.h);
  61. $("#crop_img").css({
  62. "background-position" :
  63. "-"+(img_ori_x)+"px -"+(img_ori_y)+"px",
  64. "left" : img_ori_x + adjust_ini_l,
  65. "top" : img_ori_y + adjust_ini_t
  66. });
  67. drag_box.m_x = e.pageX;
  68. drag_box.m_y = e.pageY;
  69. }
  70. });
  71. function outside_check(value, bottom, top){
  72. if(value < bottom){
  73. value = bottom;
  74. }else if(value > top){
  75. value = top;
  76. }
  77. return value;
  78. }

上面的JavaScript code主要是針對點選image crop區塊,可以做拖移的動作,並且侷限在圖

片內。在這邊提一下mousemove的部分,在取得位移的差值時,會利用outside_check function

來檢查是否低於最小及最大值(實際的圖寬高 - 不透明圖寬高)的界線。

crop_box.x, crop_box.y為計算crop image(虛線框框)位移後當下的position,

而img_ori_x, img_ori_y是針對不透明的圖片當下的position!

$(".exec_crop_region").offset是相對於document的位移;

而$("#crop_img").css是相對於它的parent的位移。

2. 拉大區塊



相關JavaScript code如下:
  1. var adjudge = {
  2. ES : false,
  3. EN : false,
  4. WS : false,
  5. WN : false,
  6. m_x : 0,
  7. m_y : 0,
  8. r_width : 0,
  9. r_height : 0,
  10. left : 0,
  11. top : 0,
  12. e_mousedown : null
  13. };
  14. $("#adjudge_east_south").mousedown(function(e){
  15. if(!adjudge.ES){
  16. d_box_offset = adjudge_ini(e, this);
  17. adjudge.ES = true;
  18. adjudge.r_width = img_obj.width - 
  19. (d_box_offset.left - drag_box.ori_x);
  20. adjudge.r_height = img_obj.height - 
  21. (d_box_offset.top - drag_box.ori_y);
  22. adjudge.left = d_box_offset.left;
  23. adjudge.top = d_box_offset.top;
  24. }
  25. return false;
  26. });
  27. $("#adjudge_east_north").mousedown(function(e){
  28. if(!adjudge.EN){
  29. d_box_offset = adjudge_ini(e, this);
  30. adjudge.EN = true;
  31. adjudge.r_width = img_obj.width - 
  32. (d_box_offset.left - drag_box.ori_x);
  33. adjudge.r_height = $(".exec_crop_region").height() + 
  34. (d_box_offset.top - drag_box.ori_y);
  35. adjudge.left = d_box_offset.left;
  36. adjudge.top = d_box_offset.top + (drag_box.h - drag_box.min_h);
  37. }
  38. return false;
  39. });
  40. $("#adjudge_west_north").mousedown(function(e){
  41. if(!adjudge.WN){
  42. d_box_offset = adjudge_ini(e, this);
  43. adjudge.WN = true;
  44. adjudge.r_width = $(".exec_crop_region").width()  
  45. + (d_box_offset.left - drag_box.ori_x);
  46. adjudge.r_height = $(".exec_crop_region").height() 
  47. + (d_box_offset.top - drag_box.ori_y);
  48. adjudge.left = d_box_offset.left+ (drag_box.w - drag_box.min_w);
  49. adjudge.top = d_box_offset.top + (drag_box.h - drag_box.min_h);
  50. }
  51. return false;
  52. });
  53. $("#adjudge_west_south").mousedown(function(e){
  54. if(!adjudge.WS){
  55. d_box_offset = adjudge_ini(e, this);
  56. adjudge.WS = true;
  57. adjudge.r_width = $(".exec_crop_region").width() 
  58. + (d_box_offset.left - drag_box.ori_x);
  59. adjudge.r_height = img_obj.height 
  60. - (d_box_offset.top - drag_box.ori_y);
  61. adjudge.left = d_box_offset.left + (drag_box.w - drag_box.min_w);
  62. adjudge.top = d_box_offset.top;
  63. }
  64. return false;
  65. });
  66. function adjudge_ini(e, this_obj){
  67. adjudge.m_x = e.pageX;
  68. adjudge.m_y = e.pageY;
  69. adjudge.e_mousedown = this_obj;
  70. return $(".exec_crop_region").offset();
  71. }
  72. $(document).mousemove(function(e){
  73. if(adjudge.e_mousedown != null){
  74. d_box_offset = $(".exec_crop_region").offset();
  75. curr_crop_w = $(".exec_crop_region").width();
  76. curr_crop_h = $(".exec_crop_region").height();
  77. delta.x = e.pageX - adjudge.m_x;
  78. delta.y = e.pageY - adjudge.m_y;
  79. if(adjudge.ES){
  80. crop_box.w = outside_check(curr_crop_w + delta.x, 
  81. drag_box.min_w, adjudge.r_width);
  82. crop_box.h = outside_check(curr_crop_h + delta.y, 
  83. drag_box.min_h, adjudge.r_height);
  84. crop_box.x = adjudge.left;
  85. crop_box.y = adjudge.top;
  86. }else if(adjudge.EN){
  87. crop_box.w = outside_check(curr_crop_w + delta.x, 
  88. drag_box.min_w, adjudge.r_width);
  89. crop_box.h = outside_check(curr_crop_h - delta.y, 
  90. drag_box.min_h, adjudge.r_height);
  91. crop_box.x = adjudge.left;
  92. crop_box.y = outside_check(d_box_offset.top + delta.y, 
  93. drag_box.ori_y, adjudge.top);
  94. }else if(adjudge.WN){
  95. crop_box.w = outside_check(curr_crop_w - delta.x, 
  96. drag_box.min_w, adjudge.r_width);
  97. crop_box.h = outside_check(curr_crop_h - delta.y, 
  98. drag_box.min_h, adjudge.r_height);
  99. crop_box.x = outside_check(d_box_offset.left + delta.x, 
  100. drag_box.ori_x, adjudge.left);
  101. crop_box.y = outside_check(d_box_offset.top + delta.y, 
  102. drag_box.ori_y, adjudge.top);
  103. }else if(adjudge.WS){
  104. crop_box.w = outside_check(curr_crop_w - delta.x, 
  105. drag_box.min_w, adjudge.r_width);
  106. crop_box.h = outside_check(curr_crop_h + delta.y, 
  107. drag_box.min_h, adjudge.r_height);
  108. crop_box.x = outside_check(d_box_offset.left + delta.x, 
  109. drag_box.ori_x, adjudge.left);
  110. crop_box.y = adjudge.top;
  111. }
  112. img_ori_x = outside_check(crop_box.x - offset_img.x, 0, 
  113. img_obj.width - drag_box.min_w);
  114. img_ori_y = outside_check(crop_box.y - offset_img.y, 0, 
  115. img_obj.height - drag_box.min_h);
  116. $(".exec_crop_region").offset({
  117. "left" : crop_box.x,
  118. "top" : crop_box.y
  119. });
  120. $(".exec_crop_region").css({
  121. "width" : crop_box.w,
  122. "height" : crop_box.h
  123. });
  124. $("#crop_img").offset({
  125. "left" : crop_box.x,
  126. "top" : crop_box.y
  127. });
  128. $("#crop_img").css({
  129. "background-position" : "-"+(img_ori_x)+"px -"+(img_ori_y)+"px",
  130. "width": crop_box.w,
  131. "height":crop_box.h
  132. });
  133. adjudge.m_x = e.pageX;
  134. adjudge.m_y = e.pageY;
  135. }
  136. }).mouseup(function(e){
  137. if(adjudge.ES){
  138. adjudge.ES = false;
  139. }else if(adjudge.EN){
  140. adjudge.EN = false;
  141. }else if(adjudge.WN){
  142. adjudge.WN = false;
  143. }else if(adjudge.WS){
  144. adjudge.WS = false;
  145. }
  146. drag_box.h = $(".exec_crop_region").height();
  147. drag_box.w = $(".exec_crop_region").width();
  148. adjudge.e_mousedown = null;
  149. e.stopPropagation();
  150. });

這個部分主要都會分成四個類似的小功能做,分別是右上、右下、左上、左下等四個

方塊,來調整當下能夠延展的大小!

當您點下某個方塊時(以左上的為例),此時,您要得到當下crop image現在的offset的position,

再來是計算adjudge.r_width, adjust.r_height (還能夠延展的長與寬)

adjust.r_width = $(".exec_crop_region").width()  + (d_box_offset.left - drag_box.ori_x);

圖示如下:



d_box_offset為當下的位移 減去 drag_box一開始的位移就會得到左向箭頭的長度,再加上

目前crop_image的長度,即表示最大能夠位移到的臨界值。若左拉過來之目前的長度等於

此臨界值就無法繼續拉大了!高度以此類推。

再來是adjudge.left = d_box_offset.left+ (drag_box.w - drag_box.min_w);

一般來說width與height若是增長都是向右方及下方增長,等於左上角的原點是固定的!

因此假如我們現在要往左上增長,除了增長寬及高,還必須位移left及top才可以達到效果

以這段code為例:先計算出adjudge.top的最高位置

adjudge.top  = d_box_offset.top + (drag_box.h - drag_box.min_h);

若當下image crop在的位置是(579, 178),那麼往上拉會由下面的code計算出新的top

crop_box.y = outside_check(d_box_offset.top  + delta.y, drag_box.ori_y, adjudge.top);

d_box_offset.top  + delta.y是一直遞減,遞減到最小值drag_box.ori_y(image cop的初始top : 20)

此時若要往回拉,拉回原本的大小, 即d_box_offset.top + (drag_box.h - drag_box.min_h);

20 +  283(drag_box目前高度) - 125 = 178,此時往回拉即可以回到原來的位置,大致的概

念是這樣,寬度的部分以此類推。最簡單的是右下,往右邊及下面拉,基本上top, left是不

需要移動的!


三、進行image crop的處理

再來就是進行crop的處理,在這邊必須傳給Server端幾個參數,才可以進行crop,分別是:

var x_axis_pos = parseInt($(".exec_crop_region").css("left")) - adjust_ini_l;
var y_axis_pos = parseInt($(".exec_crop_region").css("top")) - adjust_ini_t;
var crop_width = $(".exec_crop_region").width();
var crop_height= $(".exec_crop_region").height();

寬度及高度就不用說囉!重點是$(".exec_crop_region").css("left"),表示取得當下

image_crop相對於其parent的位移,要減掉adjust_ini_l是因為crop image與圖片一開始均padding

內縮20px,因此exec_crop_region初始值為(20px, 20px)

相關的JavaScrip code如下:
  1. $("#exec_crop").click(function() {
  2. $(this).attr("disabled", true);
  3. var x_axis_pos = parseInt(
  4. $(".exec_crop_region").css("left")) - adjust_ini_l;
  5. var y_axis_pos = parseInt(
  6. $(".exec_crop_region").css("top")) - adjust_ini_t;
  7. var crop_width = $(".exec_crop_region").width();
  8. var crop_height= $(".exec_crop_region").height();
  9. var post_data = "&width="+crop_width+"&height="+
  10. crop_height+"&x_pos="+x_axis_pos+"&y_pos="+y_axis_pos;
  11. $.ajax({
  12. type: 'POST',
  13. url: 'do_img_crop.php?r='+Math.random(),
  14. data: post_data,
  15. dataType: 'text',
  16. beforeSend: function(){
  17. $("#process_img_crop").show();
  18. },
  19. complete: function(){
  20. $("#process_img_crop").hide();
  21. },
  22. success: function(text){
  23. if(text == "true"){
  24. $("#output_result").html("<div>(x, y) => ("+x_axis_pos+", "+y_axis_pos+"), w : "+crop_width+"px, h :"+crop_height+"px</div><img src='crop_Hydrangeas.jpg' />");
  25. } else{
  26. $("#output_result").html("Image Crop Error!");
  27. }
  28. reset_setting();
  29. }
  30. });
  31. console.log(x_axis_pos+","+y_axis_pos+";width:"+
  32. crop_width+";"+crop_height);
  33. });
  34. $("#clear_result").click(function(){
  35. $("#exec_crop").attr("disabled", false);
  36. $("#output_result").empty();
  37. });
  38. function reset_setting(){
  39. $(".crop_block_background").hide();
  40. $("#reg_pos").text("");
  41. $("#crop_img").css({
  42. "background-position" : "0px 0px",
  43. "width" : drag_box.min_w,
  44. "height" : drag_box.min_h
  45. });
  46. $(".exec_crop_region").css({
  47. "width" : drag_box.min_w,
  48. "height" : drag_box.min_h
  49. });
  50. }


再來是PHP code的部分:

  1. <?php
  2. $img_name = "Hydrangeas.jpg";
  3. $crop_width = $_POST["width"];
  4. $crop_height = $_POST["height"];
  5. $crop_x = $_POST["x_pos"];
  6. $crop_y = $_POST["y_pos"];
  7. // Resample
  8. $image_crop = imagecreatetruecolor($crop_width, $crop_height);
  9. $image = imagecreatefromjpeg($img_name);
  10. imagecopyresampled($image_crop, $image, 0, 0, $crop_x, $crop_y, 
  11. $crop_width, $crop_height, $crop_width, $crop_height);
  12. // Output
  13. if(imagejpeg($image_crop, "crop_".$img_name, 100)){
  14. echo "true";
  15. } else{
  16. echo "false";
  17. }
  18. // Free up memory
  19. imagedestroy($image);
  20. imagedestroy($image_crop);
  21. ?>

PHP crop image的sample可以在官網找到,觀念也講得很清楚!

最後進行image crop:

選定image crop範圍

image crop後直接呈現在下面

在這邊要提醒的是,圖片名稱是寫死的(因此每次切割後的圖片會一直覆蓋掉舊的圖片),

因此您可以自行製作上傳圖片的功能,但要注意的是Client端假設圖片呈現是512x384,

那麼Server端也需要有相同大小的圖片,以便做切割之用!

最後,這篇文章有點長及繁瑣,若有問題的地方請不吝指正!

留言