上传前使用 HTML5 调整图像大小

问题描述 投票:0回答:6

我在 stackoverflow 上找到了一些不同的帖子甚至问题来回答这个问题。我基本上正在实现与这篇文章相同的事情。

这是我的问题。当我上传照片时,我还需要提交表格的其余部分。这是我的 html:

<form id="uploadImageForm" enctype="multipart/form-data">
  <input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
  <input id="name" value="#{name}" />
  ... a few more inputs ... 
</form>

以前,我不需要调整图像大小,所以我的 javascript 看起来像这样:

window.uploadPhotos = function(url){
    var data = new FormData($("form[id*='uploadImageForm']")[0]);

    $.ajax({
        url: url,
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function(data){
            ... handle error...
            }
        }
    });
};

这一切都很好......现在我需要调整图像大小......我如何替换表单中的图像,以便发布调整大小的图像而不是上传的图像?

window.uploadPhotos = function(url){

    var resizedImage;

    // Read in file
    var file = event.target.files[0];

    // Ensure it's an image
    if(file.type.match(/image.*/)) {
        console.log('An image has been loaded');

        // Load the image
        var reader = new FileReader();
        reader.onload = function (readerEvent) {
            var image = new Image();
            image.onload = function (imageEvent) {

                // Resize the image
                var canvas = document.createElement('canvas'),
                    max_size = 1200,
                    width = image.width,
                    height = image.height;
                if (width > height) {
                    if (width > max_size) {
                        height *= max_size / width;
                        width = max_size;
                    }
                } else {
                    if (height > max_size) {
                        width *= max_size / height;
                        height = max_size;
                    }
                }
                canvas.width = width;
                canvas.height = height;
                canvas.getContext('2d').drawImage(image, 0, 0, width, height);
                resizedImage = canvas.toDataURL('image/jpeg');
            }
            image.src = readerEvent.target.result;
        }
        reader.readAsDataURL(file);
    }


   // TODO: Need some logic here to switch out which photo is being posted...

    var data = new FormData($("form[id*='uploadImageForm']")[0]);

    $.ajax({
        url: url,
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function(data){
            ... handle error...
            }
        }
    });
};

我考虑过将文件输入移出表单,并在表单中设置一个隐藏输入,将其值设置为调整大小的图像的值...但我想知道是否可以替换图像这已经在表格中了。

javascript html canvas
6个回答
232
投票

这就是我最终所做的,效果很好。

首先,我将文件输入移到表单之外,以便不提交:

<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<form id="uploadImageForm" enctype="multipart/form-data">
    <input id="name" value="#{name}" />
    ... a few more inputs ... 
</form>

然后我更改了

uploadPhotos
函数以仅处理调整大小:

window.uploadPhotos = function(url){
    // Read in file
    var file = event.target.files[0];

    // Ensure it's an image
    if(file.type.match(/image.*/)) {
        console.log('An image has been loaded');

        // Load the image
        var reader = new FileReader();
        reader.onload = function (readerEvent) {
            var image = new Image();
            image.onload = function (imageEvent) {

                // Resize the image
                var canvas = document.createElement('canvas'),
                    max_size = 544,// TODO : pull max size from a site config
                    width = image.width,
                    height = image.height;
                if (width > height) {
                    if (width > max_size) {
                        height *= max_size / width;
                        width = max_size;
                    }
                } else {
                    if (height > max_size) {
                        width *= max_size / height;
                        height = max_size;
                    }
                }
                canvas.width = width;
                canvas.height = height;
                canvas.getContext('2d').drawImage(image, 0, 0, width, height);
                var dataUrl = canvas.toDataURL('image/jpeg');
                var resizedImage = dataURLToBlob(dataUrl);
                $.event.trigger({
                    type: "imageResized",
                    blob: resizedImage,
                    url: dataUrl
                });
            }
            image.src = readerEvent.target.result;
        }
        reader.readAsDataURL(file);
    }
};

如您所见,我使用

canvas.toDataURL('image/jpeg');
将调整大小的图像更改为 dataUrl,然后调用函数
dataURLToBlob(dataUrl);
将 dataUrl 转换为 blob,然后将其附加到表单中。创建 blob 后,我会触发一个自定义事件。这是创建 blob 的函数:

/* Utility function to convert a canvas to a BLOB */
var dataURLToBlob = function(dataURL) {
    var BASE64_MARKER = ';base64,';
    if (dataURL.indexOf(BASE64_MARKER) == -1) {
        var parts = dataURL.split(',');
        var contentType = parts[0].split(':')[1];
        var raw = parts[1];

        return new Blob([raw], {type: contentType});
    }

    var parts = dataURL.split(BASE64_MARKER);
    var contentType = parts[0].split(':')[1];
    var raw = window.atob(parts[1]);
    var rawLength = raw.length;

    var uInt8Array = new Uint8Array(rawLength);

    for (var i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
    }

    return new Blob([uInt8Array], {type: contentType});
}
/* End Utility function to convert a canvas to a BLOB      */

最后,这是我的事件处理程序,它从自定义事件中获取 blob,附加表单,然后提交它。

/* Handle image resized events */
$(document).on("imageResized", function (event) {
    var data = new FormData($("form[id*='uploadImageForm']")[0]);
    if (event.blob && event.url) {
        data.append('image_data', event.blob);

        $.ajax({
            url: event.url,
            data: data,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',
            success: function(data){
               //handle errors...
            }
        });
    }
});

91
投票

如果有兴趣,我制作了打字稿版本:

interface IResizeImageOptions {
  maxSize: number;
  file: File;
}
const resizeImage = (settings: IResizeImageOptions) => {
  const file = settings.file;
  const maxSize = settings.maxSize;
  const reader = new FileReader();
  const image = new Image();
  const canvas = document.createElement('canvas');
  const dataURItoBlob = (dataURI: string) => {
    const bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
        atob(dataURI.split(',')[1]) :
        unescape(dataURI.split(',')[1]);
    const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
    const max = bytes.length;
    const ia = new Uint8Array(max);
    for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i);
    return new Blob([ia], {type:mime});
  };
  const resize = () => {
    let width = image.width;
    let height = image.height;

    if (width > height) {
        if (width > maxSize) {
            height *= maxSize / width;
            width = maxSize;
        }
    } else {
        if (height > maxSize) {
            width *= maxSize / height;
            height = maxSize;
        }
    }

    canvas.width = width;
    canvas.height = height;
    canvas.getContext('2d').drawImage(image, 0, 0, width, height);
    let dataUrl = canvas.toDataURL('image/jpeg');
    return dataURItoBlob(dataUrl);
  };

  return new Promise((ok, no) => {
      if (!file.type.match(/image.*/)) {
        no(new Error("Not an image"));
        return;
      }

      reader.onload = (readerEvent: any) => {
        image.onload = () => ok(resize());
        image.src = readerEvent.target.result;
      };
      reader.readAsDataURL(file);
  })    
};

这是 javascript 结果:

var resizeImage = function (settings) {
    var file = settings.file;
    var maxSize = settings.maxSize;
    var reader = new FileReader();
    var image = new Image();
    var canvas = document.createElement('canvas');
    var dataURItoBlob = function (dataURI) {
        var bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
            atob(dataURI.split(',')[1]) :
            unescape(dataURI.split(',')[1]);
        var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
        var max = bytes.length;
        var ia = new Uint8Array(max);
        for (var i = 0; i < max; i++)
            ia[i] = bytes.charCodeAt(i);
        return new Blob([ia], { type: mime });
    };
    var resize = function () {
        var width = image.width;
        var height = image.height;
        if (width > height) {
            if (width > maxSize) {
                height *= maxSize / width;
                width = maxSize;
            }
        } else {
            if (height > maxSize) {
                width *= maxSize / height;
                height = maxSize;
            }
        }
        canvas.width = width;
        canvas.height = height;
        canvas.getContext('2d').drawImage(image, 0, 0, width, height);
        var dataUrl = canvas.toDataURL('image/jpeg');
        return dataURItoBlob(dataUrl);
    };
    return new Promise(function (ok, no) {
        if (!file.type.match(/image.*/)) {
            no(new Error("Not an image"));
            return;
        }
        reader.onload = function (readerEvent) {
            image.onload = function () { return ok(resize()); };
            image.src = readerEvent.target.result;
        };
        reader.readAsDataURL(file);
    });
};

用法如下:

resizeImage({
    file: $image.files[0],
    maxSize: 500
}).then(function (resizedImage) {
    console.log("upload resized image")
}).catch(function (err) {
    console.error(err);
});

或(

async
/
await
):

const config = {
    file: $image.files[0],
    maxSize: 500
};
const resizedImage = await resizeImage(config)
console.log("upload resized image")

12
投票

2022 年,我们将提供一些新的 API。这是我想出的解决方案。我们不必搞乱 FileReader API 或图像加载回调。

以下代码接受文件对象或 Blob 对象,并输出经过裁剪、居中、调整大小的图像的 blob,并将其转换为 webp。


export default async (file, size) => {
  size ??= 256

  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')

  canvas.width = size
  canvas.height = size

  const bitmap = await createImageBitmap(file)
  const { width, height } = bitmap

  const ratio = Math.max(size / width, size / height)

  const x = (size - (width * ratio)) / 2
  const y = (size - (height * ratio)) / 2

  ctx.drawImage(bitmap, 0, 0, width, height, x, y, width * ratio, height * ratio)

  return new Promise(resolve => {
    canvas.toBlob(blob => {
      resolve(blob)
    }, 'image/webp', 1)
  })
}

10
投票

如果你们中的一些人像我一样遇到方向问题,我已将此处的解决方案与 exif 方向修复结合起来

https://gist.github.com/SagiMedina/f00a57de4e211456225d3114fd10b0d0

import EXIF from 'exif-js';

const hasBlobConstructor = typeof (Blob) !== 'undefined' && (function checkBlobConstructor() {
    try {
        return Boolean(new Blob());
    } catch (error) {
        return false;
    }
}());

const hasArrayBufferViewSupport = hasBlobConstructor && typeof (Uint8Array) !== 'undefined' && (function checkArrayBufferView() {
    try {
        return new Blob([new Uint8Array(100)]).size === 100;
    } catch (error) {
        return false;
    }
}());

const hasToBlobSupport = (typeof HTMLCanvasElement !== 'undefined' ? HTMLCanvasElement.prototype.toBlob : false);

const hasBlobSupport = (hasToBlobSupport || (typeof Uint8Array !== 'undefined' && typeof ArrayBuffer !== 'undefined' && typeof atob !== 'undefined'));

const hasReaderSupport = (typeof FileReader !== 'undefined' || typeof URL !== 'undefined');

const hasCanvasSupport = (typeof HTMLCanvasElement !== 'undefined');

export default class ImageTools {

    constructor() {
        this.browserSupport = this.isSupportedByBrowser();
    }

    isSupportedByBrowser = () => (hasCanvasSupport && hasBlobSupport && hasReaderSupport);

    resize = (file, maxDimensions) => new Promise((resolve) => {

        if (!this.browserSupport || !file.type.match(/image.*/)) return resolve(file);  // early exit - not supported

        if (file.type.match(/image\/gif/)) return resolve(file); // early exit - could be an animated gif

        const image = document.createElement('img');

        image.onload = () => {
            let width  = image.width;
            let height = image.height;

            if (width >= height && width > maxDimensions.width) {
                height *= maxDimensions.width / width;
                width = maxDimensions.width;
            } else if (height > maxDimensions.height) {
                width *= maxDimensions.height / height;
                height = maxDimensions.height;
            } else return resolve(file); // early exit; no need to resize

            EXIF.getData(image, () => {
                const orientation = EXIF.getTag(image, 'Orientation');
                const imageCanvas = this.drawImageToCanvas(image, orientation, 0, 0, width, height, 'contain');
                if (hasToBlobSupport) imageCanvas.toBlob(blob => resolve(blob), file.type);
                else resolve(this.toBlob(imageCanvas, file.type));
            });
        };

        this.loadImage(image, file);

        return true;
    });

    crop = (file, dimensions) => new Promise((resolve) => {

        if (!this.browserSupport || !file.type.match(/image.*/)) return resolve(file); // early exit - not supported

        if (file.type.match(/image\/gif/)) return resolve(file); // early exit - could be an animated gif

        const image = document.createElement('img');

        image.onload = () => {

            if (dimensions.width > image.width && dimensions.height > image.height) return resolve(file); // early exit - no need to resize

            const width = Math.min(dimensions.width, image.width);
            const height = Math.min(dimensions.height, image.height);

            if (image.width > dimensions.width * 2 || image.height > dimensions.height * 2) {
                return this.resize(file, { width: dimensions.width * 2, height: dimensions.height * 2 }).then((zoomedOutImage) => {
                    this.crop(zoomedOutImage, { width, height }).then(resolve);
                });
            }

            EXIF.getData(image, () => {
                const orientation = EXIF.getTag(image, 'Orientation');
                const imageCanvas = this.drawImageToCanvas(image, orientation, 0, 0, width, height, 'crop');
                if (hasToBlobSupport) imageCanvas.toBlob(blob => resolve(blob), file.type);
                else resolve(this.toBlob(imageCanvas, file.type));
            });
        };

        this.loadImage(image, file);

        return true;
    });

    drawImageToCanvas = (img, orientation = 1, x = 0, y = 0, width = img.width, height = img.height, method = 'contain') => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');

        canvas.width = width;
        canvas.height = height;

        ctx.save();
        switch (Number(orientation)) {
            // explained here: https://i.stack.imgur.com/6cJTP.gif
            case 1:
                break;

            case 2:
                ctx.translate(width, 0);
                ctx.scale(-1, 1);
                break;

            case 3:
                ctx.translate(width, height);
                ctx.rotate((180 / 180) * Math.PI); // 180/180 is 1? No shit, but how else will you know its need 180 rotation?
                break;

            case 4:
                ctx.translate(0, height);
                ctx.scale(1, -1);
                break;

            case 5:
                canvas.width = height;
                canvas.height = width;
                ctx.rotate((90 / 180) * Math.PI);
                ctx.scale(1, -1);
                break;

            case 6:
                canvas.width = height;
                canvas.height = width;
                ctx.rotate((90 / 180) * Math.PI);
                ctx.translate(0, -height);
                break;

            case 7:
                canvas.width = height;
                canvas.height = width;
                ctx.rotate((270 / 180) * Math.PI);
                ctx.translate(-width, height);
                ctx.scale(1, -1);
                break;

            case 8:
                canvas.width = height;
                canvas.height = width;
                ctx.translate(0, width);
                ctx.rotate((270 / 180) * Math.PI);
                break;

            default:
                break;
        }

        if (method === 'crop') ctx.drawImage(img, (img.width / 2) - (width / 2), (img.height / 2) - (height / 2), width, height, 0, 0, width, height);
        else ctx.drawImage(img, x, y, width, height);
        ctx.restore();

        return canvas;
    };

    toBlob = (canvas, type) => {
        const dataURI = canvas.toDataURL(type);
        const dataURIParts = dataURI.split(',');
        let byteString;
        if (dataURIParts[0].indexOf('base64') >= 0) {
            byteString = atob(dataURIParts[1]);
        } else {
            byteString = decodeURIComponent(dataURIParts[1]);
        }
        const arrayBuffer = new ArrayBuffer(byteString.length);
        const intArray = new Uint8Array(arrayBuffer);

        for (let i = 0; i < byteString.length; i += 1) {
            intArray[i] = byteString.charCodeAt(i);
        }

        const mimeString = dataURIParts[0].split(':')[1].split(';')[0];
        let blob = null;

        if (hasBlobConstructor) {
            blob = new Blob([hasArrayBufferViewSupport ? intArray : arrayBuffer], { type: mimeString });
        } else {
            const bb = new BlobBuilder();
            bb.append(arrayBuffer);
            blob = bb.getBlob(mimeString);
        }

        return blob;
    };

    loadImage = (image, file) => {
        if (typeof (URL) === 'undefined') {
            const reader = new FileReader();
            reader.onload = (event) => {
                image.src = event.target.result;
            };
            reader.readAsDataURL(file);
        } else {
            image.src = URL.createObjectURL(file);
        }
    };

}

4
投票

我制作了自己的版本,但没有实际使用文件阅读器。相反,我使用大多数现代浏览器都支持的

createObjectUrl

/**
 * Function scaling an image from a file input to specified dimensions
 * If the specified dimensions are not proportional to image dimensions the output image will be cropped at center
 *
 * @param file {File} Input file of a form
 * @param dimensions {{width: number, height: number}} Dimenstions of the output image
 * @returns {Promise<Blob | null>} Promise resolving to a scale image or a null if provided an invalid file type
 */
export async function scaleImageBeforeUpload(file: File, dimensions: {width: number, height: number}): Promise<Blob | null> {
    // ensure the file is an image
    if (!file.type.match(/image.*/)) return null;

    const image = new Image();
    image.src = URL.createObjectURL(file);

    await new Promise<Event>((res) => image.onload = res);
    const canvas = document.createElement("canvas");
    const context = canvas.getContext("2d", {alpha: true});

    canvas.width = dimensions.width;
    canvas.height = dimensions.height;

    if (image.height <= image.width) {
        const scaleProportions = canvas.height / image.height;
        const scaledWidth = scaleProportions * image.width;
        context.drawImage(image, (canvas.width - scaledWidth)/2, 0, scaledWidth, canvas.height);
    }
    else {
        const scaleProportions = canvas.width / image.width;
        const scaledHeight = scaleProportions * image.height;
        context.drawImage(image, 0, (canvas.height - scaledHeight)/2, canvas.width, scaledHeight);
    }

    return new Promise((res) => canvas.toBlob(res));
}

0
投票

世界上最低效的调整图像大小以适应最大文件大小的方法

    function processImage(base64) {
        return new Promise(function(resolve, reject) {
            var img = new Image();
            img.onload = function() {
                var canvas = document.createElement('canvas');
                var ctx = canvas.getContext('2d');
                var width = img.width;
                var height = img.height;
                var resizedBase64 = null;
                while (resizedBase64 == null) {
                    console.log("width: " + width + " height: " + height);
                    canvas.width = width;
                    canvas.height = height;
                    ctx.drawImage(img, 0, 0, width, height);
                    if (canvas.toDataURL('image/png').length > maxFileSize) {
                        width = width * 0.9;
                        height = height * 0.9;
                    } else {
                        resizedBase64 = canvas.toDataURL('image/png')
                    }
                }
                console.log("width: " + width + " height: " + height);
                resolve(resizedBase64);
            };
            img.src = base64;
        });
    }
© www.soinside.com 2019 - 2024. All rights reserved.