[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 相关文章推荐
extjs 学习笔记(二) Ext.Element类
Oct 13 Javascript
javascript中的变量作用域以及变量提升详细介绍
Oct 24 Javascript
JS实现定时页面弹出类似QQ新闻的提示框
Nov 07 Javascript
JS小功能(checkbox实现全选和全取消)实例代码
Nov 28 Javascript
获取3个数组不重复的值的具体实现
Dec 30 Javascript
Jquery中offset()和position()的区别分析
Feb 05 Javascript
PhantomJS快速入门教程(服务器端的 JavaScript API 的 WebKit)
Aug 06 Javascript
jQuery增加与删除table列的方法
Mar 01 Javascript
Javascript随机标签云代码实例
Jun 21 Javascript
分享JS数组求和与求最大值的方法
Aug 11 Javascript
JavaScript制作简单的框选图表
May 15 Javascript
jquery引入外部CDN 加载失败则引入本地jq库
May 23 jQuery
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写的小东西
2006/12/06 PHP
php中计算未知长度的字符串哪个字符出现的次数最多的代码
2012/08/14 PHP
教你在PHPStorm中配置Xdebug
2015/07/27 PHP
PHP模板引擎Smarty自定义变量调解器用法
2016/04/11 PHP
使用EXT实现无刷新动态调用股票信息
2008/11/01 Javascript
JS检测图片大小的实例
2013/08/21 Javascript
JQuery实现当鼠标停留在某区域3秒后自动执行
2014/09/09 Javascript
JavaScript实现点击单选按钮改变输入框中文本域内容的方法
2015/08/12 Javascript
Bootstrap基本组件学习笔记之列表组(11)
2016/12/07 Javascript
详解前端自动化工具gulp自动添加版本号
2016/12/20 Javascript
JavaScript常见JSON操作实例分析
2018/08/08 Javascript
微信小程序框架wepy之动态控制类名
2018/09/14 Javascript
如何在微信小程序中实现Mixins方案
2019/06/20 Javascript
使用vscode快速建立vue模板过程详解
2019/10/10 Javascript
[08:08]2014DOTA2国际邀请赛中国区预选赛精彩TOPPLAY
2014/06/25 DOTA
Python中的条件判断语句基础学习教程
2016/02/07 Python
python按综合、销量排序抓取100页的淘宝商品列表信息
2018/02/24 Python
对python周期性定时器的示例详解
2019/02/19 Python
PowerBI和Python关于数据分析的对比
2019/07/11 Python
python中count函数简单用法
2020/01/05 Python
Python3连接Mysql8.0遇到的问题及处理步骤
2020/02/17 Python
深入浅析Python 函数注解与匿名函数
2020/02/24 Python
Python自动化测试笔试面试题精选
2020/03/12 Python
Python matplotlib 绘制双Y轴曲线图的示例代码
2020/06/12 Python
Python实现京东抢秒杀功能
2021/01/25 Python
python 装饰器重要在哪
2021/02/14 Python
如何进行有效的自我评价
2013/09/27 职场文书
自主招生自荐信范文
2013/12/04 职场文书
班组长安全生产职责
2013/12/16 职场文书
法律七进实施方案
2014/03/15 职场文书
总经理检讨书
2014/09/15 职场文书
职工擅自离岗检讨书
2014/09/23 职场文书
小学假期安全广播稿
2014/09/28 职场文书
党员转正意见怎么写
2015/06/03 职场文书
MySQL 表空间碎片的概念及相关问题解决
2021/05/07 MySQL
浅谈python数据类型及其操作
2021/05/25 Python