[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 相关文章推荐
取选中的radio的值
Jan 11 Javascript
JS中动态添加事件(绑定事件)的代码
Jan 09 Javascript
jQuery插件开发全解析
Oct 10 Javascript
js局部刷新页面时间具体实现
Jul 04 Javascript
Javascript中数组sort和reverse用法分析
Dec 30 Javascript
jQuery实现tab选项卡效果的方法
Jul 08 Javascript
jQuery中通过ajax的get()函数读取页面的方法
Feb 29 Javascript
详解BootStrap中Affix控件的使用及保持布局的美观的方法
Jul 08 Javascript
微信小程序 五星评分的实现实例
Aug 04 Javascript
五步轻松实现JavaScript HTML时钟效果
Mar 25 Javascript
详解React项目如何修改打包地址(编译输出文件地址)
Mar 21 Javascript
微信小程序模板消息限制实现无限制主动推送的示例代码
Aug 27 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中文件上传的安全问题
2006/10/09 PHP
PHP爆绝对路径方法收集整理
2012/09/17 PHP
php时间戳格式化显示友好的时间函数分享
2014/10/21 PHP
javascript 控制 html元素 显示/隐藏实现代码
2009/09/01 Javascript
基于JQuery框架的AJAX实例代码
2009/11/03 Javascript
JavaScript 命名空间 使用介绍
2013/08/29 Javascript
angularjs学习笔记之双向数据绑定
2015/09/26 Javascript
JS实现控制文本框的内容
2016/07/10 Javascript
简单谈谈JS数组中的indexOf方法
2016/10/13 Javascript
vue中使用refs定位dom出现undefined的解决方法
2017/12/21 Javascript
微信小程序三级联动选择器使用方法
2020/05/19 Javascript
JS设计模式之访问者模式定义与用法分析
2018/02/05 Javascript
完美解决axios跨域请求出错的问题
2018/02/05 Javascript
Vue 自定义动态组件实例详解
2018/03/28 Javascript
JS 验证码功能的三种实现方式
2018/11/26 Javascript
Python中类型检查的详细介绍
2017/02/13 Python
Python语言描述机器学习之Logistic回归算法
2017/12/21 Python
Python基于pycrypto实现的AES加密和解密算法示例
2018/04/10 Python
python3实现多线程聊天室
2018/12/12 Python
python截取两个单词之间的内容方法
2018/12/25 Python
python实现将文件夹内的每张图片批量分割成多张
2019/07/22 Python
Python使用matplotlib绘制Logistic曲线操作示例
2019/11/28 Python
css3 按钮 利用css3实现超酷下载按钮
2013/03/18 HTML / CSS
纯CSS3发光分享按钮的实现教程
2014/09/06 HTML / CSS
高山背包:High Sierra
2017/11/23 全球购物
Glamest意大利:女性在线奢侈品零售店
2019/04/28 全球购物
环境科学专业个人求职的自我评价
2013/11/28 职场文书
档案接收函范文
2014/01/10 职场文书
大学生家政服务项目创业计划书
2014/01/30 职场文书
学校万圣节活动方案
2014/02/13 职场文书
商场促销活动总结
2014/07/10 职场文书
2014年城管个人工作总结
2014/12/08 职场文书
小学运动会宣传稿
2015/07/23 职场文书
2016年清明节寄语
2015/12/04 职场文书
2019个人年度目标制定攻略!
2019/07/12 职场文书
2019新员工试用期转正申请书3篇
2019/08/13 职场文书