[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调用Activex控件的事件的实现方法
Apr 11 Javascript
jQuery不间断滚动效果(模拟百度新闻支持文字/图片/垂直滚动)
Feb 05 Javascript
基于JavaScript 声明全局变量的三种方式详解
May 07 Javascript
浅谈javascript对象模型和function对象
Dec 26 Javascript
深入解析JavaScript中函数的Currying柯里化
Mar 19 Javascript
JavaScript 动态三角函数实例详解
Jan 08 Javascript
bootstrap table使用入门基本用法
May 24 Javascript
jQuery实现可兼容IE6的淡入淡出效果告警提示功能示例
Sep 20 jQuery
node使用Koa2搭建web项目的方法
Oct 17 Javascript
实例教学如何写vue插件
Nov 30 Javascript
Vue组件间数据传递的方式(3种)
Jul 13 Javascript
Node.js利用Express实现用户注册登陆功能(推荐)
Oct 26 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 抓取网页图片并且另存为的实现代码
2010/03/24 PHP
PHP中对各种加密算法、Hash算法的速度测试对比代码
2014/07/08 PHP
php下pdo的mysql事务处理用法实例
2014/12/27 PHP
PHP简单实现解析xml为数组的方法
2018/05/02 PHP
jquery简单的拖动效果实现原理及示例
2013/07/26 Javascript
浅析JavaScript原型继承的陷阱
2013/12/03 Javascript
jquery.validate.js插件使用经验记录
2014/07/02 Javascript
jQuery Ajax()方法使用指南
2014/11/19 Javascript
jQuery提示效果代码分享
2014/11/20 Javascript
node.js中的fs.truncate方法使用说明
2014/12/15 Javascript
jQuery插件制作之参数用法实例分析
2015/06/01 Javascript
JavaScript实现的经典文件树菜单效果
2015/09/08 Javascript
jQuery中hover与mouseover和mouseout的区别分析
2015/12/24 Javascript
JSON与js对象序列化实例详解
2017/03/16 Javascript
微信页面弹出键盘后iframe内容变空白的解决方案
2017/09/20 Javascript
使用Vue完成一个简单的todolist的方法
2017/12/01 Javascript
jQuery实现右侧抽屉式在线客服功能
2017/12/25 jQuery
学习JS中的DOM节点以及操作
2018/04/30 Javascript
Node.js中package.json中库的版本号(~和^)
2019/04/02 Javascript
微信小程序将页面按钮悬浮固定在底部的实现代码
2020/10/29 Javascript
[03:51]吞吞映像 每周精彩击杀top10第二弹
2014/06/25 DOTA
[01:52]深扒TI7聊天轮盘语音出处7
2017/05/11 DOTA
python简单判断序列是否为空的方法
2015/06/30 Python
Python中print和return的作用及区别解析
2019/05/05 Python
python使用time、datetime返回工作日列表实例代码
2019/05/09 Python
Python获取一个用户名的组ID过程解析
2019/09/03 Python
selenium如何定位span元素的实现
2021/01/13 Python
canvas基础之图形验证码的示例
2018/01/02 HTML / CSS
浅谈利用缓存来优化HTML5 Canvas程序的性能
2015/05/12 HTML / CSS
Html5获取高德地图定位天气的方法
2019/12/26 HTML / CSS
连卡佛中国官网:Lane Crawford中文站
2018/01/27 全球购物
英国知名小木屋定制网站:Tiger Sheds
2020/03/06 全球购物
机电专业毕业生求职信
2014/07/01 职场文书
卫生院义诊活动总结
2015/05/07 职场文书
2015社区健康教育工作总结
2015/05/20 职场文书
简单且有用的Python数据分析和机器学习代码
2021/07/02 Python