[Bootstrap-插件使用]Jcrop+fileinput组合实现头像上传功能实例代码


Posted in Javascript onDecember 20, 2016

很久没有更新博客了,再不写点东西都烂了。

这次更新一个小内容,是两个插件的组合使用,实现头像上传功能。

业务需求:

  • 头像上传功能,要对上传的文件进行剪切,且保证头像到服务器时必须是正方形的。
  • 优化<input type="file">的显示样式,基础的样式实在太难看了。
  • 上传的头像需要进行质量压缩跟大小裁剪,以减缓浏览器的压力。

成果预览:

[Bootstrap-插件使用]Jcrop+fileinput组合实现头像上传功能实例代码

使用到的技术插件

  • Jcrop:用于前端“裁剪”图片
  • bootstrap-fileinput:用于前端优化上传控件样式
  • ARTtemplate:JS版的JSTL?反正就是一个腾讯的模板化插件,很好用,真心。
  • bootstrap-sco.modal.js:这个是bootstrap的一个模态插件
  • SpringMVC:使用框架自带的MultipartFile来获取文件(效率能够大大的提高)
  • Image:这个是Java的内置类,用于处理图片很方便。

原理说明

首先是Jcrop这个前端JS插件,这个插件很好用,其实在各大网站中也很常见,先上图:

[Bootstrap-插件使用]Jcrop+fileinput组合实现头像上传功能实例代码

说说原理,实际上,Jcrop并没有在客户端帮我们把图片进行裁剪,只是收集了用户的“裁剪信息”,然后传到后端,最后的裁剪和压缩,还是要依靠服务器上的代码来进行。

我们可以看到这个插件在图片上显示出了8个控制点,让用户选择裁剪区域,当用户选择成功后,会自动的返回“裁剪信息”,所谓的裁剪信息,其实就是选框的左上角原点坐标,及裁剪框的宽度和高度,通过这四个值,在后端就可以进行裁剪工作了。

但是我们要注意,用户在上传图片的时候,长度宽度都是不规则的,当然我们可以用bootstap-fileinput这个插件去限制用户只能上传指定宽高的图片,但这就失去了我们“裁剪”的意义,而且用户的体验就非常差劲。然而jcrop所返回的坐标值及宽高,并不是基于所上传图片自身的像素,而是如图中所示,是外层DIV的宽高。举一个例子,上图我实际放入的个人照片宽度是852px,但是Jcrop的截取宽度是312px,这个312px并不是真正图片上的实际宽度,是经过缩放后的宽度,所以我们后端一定需要重新对这个312px进行一次还原,还原到照片实际比例的宽度。

好啦,原理就是这样子。接下来,就是上代码了。

HTML

<script id="portraitUpload" type="text/html">
  <div style="padding: 10px 20px">
    <form role="form" enctype="multipart/form-data" method="post">
      <div class="embed-responsive embed-responsive-16by9">
        <div class="embed-responsive-item pre-scrollable">
          <img alt="" src="${pageContext.request.contextPath}/img/showings.jpg" id="cut-img"
             class="img-responsive img-thumbnail"/>
        </div>
      </div>
      <div class="white-divider-md"></div>
      <input type="file" name="imgFile" id="fileUpload"/>
      <div class="white-divider-md"></div>
      <div id="alert" class="alert alert-danger hidden" role="alert"></div>
      <input type="hidden" id="x" name="x"/>
      <input type="hidden" id="y" name="y"/>
      <input type="hidden" id="w" name="w"/>
      <input type="hidden" id="h" name="h"/>
    </form>
  </div>
</script>

这个就是一个ArtTemplate的模板代码,就写在</body>标签上方就行了,因为text/html这个类型,不会被识别,所以实际上用Chrome调试就可以看得到,前端用户是看不到这段代码的。

简单解释一下这个模板,这个模板是我最后放入模态窗口时用的模板,就是把这段代码,直接丢进模态弹出来的内容部分。因为是文件上传,自然需要套一个<form>标签,然后必须给form标签放入 enctype="multipart/form-data",否则后端Spring就无法获取这个文件。

"embed-responsive embed-responsive-16by9"这个类就是用来限制待编辑图片加载后的宽度大小,值得注意的是,我在其内种,加了一个

<div class="embed-responsive-item pre-scrollable">

pre-scrollable这个类,会让加载的图片不会因为太大而“变形”,因为我外层通过embed-responsive-16by9限制死了图片的宽高,图片本身又加了img-responsive这个添加响应式属性的类,为了防止图片缩放,导致截图障碍,所以就给内层加上pre-scrollable,这个会给图片这一层div加上滚动条,如果图片高度太高,超过了外层框,则会出现滚动条,而这不会影响图片截取,同时又保证了模态窗口不会“太长”,导致体验糟糕(尤其在移动端)。

底下四个隐藏域相信大家看他们的name值也就知道个大概,这个就是用于存放Jcrop截取时所产生的原点坐标和截取宽高的值。

JS

$(document).ready(function () {
  new PageInit().init();
});


function PageInit() {
  var api = null;
  var _this = this;
  this.init = function () {
    $("[name='upload']").on('click', this.portraitUpload)
  };


  this.portraitUpload = function () {
    var model = $.scojs_modal({
        title: '头像上传',
        content: template('portraitUpload'),
        onClose: refresh
      }
    );
    model.show();
    var fileUp = new FileUpload();
    var portrait = $('#fileUpload');
    var alert = $('#alert');
    fileUp.portrait(portrait, '/file/portrait', _this.getExtraData);
    portrait.on('change', _this.readURL);
    portrait.on('fileuploaderror', function (event, data, msg) {
      alert.removeClass('hidden').html(msg);
      fileUp.fileinput('disable');
    });
    portrait.on('fileclear', function (event) {
      alert.addClass('hidden').html();
    });
    portrait.on('fileloaded', function (event, file, previewId, index, reader) {
      alert.addClass('hidden').html();
    });
    portrait.on('fileuploaded', function (event, data) {
      if (!data.response.status) {
        alert.html(data.response.message).removeClass('hidden');
      }
    })
  };

  this.readURL = function () {
    var img = $('#cut-img');
    var input = $('#fileUpload');
    if (input[0].files && input[0].files[0]) {
      var reader = new FileReader();
      reader.readAsDataURL(input[0].files[0]);
      reader.onload = function (e) {
        img.removeAttr('src');
        img.attr('src', e.target.result);
        img.Jcrop({
          setSelect: [20, 20, 200, 200],
          handleSize: 10,
          aspectRatio: 1,
          onSelect: updateCords
        }, function () {
          api = this;
        });
      };
      if (api != undefined) {
        api.destroy();
      }
    }
    function updateCords(obj) {
      $("#x").val(obj.x);
      $("#y").val(obj.y);
      $("#w").val(obj.w);
      $("#h").val(obj.h);
    }
  };

  this.getExtraData = function () {
    return {
      sw: $('.jcrop-holder').css('width'),
      sh: $('.jcrop-holder').css('height'),
      x: $('#x').val(),
      y: $('#y').val(),
      w: $('#w').val(),
      h: $('#h').val()
    }
  }
}

这个JS是上传页面的相关逻辑。会JS的人都看得懂它的意义,我就简单说一下几个事件的意义:

portrait.on('fileuploaderror', function (event, data, msg) {
       alert.removeClass('hidden').html(msg);
       fileUp.fileinput('disable');
     });

这个事件,是用于bootstrap-fileinput插件在校验文件格式、文件大小等的时候,如果不符合我们的要求,则会对前面HTML代码中有一个

<div id="alert" class="alert alert-danger hidden" role="alert"></div>

进行一些错误信息的显示操作。

portrait.on('fileclear', function (event) {
       alert.addClass('hidden').html();
     });

这部分代码,是当文件移除时,隐藏错误信息提示区,以及清空内容,当然这是符合我们的业务逻辑的。

portrait.on('fileloaded', function (event, file, previewId, index, reader) {
       alert.addClass('hidden').html();
     });

这部分代码是当选择文件时(此时还没进行文件校验),隐藏错误信息,清空错误内容,这么做是为了应对如果上一次文件校验时有错误,而重新选择文件时,肯定要清空上一次的错误信息,再显示本次的错误信息。

portrait.on('fileuploaded', function (event, data) {
       if (!data.response.status) {
         alert.html(data.response.message).removeClass('hidden');
       }
     })

这部分是当文件上传后,后端如果返回了错误信息,则需要进行相关的提示信息处理。

this.getExtraData = function () {
    return {
      sw: $('.jcrop-holder').css('width'),
      sh: $('.jcrop-holder').css('height'),
      x: $('#x').val(),
      y: $('#y').val(),
      w: $('#w').val(),
      h: $('#h').val()
    }
  }

这部分代码是获取上传文件时,附带需要发往后端的参数,这里面可以看到,x、y自然是Jcrop截取时,选框的左上角原点坐标,w、h自然就是截取的宽高,但是刚才我说了,这个是经过缩放后的宽高,不是依据图片实际像素的宽高。而sw、sh代表的是scaleWidth、scaleHeight,就是缩放宽高的意思。这个.jcrop-holder的对象是当Jcrop插件启用后,加载的图片外层容器的对象,只需要获取这个对象的宽高,就是图片被压缩的宽高,但是因为我限制了图片的宽度和高度,宽度的比例是定死的(不是宽高定死,只是比例定死,bootstrap本身就是响应式框架,所以不能单纯的说宽高定死,宽高会随着使用终端的变化而变化),高度是根据宽度保持16:4,可是我又加了pre-scrollable这个类让图片过高时以滚动条的方式不破坏外层容器的高度,所以我们实际能拿来计算缩放比例的,是宽度,而不是高度,但是这里我一起传,万一以后有其他的使用场景,要以高度为准也说不定。

好了,然后我需要贴上bootstrap-fileinput插件的配置代码:

this.portrait = function (target, uploadUrl, data) {
    target.fileinput({
      language: 'zh', //设置语言
      maxFileSize: 2048,//文件最大容量
      uploadExtraData: data,//上传时除了文件以外的其他额外数据
      showPreview: false,//隐藏预览
      uploadAsync: true,//ajax同步
      dropZoneEnabled: false,//是否显示拖拽区域
      uploadUrl: uploadUrl, //上传的地址
      allowedFileExtensions: ['jpg'],//接收的文件后缀
      showUpload: true, //是否显示上传按钮
      showCaption: true,//是否显示标题
      browseClass: "btn btn-primary", //按钮样式
      previewFileIcon: "<i class='glyphicon glyphicon-king'></i>",
      ajaxSettings: {//这个是因为我使用了SpringSecurity框架,有csrf跨域提交防御,所需需要设置这个值
        beforeSend: function (xhr) {
          xhr.setRequestHeader(header, token);
        }
      }
    });
  }

这个代码有写了注释,我就不多解释了。关于Ajax同步,是因为我个人认为,上传文件这个还是做成同步比较好,等文件上传完成后,js代码才能继续执行下去。因为文件上传毕竟是一个耗时的工作,有的逻辑又确实需要当文件上传成功以后才执行,比如刷新页面,所以为了避免出现问题,还是做成同步的比较好。还有就是去掉预览,用过bootstrap-fileinput插件的都知道,这个插件的图片预览功能很强大,甚至可以单独依靠这个插件来制作相册管理。但是因为我们这次要结合Jcrop,所以要割掉这部分功能。

SpringMVC-Controller获取文件

@ResponseBody
  @RequestMapping(value = "/portrait", method = {RequestMethod.POST})
  public JsonResult upload(HttpServletRequest request) throws Exception {
    Integer x = Integer.parseInt(MyStringTools.checkParameter(request.getParameter("x"), "图片截取异常:X!"));
    Integer y = Integer.parseInt(MyStringTools.checkParameter(request.getParameter("y"), "图片截取异常:Y!"));
    Integer w = Integer.parseInt(MyStringTools.checkParameter(request.getParameter("w"), "图片截取异常:W!"));
    Integer h = Integer.parseInt(MyStringTools.checkParameter(request.getParameter("h"), "图片截取异常:H!"));
    String scaleWidthString = MyStringTools.checkParameter(request.getParameter("sw"), "图片截取异常:SW!");
    int swIndex = scaleWidthString.indexOf("px");
    Integer sw = Integer.parseInt(scaleWidthString.substring(0, swIndex));
    String scaleHeightString = MyStringTools.checkParameter(request.getParameter("sh"), "图片截取异常:SH!");
    int shIndex = scaleHeightString.indexOf("px");
    Integer sh = Integer.parseInt(scaleHeightString.substring(0, shIndex));


    //获取用户ID用于指向对应文件夹
    SysUsers sysUsers = HttpTools.getSessionUser(request);
    int userID = sysUsers.getUserId();
    //获取文件路径
    String filePath = FileTools.getPortraitPath(userID);

    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(
        request.getSession().getServletContext());

    String path;
    //检查form中是否有enctype="multipart/form-data"
    if (multipartResolver.isMultipart(request)) {
      //将request变成多部分request
      MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
      //获取multiRequest 中所有的文件名
      Iterator iterator = multiRequest.getFileNames();
      while (iterator.hasNext()) {
        //一次遍历所有文件
        MultipartFile multipartFile = multiRequest.getFile(iterator.next().toString());
        if (multipartFile != null) {
          String[] allowSuffix = {".jpg",".JPG"};
          if (!FileTools.checkSuffix(multipartFile.getOriginalFilename(), allowSuffix)) {
            throw new BusinessException("文件后缀名不符合要求!");
          }
          path = filePath + FileTools.getPortraitFileName(multipartFile.getOriginalFilename());
          //存入硬盘
          multipartFile.transferTo(new File(path));
          //图片截取
          if (FileTools.imgCut(path, x, y, w, h, sw, sh)) {
            CompressTools compressTools = new CompressTools();
            if (compressTools.simpleCompress(new File(path))) {
              return JsonResult.success(FileTools.filePathToSRC(path, FileTools.IMG));
            } else {
              return JsonResult.error("图片压缩失败!请重新上传!");
            }
          } else {
            return JsonResult.error("图片截取失败!请重新上传!");
          }
        }
      }
    }
    return JsonResult.error("图片获取失败!请重新上传!");
  }

Image图片切割

/**
   * 截图工具,根据截取的比例进行缩放裁剪
   *
   * @param path    图片路径
   * @param zoomX    缩放后的X坐标
   * @param zoomY    缩放后的Y坐标
   * @param zoomW    缩放后的截取宽度
   * @param zoomH    缩放后的截取高度
   * @param scaleWidth 缩放后图片的宽度
   * @param scaleHeight 缩放后的图片高度
   * @return 是否成功
   * @throws Exception 任何异常均抛出
   */
  public static boolean imgCut(String path, int zoomX, int zoomY, int zoomW,
                 int zoomH, int scaleWidth, int scaleHeight) throws Exception {
    Image img;
    ImageFilter cropFilter;
    BufferedImage bi = ImageIO.read(new File(path));
    int fileWidth = bi.getWidth();
    int fileHeight = bi.getHeight();
    double scale = (double) fileWidth / (double) scaleWidth;

    double realX = zoomX * scale;
    double realY = zoomY * scale;
    double realW = zoomW * scale;
    double realH = zoomH * scale;

    if (fileWidth >= realW && fileHeight >= realH) {
      Image image = bi.getScaledInstance(fileWidth, fileHeight, Image.SCALE_DEFAULT);
      cropFilter = new CropImageFilter((int) realX, (int) realY, (int) realW, (int) realH);
      img = Toolkit.getDefaultToolkit().createImage(
          new FilteredImageSource(image.getSource(), cropFilter));
      BufferedImage bufferedImage = new BufferedImage((int) realW, (int) realH, BufferedImage.TYPE_INT_RGB);
      Graphics g = bufferedImage.getGraphics();
      g.drawImage(img, 0, 0, null);
      g.dispose();
      //输出文件
      return ImageIO.write(bufferedImage, "JPEG", new File(path));
    } else {
      return true;
    }
  }

缩放比例scale一定要用double,并且宽高也要转换成double后再相除,否则会变成求模运算,这样会降低精度,别小看这里的精度下降,最终的截图效果根据图片的缩放程度,误差可是有可能被放大的很离谱的。

图片压缩

package com.magic.rent.tools;

/**
 * 知识产权声明:本文件自创建起,其内容的知识产权即归属于原作者,任何他人不可擅自复制或模仿.
 * 创建者: wu  创建时间: 2016/12/15
 * 类说明: 缩略图类(通用) 本java类能将jpg、bmp、png、gif图片文件,进行等比或非等比的大小转换。 具体使用方法
 * 更新记录:
 */

import com.magic.rent.exception.custom.BusinessException;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;

public class CompressTools {
  private File file; // 文件对象
  private String inputDir; // 输入图路径
  private String outputDir; // 输出图路径
  private String inputFileName; // 输入图文件名
  private String outputFileName; // 输出图文件名
  private int outputWidth = 100; // 默认输出图片宽
  private int outputHeight = 100; // 默认输出图片高
  private boolean proportion = true; // 是否等比缩放标记(默认为等比缩放)
  private static Logger logger = LoggerFactory.getLogger(CompressTools.class);


  public CompressTools() {
  }

  public CompressTools(boolean proportion) {
    this.proportion = proportion;
  }

  /**
   * 设置输入参数
   *
   * @param inputDir
   * @param inputFileName
   * @return
   */
  public CompressTools setInputInfo(String inputDir, String inputFileName) {
    this.inputDir = inputDir;
    this.inputFileName = inputFileName;
    return this;
  }

  /**
   * 设置输出参数
   *
   * @param outputDir
   * @param outputFileName
   * @param outputHeight
   * @param outputWidth
   * @param proportion
   * @return
   */
  public CompressTools setOutputInfo(String outputDir, String outputFileName, int outputHeight, int outputWidth, boolean proportion) {
    this.outputDir = outputDir;
    this.outputFileName = outputFileName;
    this.outputWidth = outputWidth;
    this.outputHeight = outputHeight;
    this.proportion = proportion;
    return this;
  }


  // 图片处理
  public boolean compress() throws Exception {
    //获得源文件
    file = new File(inputDir);
    if (!file.exists()) {
      throw new BusinessException("文件不存在!");
    }
    Image img = ImageIO.read(file);
    // 判断图片格式是否正确
    if (img.getWidth(null) == -1) {
      System.out.println(" can't read,retry!" + "<BR>");
      return false;
    } else {
      int newWidth;
      int newHeight;
      // 判断是否是等比缩放
      if (this.proportion) {
        // 为等比缩放计算输出的图片宽度及高度
        double rate1 = ((double) img.getWidth(null)) / (double) outputWidth + 0.1;
        double rate2 = ((double) img.getHeight(null)) / (double) outputHeight + 0.1;
        // 根据缩放比率大的进行缩放控制
        double rate = rate1 > rate2 ? rate1 : rate2;
        newWidth = (int) (((double) img.getWidth(null)) / rate);
        newHeight = (int) (((double) img.getHeight(null)) / rate);
      } else {
        newWidth = outputWidth; // 输出的图片宽度
        newHeight = outputHeight; // 输出的图片高度
      }
      long start = System.currentTimeMillis();
      BufferedImage tag = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
      /*
       * Image.SCALE_SMOOTH 的缩略算法 生成缩略图片的平滑度的
       * 优先级比速度高 生成的图片质量比较好 但速度慢
       */
      tag.getGraphics().drawImage(img.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH), 0, 0, null);
      FileOutputStream out = new FileOutputStream(outputDir);

      // JPEGImageEncoder可适用于其他图片类型的转换
      JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
      encoder.encode(tag);
      out.close();
      long time = System.currentTimeMillis() - start;
      logger.info("[输出路径]:" + outputDir + "\t[图片名称]:" + outputFileName + "\t[压缩前大小]:" + getPicSize() + "\t[耗时]:" + time + "毫秒");
      return true;
    }
  }


  /**
   * 简单压缩方法,压缩后图片将直接覆盖源文件
   *
   * @param images
   * @return
   * @throws Exception
   */
  public boolean simpleCompress(File images) throws Exception {
    setInputInfo(images.getPath(), images.getName());
    setOutputInfo(images.getPath(), images.getName(), 300, 300, true);
    return compress();
  }

  /**
   * 获取图片大小,单位KB
   *
   * @return
   */
  private String getPicSize() {
    return file.length() / 1024 + "KB";
  }

  public static void main(String[] args) throws Exception {
    CompressTools compressTools = new CompressTools();
    compressTools.setInputInfo("/Users/wu/Downloads/background.jpg", "background.jpg");
    compressTools.setOutputInfo("/Users/wu/Downloads/background2.jpg", "background2.jpg", 633, 1920, false);
    compressTools.compress();
  }

}

我专门把图片压缩写成了一个类。

其中可以看到一些关于文件路径的方法,其实没有什么特别的,就是截取后缀获取路径之类的,我这边也贴出来吧,免得有些朋友看的云里雾里的。

package com.magic.rent.tools;

import com.magic.rent.exception.custom.BusinessException;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.io.File;
import java.util.ArrayList;

/**
 * 知识产权声明:本文件自创建起,其内容的知识产权即归属于原作者,任何他人不可擅自复制或模仿.
 * 创建者: wu  创建时间: 2016/11/25
 * 类说明:
 * 更新记录:
 */
public class FileTools {

  public static final int IMG = 1;

  /**
   * 获取项目根目录
   *
   * @return 根目录
   */
  public static String getWebRootPath() {
    return System.getProperty("web.root");
  }

  /**
   * 获取头像目录,若不存在则直接创建一个
   *
   * @param userID 用户ID
   * @return
   */
  public static String getPortraitPath(int userID) {
    String realPath = getWebRootPath() + "img/portrait/" + userID + "/";
    File file = new File(realPath);
    //判断文件夹是否存在,不存在则创建一个
    if (!file.exists() || !file.isDirectory()) {
      if (!file.mkdirs()) {
        throw new BusinessException("创建头像文件夹失败!");
      }
    }
    return realPath;
  }

  /**
   * 重命名头像文件
   *
   * @param fileName 文件名
   * @return
   */
  public static String getPortraitFileName(String fileName) {
    // 获取文件后缀
    String suffix = getSuffix(fileName);
    return "portrait" + suffix;
  }

  /**
   * 判断文件后缀是否符合要求
   *
   * @param fileName  文件名
   * @param allowSuffix 允许的后缀集合
   * @return
   * @throws Exception
   */
  public static boolean checkSuffix(String fileName, String[] allowSuffix) throws Exception {
    String fileExtension = getSuffix(fileName);
    boolean flag = false;
    for (String extension : allowSuffix) {
      if (fileExtension.equals(extension)) {
        flag = true;
      }
    }
    return flag;
  }


  public static String getSuffix(String fileName) {
    return fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
  }

  /**
   * 将文件地址转成链接地址
   *
   * @param filePath 文件路径
   * @param fileType 文件类型
   * @return
   */
  public static String filePathToSRC(String filePath, int fileType) {
    String href = "";
    if (null != filePath && !filePath.equals("")) {
      switch (fileType) {
        case IMG:
          if (filePath.contains("/img/")) {
            int index = filePath.indexOf("/img/");
            href = filePath.substring(index);
          } else {
            href = "";
          }
          return href;
      }
    }
    return href;
  }

  /**
   * 获取指定文件或文件路径下的所有文件清单
   *
   * @param fileOrPath 文件或文件路径
   * @return
   */
  public static ArrayList<File> getListFiles(Object fileOrPath) {
    File directory;
    if (fileOrPath instanceof File) {
      directory = (File) fileOrPath;
    } else {
      directory = new File(fileOrPath.toString());
    }

    ArrayList<File> files = new ArrayList<File>();

    if (directory.isFile()) {
      files.add(directory);
      return files;
    } else if (directory.isDirectory()) {
      File[] fileArr = directory.listFiles();
      if (null != fileArr && fileArr.length != 0) {
        for (File fileOne : fileArr) {
          files.addAll(getListFiles(fileOne));
        }
      }
    }

    return files;
  }


  /**
   * 截图工具,根据截取的比例进行缩放裁剪
   *
   * @param path    图片路径
   * @param zoomX    缩放后的X坐标
   * @param zoomY    缩放后的Y坐标
   * @param zoomW    缩放后的截取宽度
   * @param zoomH    缩放后的截取高度
   * @param scaleWidth 缩放后图片的宽度
   * @param scaleHeight 缩放后的图片高度
   * @return 是否成功
   * @throws Exception 任何异常均抛出
   */
  public static boolean imgCut(String path, int zoomX, int zoomY, int zoomW,
                 int zoomH, int scaleWidth, int scaleHeight) throws Exception {
    Image img;
    ImageFilter cropFilter;
    BufferedImage bi = ImageIO.read(new File(path));
    int fileWidth = bi.getWidth();
    int fileHeight = bi.getHeight();
    double scale = (double) fileWidth / (double) scaleWidth;

    double realX = zoomX * scale;
    double realY = zoomY * scale;
    double realW = zoomW * scale;
    double realH = zoomH * scale;

    if (fileWidth >= realW && fileHeight >= realH) {
      Image image = bi.getScaledInstance(fileWidth, fileHeight, Image.SCALE_DEFAULT);
      cropFilter = new CropImageFilter((int) realX, (int) realY, (int) realW, (int) realH);
      img = Toolkit.getDefaultToolkit().createImage(
          new FilteredImageSource(image.getSource(), cropFilter));
      BufferedImage bufferedImage = new BufferedImage((int) realW, (int) realH, BufferedImage.TYPE_INT_RGB);
      Graphics g = bufferedImage.getGraphics();
      g.drawImage(img, 0, 0, null);
      g.dispose();
      //输出文件
      return ImageIO.write(bufferedImage, "JPEG", new File(path));
    } else {
      return true;
    }
  }
}

顺便一提:getWebRootPath这个方法,要生效,必须在Web.xml中做一个配置:

<context-param>
    <param-name>webAppRootKey</param-name>
     <param-value>web.root</param-value>
   </context-param>

否则是无法动态获取项目的本地路径的。这个配置只要跟在Spring配置后面就行了,应该就不会有什么大碍,其实就是获取本地路径然后设置到系统参数当中。

好了,这就是整个插件的功能了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript将相对路径转绝对路径示例
Mar 14 Javascript
JavaScript 开发工具webstrom使用指南
Dec 09 Javascript
AngularJS入门教程之AngularJS指令
Apr 18 Javascript
jQuery的图片轮播插件PgwSlideshow使用详解
Aug 11 Javascript
jQuery实现文字自动横移
Jan 08 Javascript
Angularjs上传图片实例详解
Aug 06 Javascript
深入理解Vue 组件之间传值
Aug 16 Javascript
详解如何在vscode里面调试js和node.js的方法步骤
Dec 24 Javascript
基于vue通用表单解决方案的思考与分析
Mar 16 Javascript
浅析vue插槽和作用域插槽的理解
Apr 22 Javascript
Vue源码解析之数据响应系统的使用
Apr 24 Javascript
解决Layui数据表格显示无数据提示的问题
Nov 14 Javascript
JS简单实现表格排序功能示例
Dec 20 #Javascript
深入了解JavaScript的逻辑运算符(与、或)
Dec 20 #Javascript
js定时器实例分享
Dec 20 #Javascript
BootStrap Table 获取同行不同列元素的方法
Dec 19 #Javascript
Jquery Easyui进度条组件Progress使用详解(8)
Mar 26 #Javascript
详解Jquery的事件操作和文档操作
Dec 19 #Javascript
如何解决jQuery EasyUI 已打开Tab重新加载问题
Dec 19 #Javascript
You might like
php实现图形显示Ip地址的代码及注释
2014/01/20 PHP
php中chdir()函数用法实例
2014/11/13 PHP
PHP分页初探 一个最简单的PHP分页代码的简单实现
2016/06/21 PHP
PHP 5.6.11中CURL模块问题的解决方法
2016/08/08 PHP
基于PHP的登录和注册的功能的实现
2020/08/06 PHP
JQuery 操作Javascript对象和数组的工具函数小结
2010/01/22 Javascript
Javascript Throttle &amp; Debounce应用介绍
2013/03/19 Javascript
js表单处理中单选、多选、选择框值的获取及表单的序列化
2016/03/08 Javascript
jquery分页插件jquery.pagination.js实现无刷新分页
2016/04/01 Javascript
JS中用三种方式实现导航菜单中的二级下拉菜单
2016/10/31 Javascript
jQuery实现字体颜色渐变效果的方法
2017/03/29 jQuery
jQuery中clone()函数实现表单中增加和减少输入项
2017/05/13 jQuery
canvas基础绘制-绚丽倒计时的实例
2017/09/17 Javascript
Vue使用json-server进行后端数据模拟功能
2018/04/17 Javascript
微信小程序 MinUI组件库系列之badge徽章组件示例
2018/08/20 Javascript
详解JavaScript实现动态的轮播图效果
2019/04/29 Javascript
layui监听select变化,以及设置radio选中的方法
2019/09/24 Javascript
Nodejs + sequelize 实现增删改查操作
2020/11/07 NodeJs
ES6学习教程之Promise用法详解
2020/11/22 Javascript
Python 正则表达式(转义问题)
2014/12/15 Python
python3使用PyMysql连接mysql数据库实例
2017/02/07 Python
在Qt5和PyQt5中设置支持高分辨率屏幕自适应的方法
2019/06/18 Python
pyinstaller打包opencv和numpy程序运行错误解决
2019/08/16 Python
用Python生成HTML表格的方法示例
2020/03/06 Python
Django搭建项目实战与避坑细节详解
2020/12/06 Python
车间工艺员岗位职责
2013/12/09 职场文书
创业计划书的主要内容有哪些
2014/01/29 职场文书
学生安全责任书
2014/04/15 职场文书
十佳护士先进事迹
2014/05/08 职场文书
2014教育局对照检查材料思想汇报
2014/09/23 职场文书
工人先进事迹材料
2014/12/26 职场文书
先进工作者个人总结
2015/02/15 职场文书
《围炉夜话》110句人生箴言,精辟有内涵,引人深思
2019/10/23 职场文书
Python通过m3u8文件下载合并ts视频的操作
2021/04/16 Python
使用qt quick-ListView仿微信好友列表和聊天列表的示例代码
2021/06/13 Python
Zabbix6通过ODBC方式监控Oracle 19C的详细过程
2022/09/23 Servers