溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java中怎么實(shí)現(xiàn)圖片滑動(dòng)驗(yàn)證

發(fā)布時(shí)間:2021-08-07 11:58:17 來(lái)源:億速云 閱讀:115 作者:Leah 欄目:編程語(yǔ)言

java中怎么實(shí)現(xiàn)圖片滑動(dòng)驗(yàn)證,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。

一、后端圖片裁剪與生成

首先是一個(gè)圖片處理工具VerifyImageUtil.class,它主要的作用是生成兩張圖片:一張被扣除了一部分的原始圖片;一張摳出來(lái)圖片。兩兩結(jié)合,可以組成一張完整的圖片。原始圖片(target目錄)提供了20張,規(guī)格都是590*360的;摳圖需要的模板圖(template目錄)有4張,規(guī)格都是93*360的(圖片等各種資源會(huì)在文末給出)。將圖片資源導(dǎo)入到我們項(xiàng)目的靜態(tài)資源路徑下(你也可以通過(guò)其他方式存儲(chǔ)它們),我這邊是Spring Boot的項(xiàng)目,所以就放在resource下的static目錄下了:

下面是VerifyImageUtil.class

package com.mine.risk.util; import org.apache.commons.lang.StringUtils; import javax.imageio.ImageIO;import javax.imageio.ImageReadParam;import javax.imageio.ImageReader;import javax.imageio.stream.ImageInputStream;import java.awt.*;import java.awt.image.BufferedImage;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.InputStream;import java.text.NumberFormat;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Random; /** * 滑塊驗(yàn)證工具類 * @author : spirit * @date : Created in 10:57 2019/9/05 */public class VerifyImageUtil {  /** 源文件寬度 */ private static final int ORI_WIDTH = 590; /** 源文件高度 */ private static final int ORI_HEIGHT = 360;  /** 摳圖坐標(biāo)x */ private static int X;  /** 摳圖坐標(biāo)y */ private static int Y;  /** 模板圖寬度 */ private static int WIDTH;  /** 模板圖高度 */ private static int HEIGHT;  public static int getX() {  return X; }  public static int getY() {  return Y; }  /**  * 根據(jù)模板切圖  * @param templateFile 模板文件  * @param targetFile 目標(biāo)文件  * @param templateType 模板文件類型  * @param targetType 目標(biāo)文件類型  * @return 切圖map集合  * @throws Exception 異常  */ public static Map<String, byte[]> pictureTemplatesCut(File templateFile, File targetFile, String templateType, String targetType) throws Exception {  Map<String, byte[]> pictureMap = new HashMap<>(2);  if (StringUtils.isEmpty(templateType) || StringUtils.isEmpty(targetType)) {   throw new RuntimeException("file type is empty");  }  InputStream targetIs = new FileInputStream(targetFile);  // 模板圖  BufferedImage imageTemplate = ImageIO.read(templateFile);  WIDTH = imageTemplate.getWidth();  HEIGHT = imageTemplate.getHeight();  // 隨機(jī)生成摳圖坐標(biāo)  generateCutoutCoordinates();  // 最終圖像  BufferedImage newImage = new BufferedImage(WIDTH, HEIGHT, imageTemplate.getType());  Graphics2D graphics = newImage.createGraphics();  graphics.setBackground(Color.white);   int bold = 5;  // 獲取感興趣的目標(biāo)區(qū)域  BufferedImage targetImageNoDeal = getTargetArea(X, Y, WIDTH, HEIGHT, targetIs, targetType);   // 根據(jù)模板圖片摳圖  newImage = dealCutPictureByTemplate(targetImageNoDeal, imageTemplate, newImage);   // 設(shè)置“抗鋸齒”的屬性  graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);  graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));  graphics.drawImage(newImage, 0, 0, null);  graphics.dispose();   //新建流。  ByteArrayOutputStream os = new ByteArrayOutputStream();  //利用ImageIO類提供的write方法,將bi以png圖片的數(shù)據(jù)模式寫入流。  ImageIO.write(newImage, "png", os);  byte[] newImages = os.toByteArray();  pictureMap.put("newImage", newImages);   // 源圖生成遮罩  BufferedImage oriImage = ImageIO.read(targetFile);  byte[] oriCopyImages = dealOriPictureByTemplate(oriImage, imageTemplate, X, Y);  pictureMap.put("oriCopyImage", oriCopyImages);  System.out.println("X="+X+";y="+Y);  return pictureMap; }  /**  * 摳圖后原圖生成  * @param oriImage 原始圖片  * @param templateImage 模板圖片  * @param x 坐標(biāo)X  * @param y 坐標(biāo)Y  * @return 添加遮罩層后的原始圖片  * @throws Exception 異常  */ private static byte[] dealOriPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage, int x,             int y) throws Exception {  // 源文件備份圖像矩陣 支持alpha通道的rgb圖像  BufferedImage oriCopyImage = new BufferedImage(oriImage.getWidth(), oriImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);  // 源文件圖像矩陣  int[][] oriImageData = getData(oriImage);  // 模板圖像矩陣  int[][] templateImageData = getData(templateImage);   //copy 源圖做不透明處理  for (int i = 0; i < oriImageData.length; i++) {   for (int j = 0; j < oriImageData[0].length; j++) {    int rgb = oriImage.getRGB(i, j);    int r = (0xff & rgb);    int g = (0xff & (rgb >> 8));    int b = (0xff & (rgb >> 16));    //無(wú)透明處理    rgb = r + (g << 8) + (b << 16) + (255 << 24);    oriCopyImage.setRGB(i, j, rgb);   }  }   for (int i = 0; i < templateImageData.length; i++) {   for (int j = 0; j < templateImageData[0].length - 5; j++) {    int rgb = templateImage.getRGB(i, j);    //對(duì)源文件備份圖像(x+i,y+j)坐標(biāo)點(diǎn)進(jìn)行透明處理    if (rgb != 16777215 && rgb <= 0) {     int rgb_ori = oriCopyImage.getRGB(x + i, y + j);     int r = (0xff & rgb_ori);     int g = (0xff & (rgb_ori >> 8));     int b = (0xff & (rgb_ori >> 16));     rgb_ori = r + (g << 8) + (b << 16) + (150 << 24);     oriCopyImage.setRGB(x + i, y + j, rgb_ori);    } else {     //do nothing    }   }  }  //新建流  ByteArrayOutputStream os = new ByteArrayOutputStream();  //利用ImageIO類提供的write方法,將bi以png圖片的數(shù)據(jù)模式寫入流  ImageIO.write(oriCopyImage, "png", os);  //從流中獲取數(shù)據(jù)數(shù)組  return os.toByteArray(); }   /**  * 根據(jù)模板圖片摳圖  * @param oriImage 原始圖片  * @param templateImage 模板圖片  * @return 扣了圖片之后的原始圖片  */ private static BufferedImage dealCutPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage,               BufferedImage targetImage) throws Exception {  // 源文件圖像矩陣  int[][] oriImageData = getData(oriImage);  // 模板圖像矩陣  int[][] templateImageData = getData(templateImage);  // 模板圖像寬度   for (int i = 0; i < templateImageData.length; i++) {   // 模板圖片高度   for (int j = 0; j < templateImageData[0].length; j++) {    // 如果模板圖像當(dāng)前像素點(diǎn)不是白色 copy源文件信息到目標(biāo)圖片中    int rgb = templateImageData[i][j];    if (rgb != 16777215 && rgb <= 0) {     targetImage.setRGB(i, j, oriImageData[i][j]);    }   }  }  return targetImage; }  /**  * 獲取目標(biāo)區(qū)域  * @param x   隨機(jī)切圖坐標(biāo)x軸位置  * @param y   隨機(jī)切圖坐標(biāo)y軸位置  * @param targetWidth 切圖后目標(biāo)寬度  * @param targetHeight 切圖后目標(biāo)高度  * @param ois   源文件輸入流  * @return 返回目標(biāo)區(qū)域  * @throws Exception 異常  */ private static BufferedImage getTargetArea(int x, int y, int targetWidth, int targetHeight, InputStream ois,            String fileType) throws Exception {  Iterator<ImageReader> imageReaderList = ImageIO.getImageReadersByFormatName(fileType);  ImageReader imageReader = imageReaderList.next();  // 獲取圖片流  ImageInputStream iis = ImageIO.createImageInputStream(ois);  // 輸入源中的圖像將只按順序讀取  imageReader.setInput(iis, true);   ImageReadParam param = imageReader.getDefaultReadParam();  Rectangle rec = new Rectangle(x, y, targetWidth, targetHeight);  param.setSourceRegion(rec);  return imageReader.read(0, param); }  /**  * 生成圖像矩陣  * @param bufferedImage 圖片流  * @return 圖像矩陣  */ private static int[][] getData(BufferedImage bufferedImage){  int[][] data = new int[bufferedImage.getWidth()][bufferedImage.getHeight()];  for (int i = 0; i < bufferedImage.getWidth(); i++) {   for (int j = 0; j < bufferedImage.getHeight(); j++) {    data[i][j] = bufferedImage.getRGB(i, j);   }  }  return data; }  /**  * 隨機(jī)生成摳圖坐標(biāo)  */ private static void generateCutoutCoordinates() {  Random random = new Random();  // ORI_WIDTH:590 ORI_HEIGHT:360  // WIDTH:93 HEIGHT:360  int widthDifference = ORI_WIDTH - WIDTH;  int heightDifference = ORI_HEIGHT - HEIGHT;   if (widthDifference <= 0) {   X = 5;  } else {   X = random.nextInt(ORI_WIDTH - 3*WIDTH) + 2*WIDTH + 5;  }   if (heightDifference <= 0) {   Y = 5;  } else {   Y = random.nextInt(ORI_HEIGHT - HEIGHT) + 5;  }   NumberFormat numberFormat = NumberFormat.getInstance();  numberFormat.setMaximumFractionDigits(2); }}

有了工具類,就可以開(kāi)始生成圖片內(nèi)容了,我這邊直接在Spring的控制器生成內(nèi)容并返回

@RequestMapping("createImgValidate")@ResponseBody public Message createImgValidate(SmsVerificationCodeVo vo){  try {   Integer templateNum = new Random().nextInt(4) + 1;   Integer targetNum = new Random().nextInt(20) + 1;   File templateFile = ResourceUtils.getFile("classpath:static/images/validate/template/"+templateNum+".png");   File targetFile = ResourceUtils.getFile("classpath:static/images/validate/target/"+targetNum+".jpg");   Map<String, byte[]> pictureMap = VerifyImageUtil.pictureTemplatesCut(templateFile, targetFile,     ConstString.IMAGE_TYPE_PNG,ConstString.IMAGE_TYPE_JPG);   // 將生成的偏移位置信息設(shè)置到redis中   String key = ConstString.WEB_VALID_IMAGE_PREFIX + vo.getTelephone();   boolean verified = redisUtil.exists(key);   if(verified){    redisUtil.del(key);   }   redisUtil.set(key,(VerifyImageUtil.getX()+67)+"",SmsUtil.VALID_IMAGE_TIMEOUT);   return ResponseUtil.success(pictureMap);  } catch (Exception e) {   e.printStackTrace();   return ResponseUtil.info(ResponseEnum.BUSINESS_ERROR);  } }

基本的邏輯是從靜態(tài)資源中隨機(jī)加載一張target圖片和一張template圖片,放到圖片處理工具中,處理并返回我們需要的兩張圖片,生成圖片以后,就可以直接返回這個(gè)Map了,它會(huì)以base64的方式返回到瀏覽器端。在這里,偏移的位置信息屬于敏感內(nèi)容,它會(huì)參與前臺(tái)傳入偏移量的對(duì)比校驗(yàn),所以我這里存到了redis中,返回的內(nèi)容也就是Map,只不過(guò)我用了一個(gè)自定義的返回輔助方法(有興趣的人也可以找我要這些輔助工具)。

二、前端展示圖片

首先還是需要在Spring Boot對(duì)應(yīng)的控制器中,加入生成視圖的代碼(我做圖片滑動(dòng)驗(yàn)證主要為了在發(fā)送手機(jī)驗(yàn)證碼之前做校驗(yàn),所以有一個(gè)手機(jī)號(hào)的參數(shù))。

/**  * 跳轉(zhuǎn)到圖片驗(yàn)證界面  * @return 圖片驗(yàn)證界面 */ @RequestMapping("imgValidate") public String toImgValidate(ModelMap map, String telephone){  map.addAttribute("telephone",telephone);  return "component/imageValidate"; }

之后便是我們的HTML頁(yè)碼代碼:imageValidate.html

<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>圖片驗(yàn)證</title> <link rel="stylesheet" type="text/css" th:href="@{/static/css/bootstrap.min.css}" /> <link rel="stylesheet" type="text/css" th:href="@{/static/fontawesome/css/all.css}" /> <link rel="stylesheet" type="text/css" th:href="@{/static/css/public.css}" /> <link rel="stylesheet" type="text/css" th:href="@{/static/css/component/imageValidate.css}" /></head><body> <p id="container">  <p class="imageDiv">   <img id="validateImage" src=""/>   <img id="slideImage" src=""/>  </p>  <p class="resultDiv">   <button class="btn btn-success" onclick="exchange();"><i class="fas fa-redo"></i>換一組</button>   <span id="operateResult"></span>  </p>  <p>   <p id="sliderOuter">    <p id="dragDiv">拖動(dòng)滑塊完成拼圖</p>    <p id="sliderInner">     <i class="fas fa-angle-double-right"></i>     <p class="coverIcon"></p>    </p>   </p>  </p> </p> </body><script th:inline="javascript"> var telephone = [[${telephone}]];</script><script type="text/javascript" th:src="@{/static/js/jquery-3.4.0.min.js}" ></script><script type="text/javascript" th:src="@{/static/js/component/imageValidate.js}" ></script></html>

然后是對(duì)應(yīng)的JS邏輯代碼:imageValidate.js。之前說(shuō)過(guò)后臺(tái)返回的圖片是轉(zhuǎn)成base64了的,所以我們?cè)谏蓤D片的時(shí)候,直接在img標(biāo)簽的src內(nèi)容前加入data:image/png;base64,即可,注意又一個(gè)英文逗號(hào)。

var left = 0; $(function(){ // 初始化圖片驗(yàn)證碼 initImageValidate();  /* 初始化按鈕拖動(dòng)事件 */ // 鼠標(biāo)點(diǎn)擊事件 $("#sliderInner").mousedown(function(){  // 鼠標(biāo)移動(dòng)事件  document.onmousemove = function(ev) {   left = ev.clientX;   if(left >= 67 && left <= 563){    $("#sliderInner").css("left",(left-67)+"px");    $("#slideImage").css("left",(left-67)+"px");   }  };  // 鼠標(biāo)松開(kāi)事件  document.onmouseup=function(){   document.onmousemove=null;   checkImageValidate();  }; }); }); function initImageValidate(){ $.ajax({  async : false,  type : "POST",  url : "/common/createImgValidate",  dataType: "json",  data:{   telephone:telephone  },  success : function(data) {   if(data.status < 400){    // 設(shè)置圖片的src屬性    $("#validateImage").attr("src", "data:image/png;base64,"+data.data.oriCopyImage);    $("#slideImage").attr("src", "data:image/png;base64,"+data.data.newImage);   }else{    layer.open({     icon:2,     title: "溫馨提示",     content: data.info    });   }  },  error : function() {} });} function exchange(){ initImageValidate();} // 校驗(yàn)function checkImageValidate(){ $.ajax({  async : false,  type : "POST",  url : "/common/checkImgValidate",  dataType: "json",  data:{   telephone:telephone,   offsetHorizontal:left  },  success : function(data) {   if(data.status < 400){    $("#operateResult").html(data.info).css("color","#28a745");    // 校驗(yàn)通過(guò),調(diào)用發(fā)送短信的函數(shù)    parent.getValidateCode(left);   }else{    $("#operateResult").html(data.info).css("color","#dc3545");    // 驗(yàn)證未通過(guò),將按鈕和拼圖恢復(fù)至原位置    $("#sliderInner").animate({"left":"0px"},200);    $("#slideImage").animate({"left":"0px"},200);   }  },  error : function() {} });}

最后是css樣式代碼:imageValidate.css

body{ overflow: hidden;}#container{ width: 100%;}.fontDiv{ margin: 16px 0;}.dragFont{ font-size: 16px; color: dodgerblue;}.imageDiv{ width: 590px; height: 360px; margin: 20px auto 0 auto; position: relative;}.resultDiv{ margin: 10px 20px;}#validateImage{ border-radius: 4px;}#slideImage{ position: absolute; top: 5px; left: 0;}#sliderOuter{ width: 590px; height: 40px; margin: 12px auto; border-radius: 20px; box-shadow: 0 0 10px 5px darkgrey; display: flex; align-items: center; justify-content: center; position: relative;}#dragDiv{ width: 100%; height: 40px; position: absolute; font-size: 16px; color: dodgerblue; text-align: center; line-height: 40px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;}#sliderInner{ width: 94px; height: 40px; border-radius: 20px; font-size: 2rem; background-color: #28a745; cursor: pointer; position: absolute; left: 0;}#sliderInner i{ position: relative; top: -2px; left: 36px; color: white;}.coverIcon{ width: 100%; height: 100%; position: absolute; top: 0;}

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI