【问题标题】:Sending image manipulated via JS in AJAX POST request在 AJAX POST 请求中发送通过 JS 操作的图像
【发布时间】:2018-07-11 22:57:10
【问题描述】:

我是一名服务器端开发人员,正在学习 vanilla JS 的精髓。我需要澄清关于为我在 JS 中创建的图像对象发送 Ajax POST 请求的概念 - 这个问题就是关于这个的。

想象一个网络应用程序,用户可以在其中上传照片供其他人查看。在每张图片上传时,我使用 vanilla JS 来确认图片的 mime 类型(通过解释 magic numbers),然后调整图片大小以进行优化。

调整大小后,我会这样做:

    var canvas = document.createElement('canvas');
    canvas.width = resized_width;
    canvas.height = resized_height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(source_img, 0, 0, resized_width, resized_height);
    var resized_img = new Image();
    resized_img.src = canvas.toDataURL("image/jpeg",0.7);
    return resized_img;

返回的图像对象必须通过 Ajax 请求发送到后端。比如:

function overwrite_default_submit(e) {
  e.preventDefault();
  var form = new FormData();
  form.append("myfile", resized_img, img_name);
  var xhr = new XMLHttpRequest();
  xhr.open('POST', e.target.action);
//  xhr.send(form); // uncomment to really send the request
}

但是,调整大小后返回的图像对象本质上是一个 HTML 元素,如<img src="data:image/jpeg;base64>。而 FormData 对象中预期的对象应该是 File 对象,例如类似:File { name: "example.jpg", lastModified: 1500117303000, lastModifiedDate: Date 2017-07-15T11:15:03.000Z, webkitRelativePath: "", size: 115711, type: "image/jpeg" }

那么我该怎么做才能解决这个问题呢?宁愿在这里学习最有效的做事方式。

顺便说一句,我在 SO 上看到 an example 使用 JS FILE 对象,但我更喜欢跨浏览器的方法,因为 File 获得了 Safari、Opera Mobile 和内置 Android 浏览器的支持relatively recently

此外,我只需要纯 JS 解决方案,因为我将此作为练习来学习绳索。 JQuery 在我的雷达上,但以后再说。


我的其余代码如下(为简洁起见仅包括JPEG处理):

var max_img_width = 400;
var wranges = [max_img_width, Math.round(0.8*max_img_width), Math.round(0.6*max_img_width),Math.round(0.4*max_img_width),Math.round(0.2*max_img_width)];

// grab the file from the input and process it
function process_user_file(e) {
  var file = e.target.files[0];
  var reader = new FileReader();
  reader.onload = process_image;
  reader.readAsArrayBuffer(file.slice(0,25));
}

// checking file type programmatically (via magic numbers), getting dimensions and returning a compressed image
function process_image(e) {
  var img_width;
  var img_height;
  var view = new Uint8Array(e.target.result);
  var arr = view.subarray(0, 4);
  var header = "";
  for(var i = 0; i < arr.length; i++) {
     header += arr[i].toString(16);
  }
  switch (header) {
    case "ffd8ffe0":
    case "ffd8ffe1":
    case "ffd8ffe2":
    case "ffd8ffe3":
    case "ffd8ffe8":
        // magic numbers represent type = "image/jpeg";
        // use the 'slow' method to get the dimensions of the media
        img_file = browse_image_btn.files[0];
        var fr = new FileReader();
        fr.onload = function(){
          var dataURL = fr.result;
          var img = new Image();
          img.onload = function() {
              img_width = this.width;
              img_height = this.height;
              resized_img = resize_and_compress(this, img_width, img_height, 80);
            }
          img.src = dataURL;
        };
        fr.readAsDataURL(img_file);
        to_send = browse_image_btn.files[0];
        load_rest = true;
        subform.disabled = false;
        break;
    default:
        // type = "unknown"; // Or one can use the blob.type as fallback
        load_rest = false;
        subform.disabled = true;
        browse_image_btn.value = "";
        to_send = null;
        break;
    }
}

// resizing (& compressing) image
function resize_and_compress(source_img, img_width, img_height, quality){
    var new_width;
    switch (true) {
      case img_width < wranges[4]:
         new_width = wranges[4];
         break;
      case img_width < wranges[3]:
         new_width = wranges[4];
         break;
      case img_width < wranges[2]:
         new_width = wranges[3];
         break;
      case img_width < wranges[1]:
         new_width = wranges[2];
         break;
      case img_width < wranges[0]:
         new_width = wranges[1];
         break;
      default:
         new_width = wranges[0];
         break;
    }
    var wpercent = (new_width/img_width);
    var new_height = Math.round(img_height*wpercent);

    var canvas = document.createElement('canvas');
    canvas.width = new_width;
    canvas.height = new_height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(source_img, 0, 0, new_width, new_height);
    console.log(ctx);
    var resized_img = new Image();
    resized_img.src = canvas.toDataURL("image/jpeg",quality/100);
    return resized_img;
}

更新:我正在使用以下人员:

// converting image data uri to a blob object
function dataURItoBlob(dataURI,mime_type) {
  var byteString = atob(dataURI.split(',')[1]);
  var ab = new ArrayBuffer(byteString.length);
  var ia = new Uint8Array(ab);
  for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); }
  return new Blob([ab], { type: mime_type });
}

其中dataURI 参数为canvas.toDataURL(mime_type,quality/100)

【问题讨论】:

    标签: javascript


    【解决方案1】:

    您应该调用 canvas.toBlob() 来获取二进制文件,而不是使用 base64 字符串。

    它是异步的,因此您必须为其添加回调。

    canvas.toBlob(function(blob) {
      resized_img.onload = function() {
        // no longer need to read the blob so it's revoked
        URL.revokeObjectURL(this.url);
      };
    
      // Preview the image using createObjectURL
      resized_img.src = URL.createObjectURL(blob);
    
      // Attach the blob to the FormData
      var form = new FormData();
      form.append("myfile", blob, img_name);
    }, "image/jpeg", 0.7);
    

    【讨论】:

    • 有趣。我没有阅读您的答案,而是编写了一些不同的代码 - 我已将其添加为问题末尾的 update 。您能否看一下并与您的方法进行定性比较?哪一个更好(为什么)?很想知道利弊,如果有的话。
    • base64 的大小约为 3 倍。浏览器中的字符串是 utf16 而不是 utf8,因此它实际上占用了 2 倍以上的内存。更不用说您同时需要 blob 和 base64 字符串。它必须与 base64 进行的转换需要更长的时间。函数dataURItoBlob 使用更多代码。
    • 从浏览器支持的角度来说呢?我读过 canvas.toBlob 并没有在浏览器中得到广泛的支持(例如 Chrome 需要一个 polyfill,src:github.com/blueimp/JavaScript-Canvas-to-Blob
    • Chrome 不需要 polyfill,它在较新的浏览器中完全可以交叉兼容。见这里:developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/…
    • 好吧,我认为问题在于 mobile 浏览器的兼容性,显然需要一个 polyfill 才能使用toDataUrl。我认为完美的解决方案是同时利用您的建议和toDataUrl(以防万一)。我认为这绝对应该是您其他出色答案的一部分。签出:developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/…
    【解决方案2】:

    请参阅此 SO 帖子:How to get base64 encoded data from html image

    我认为您需要调用“canvas.toDataURL()”来获取图像的实际 base64 流。

    var image = canvas.toDataURL();
    

    然后用表单上传:Upload a base64 encoded image using FormData?

    var data = new FormData();
    data.append("image_data", image);
    

    未经测试,但这应该是关于它的。

    【讨论】:

    • 哦,我不知道我直接发布了base64流。我确实有这些数据,让我快速浏览一下,我会尽快回复您。
    • 您可以转换为 Blob:stackoverflow.com/questions/35845623/…
    • 类似canvas.toBlob(canvas.toDataURL(),'image/jpeg', 0.7)?
    • 更新:肯定不能只使用canvas.toDataURL(),我得到TypeError: Argument 2 of FormData.append is not an object。我现在就试试 blob 方法。
    猜你喜欢
    • 2018-04-20
    • 1970-01-01
    • 1970-01-01
    • 2021-08-02
    • 2019-11-24
    • 2020-05-12
    • 2021-11-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多