在不使用canvas的情况下,使用JavaScript调整Base-64图像的大小

82

我需要一种在不使用HTML元素的情况下使用JavaScript调整图片大小的方法。

我的移动端HTML应用程序捕获照片,然后将它们转换为base64字符串。最后,在将它们发送到API之前,我想要调整它们的大小。

我正在寻找一种不同且更合适的调整大小方法,而不是使用canvas元素,有什么方法吗?


Canvas不适合处理图像? - user1693593
你可以创建一个离屏画布而不将其插入DOM中。如果有兴趣,我可以举个例子。这至少比仅使用JS编码要快得多,因为画布可以在本地编译的代码中完成此操作。 - user1693593
听起来很棒,肯恩。我对此非常感兴趣! :) - rilar
2
我也很感兴趣,因为Canvas和移动设备有关...内存。你不能加载更大的图像,而“更大”现在实际上是指移动设备相机拍摄的大小,因为现今每个人都喜欢把5000万像素塞进手机里 :) - Tom
汤姆。我从未找到过一个好的解决方案。我通过在PhoneGap相机函数中简单地设置图片大小和质量来解决了这个问题。请查看PhoneGap相机文档。通过图库导入图片时也是可能的。 - rilar
显示剩余2条评论
8个回答

93

避免主要的HTML受到影响的方法是创建一个不在DOM树中的离屏画布。

这将提供一个位图缓冲区和本地编译代码来编码图像数据。这很容易实现:

function imageToDataUri(img, width, height) {

    // create an off-screen canvas
    var canvas = document.createElement('canvas'),
        ctx = canvas.getContext('2d');

    // set its dimension to target size
    canvas.width = width;
    canvas.height = height;

    // draw source image into the off-screen canvas:
    ctx.drawImage(img, 0, 0, width, height);

    // encode image to data-uri with base64 version of compressed image
    return canvas.toDataURL();
}

如果您想生成不同于PNG(默认)的格式,只需指定类型,例如:
return canvas.toDataURL('image/jpeg', quality);  // quality = [0.0, 1.0]

值得注意的是,CORS限制适用于toDataURL()
如果您的应用程序仅提供Base64编码图像(我假设它们是带有Base64数据的数据URI?),那么您需要首先“加载”该图像。
var img = new Image;

img.onload = resizeImage;
img.src = originalDataUriHere;

function resizeImage() {
    var newDataUri = imageToDataUri(this, targetWidth, targetHeight);
    // continue from here...
}

如果源码是纯base-64字符串,只需添加一个头部即可将其转换为数据URI:

function base64ToDataUri(base64) {
    return 'data:image/png;base64,' + base64;
}

只需替换image/png部分为base64字符串所代表的类型(即将其作为可选参数)。


谢谢Ken,我会尝试的! - rilar
4
对我没用。它创建了一个base64字符串,但是它只是透明的。 - boop
1
@Brettetete 如果有任何图像被绘制,它们需要满足CORS要求。 - user1693593
@boop 你必须等待图片加载完成。使用 onload 事件。 - Harun Diluka Heshan

31

Ken的答案是正确的,但他的代码无法运行。我对其进行了一些调整,现在它可以完美地工作了。要调整 Data URI 的大小:

// Takes a data URI and returns the Data URI corresponding to the resized image at the wanted size.
function resizedataURL(datas, wantedWidth, wantedHeight)
    {
        // We create an image to receive the Data URI
        var img = document.createElement('img');

        // When the event "onload" is triggered we can resize the image.
        img.onload = function()
            {        
                // We create a canvas and get its context.
                var canvas = document.createElement('canvas');
                var ctx = canvas.getContext('2d');

                // We set the dimensions at the wanted size.
                canvas.width = wantedWidth;
                canvas.height = wantedHeight;

                // We resize the image with the canvas method drawImage();
                ctx.drawImage(this, 0, 0, wantedWidth, wantedHeight);

                var dataURI = canvas.toDataURL();

                /////////////////////////////////////////
                // Use and treat your Data URI here !! //
                /////////////////////////////////////////
            };

        // We put the Data URI in the image's src attribute
        img.src = datas;
    }
// Use it like that : resizedataURL('yourDataURIHere', 50, 50);

你没有在任何地方使用“datas”。 - funguy
在代码片段底部,将 "img.src = datas;" 翻译为中文。 - Pierrick Martellière
我无法理解这段代码,我应该在“在此处使用和处理您的数据URI”中放什么? - Giox
你的代码运行得非常好,你可以轻松地显示它。通过:var newImage = document.createElement('img'); newImage.src = dataURI; document.getElementById("imgTest").innerHTML = newImage.outerHTML; 这使得它在浏览器中显示。 - ILIAS M. DOLAPO

26
Pierrick Martellière是最好的答案,我只想指出你应该使用异步函数来实现它。一旦这样做,你就可以像这样做些事情:
var newDataUri = await resizedataURL(datas,600,600);

这将在进行下一步之前等待函数的结果。这是编写代码的更清晰的方式。这是来自Pierrick的函数,稍作修改:

// Takes a data URI and returns the Data URI corresponding to the resized image at the wanted size.
function resizedataURL(datas, wantedWidth, wantedHeight){
    return new Promise(async function(resolve,reject){

        // We create an image to receive the Data URI
        var img = document.createElement('img');

        // When the event "onload" is triggered we can resize the image.
        img.onload = function()
        {        
            // We create a canvas and get its context.
            var canvas = document.createElement('canvas');
            var ctx = canvas.getContext('2d');

            // We set the dimensions at the wanted size.
            canvas.width = wantedWidth;
            canvas.height = wantedHeight;

            // We resize the image with the canvas method drawImage();
            ctx.drawImage(this, 0, 0, wantedWidth, wantedHeight);

            var dataURI = canvas.toDataURL();

            // This is the return of the Promise
            resolve(dataURI);
        };

        // We put the Data URI in the image's src attribute
        img.src = datas;

    })
}// Use it like : var newDataURI = await resizedataURL('yourDataURIHere', 50, 50);

如需更多详细信息,请查看 MDN 文档: https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise


8
问题明确指出“不使用画布”,这并不是一个答案。已被点踩。 - Adam Barnes
4
@AdamBarnes 我希望您已经对所有其他答案进行了否定的投票;如果您阅读问题的前两个或三个评论,您可以看到目标是不向DOM中插入任何元素,而是在后端完成。干杯! - user8094363
这个解决方案没有考虑使用手机相机上传的图像,可能会出现方向问题,例如图像可能会被旋转。 - pavloko
1
@pavloko 我编写了一个图像上传工具,可以解析图片的方向标签,旋转它并设置新的方向标签。然后,Google Chrome 升级以开始尊重方向标签。由于那是最后一个受欢迎的浏览器之一添加支持方向的浏览器,这可能不值得努力。我们最终不得不将其删除,因为与浏览器相斗争不值得。我认为方向只有在将由不尊重或支持方向的库在后端处理时才至关重要。 - Chris Baker
@AdamBarnes 对于技术问题的好答案并不总是试图将提问者的预设概念纳入其中。提问者说他们不想要 canvas 元素,但没有说原因。人们可能会认为他们持有错误的观念,即您必须将 canvas 元素添加到 DOM 中,而实际上并非如此。这个回答虽然没有严格涵盖提问者未指定的限制,但仍是个像样的解决方案,不应该遭到负面评价。我不喜欢硬编码方面的东西。它可能不能涵盖提问者的情况,但对于那些可以使用 canvas 元素却以为必须避免它的人来说,这仍然是一个好的解决方案。 - Chris Baker
我建议还要实现 img.onerror = reject; 以在所有情况下都履行承诺。 - Thomas Kekeisen

11
是的,你可以。这些解决方案不仅适用于将图像转换为base64,还适用于调整大小。
  1. 你可以通过jpg-js将js文件转换为图像位图。你可以仅使用这个库来调整大小,但是在将非常大的图像调整为非常小的图像时,质量会非常差。对于高分辨率图像,最好的方法是通过jpg-js将文件转换为位图,然后使用Pica库调整此位图的大小。
  2. 你可以通过jpg-js从文件中获取图像数据(或在画布上绘制图像),然后使用调整大小库pica调整canvasImageData。(适用于高分辨率图像,无画布大小限制)
  3. 你可以使用离屏画布,而无需将画布附加到页面上,并调整图像的大小。这种解决方案速度更快,但对于高分辨率图像(例如6000x6000像素),结果画布可能质量较差,或者为空白,或者浏览器可能因内存限制异常而崩溃。(适用于普通和小型图像)

Jpg-js和Pica完全不使用DOM元素。这些库仅使用图像数据,而不使用DOM元素(画布和图像)。

这种方法适用于浏览器端和Node.js。我只在浏览器端使用它,它是一个能够调整高分辨率图片(6000x6000像素及以上)大小的上传工具。
关于画布,有关尺寸限制,请参阅post

感谢您实际回答问题。我不确定为什么人们会投票支持依赖于画布的答案,尽管避免使用它是问题的关键部分...这似乎是Node.js的唯一解决方案。 - carpiediem
Pica也使用Canvas,不过这并不是所要求的解决方案。 - Pencilcheck
@Pencilcheck,它使用的是位图..但是你可以通过jpg-js创建位图而无需画布,然后将此位图放入pica中。 - Alex Nikulin

1

我的上一个建议(作为Chris)被认为没有回答问题,因为它使用了canvas。因为canvas太明显了。

如果不将图像加载到内存中进行操作,就无法完成此操作。为什么不使用类似jimp的库呢?

import Jimp from "jimp";

async function resizeBase64Image(base64: string, targetWidth: number, targetHeight: number): Promise<string> {
  // Decode the base64 image data and save it to a buffer
  const imageBuffer = Buffer.from(base64, "base64");

  // Use Jimp to load the image from the buffer and resize it
  const image = await Jimp.read(imageBuffer);
  image.resize(targetWidth, targetHeight);

  // Convert the image back to a base64 data URI
  const resizedImageBase64 = await image.getBase64Async(Jimp.MIME_PNG);

  return resizedImageBase64;
}

1
这是唯一对我有效的解决方案!而且,很棒的是它是TypeScript代码 :) - Roberto Rossini

1
我认为这种方法是解决此问题的最佳方式。
function base64Resize(sourceBase64, scale , callBack) {

    const _scale = scale;
    var img = document.createElement('img');
    img.setAttribute("src", sourceBase64);

    img.onload = () => {
        var canvas = document.createElement('canvas');
        canvas.width = img.width * _scale;
        canvas.height = img.height * _scale;

        var ctx = canvas.getContext("2d");
        var cw = canvas.width;
        var ch = canvas.height;
        var maxW = img.width * _scale;
        var maxH = img.height * _scale;

        var iw = img.width;
        var ih = img.height;
        var scl = Math.min((maxW / iw), (maxH / ih));
        var iwScaled = iw * scl;
        var ihScaled = ih * scl;
        canvas.width = iwScaled;
        canvas.height = ihScaled;
        ctx.drawImage(img, 0, 0, iwScaled, ihScaled);
        const newBase64 = canvas.toDataURL("image/jpeg", scl);

        callBack(newBase64);
    }
}

重要的一点是你应该使用img.onload事件。


8
题目说:“不使用画布”。 - Chique
我不太熟悉如何使用回调函数 - 我该怎么用呢? - Michael Fever
@MichaelFever:当你调用函数时,可以将一个函数作为第三个参数附加到函数上,该函数在调整大小完成后被调用。这也可以是内联匿名函数,例如 base64Resize("data:image/png;base64,iVBO...", 0.5, function(resizedImage){ // resizedImage 是调整大小后的 base64 图像变量 });。但这不是必需的。如果你不想要它,从参数列表中删除 callBack 并删除 callBack(newBase64); 行 :) - Prid

0
您可能需要一个调整大小的函数,该函数返回已调整大小的数据URI:
const resizeBase64Image = (base64: string, width: number, height: number): Promise<string> => {
  // Create a canvas element
  const canvas = document.createElement('canvas') as HTMLCanvasElement;

  // Create an image element from the base64 string
  const image = new Image();
  image.src = base64;

  // Return a Promise that resolves when the image has loaded
  return new Promise((resolve, reject) => {
    image.onload = () => {
      // Calculate the aspect ratio of the image
      const aspectRatio = image.width / image.height;

      // Calculate the best fit dimensions for the canvas
      if (width / height > aspectRatio) {
        canvas.width = height * aspectRatio;
        canvas.height = height;
      } else {
        canvas.width = width;
        canvas.height = width / aspectRatio;
      }

      // Draw the image to the canvas
      canvas.getContext('2d')!.drawImage(image, 0, 0, canvas.width, canvas.height);

      // Resolve the Promise with the resized image as a base64 string
      resolve(canvas.toDataURL());
    };

    image.onerror = reject;
  });
};

然后就像这样简单地等待它:

const resizedImage: string = await resizeBase64Image(base64, 100, 100);

3
即使它可能是有效的工作代码片段,你必须注意到问题明确指出“不使用画布”。尝试只添加与帖子创建者需求直接相关的答案。您始终可以搜索可能从此答案受益的问题。 - chri3g91
2
答案包括一种方法,而这个帖子的创建者直接要求不要使用它。 - chri3g91

-3
function resizeImage(base64Str) {

      var img = new Image();
      img.src = base64Str;
      var canvas = document.createElement('canvas');
      var MAX_WIDTH = 400;
      var MAX_HEIGHT = 350;
      var width = img.width;
      var height = img.height;

      if (width > height) {
        if (width > MAX_WIDTH) {
          height *= MAX_WIDTH / width;
          width = MAX_WIDTH;
        }
      } else {
        if (height > MAX_HEIGHT) {
          width *= MAX_HEIGHT / height;
          height = MAX_HEIGHT;
        }
      }
      canvas.width = width;
      canvas.height = height;
      var ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0, width, height);
      return canvas.toDataURL();
}

https://gist.github.com/ORESoftware/ba5d03f3e1826dc15d5ad2bcec37f7bf


3
“不使用画布”是问题标题中的正确表述。 - miken32
3
此外,仅仅是一段代码块的答案质量非常低,很可能会被删除。当回答一个问题时,尤其是一个老问题时,请确保不仅解释你的代码如何回答这个问题,还要解释它如何改进之前九年的答案。 - miken32
虽然这个答案确实使用了画布,而OP明确指出画布是禁止使用的(正如@miken32所指出的),但我必须说,当在谷歌上搜索“调整base64图像”时,这个问题会弹出来,对于所有愿意使用画布的人(包括我自己),这个答案实际上是一个相当好的答案。 - LukasKroess

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接