it-swarm.com.ru

Как сжать изображение через Javascript в браузере?

TL; DR;

Есть ли способ сжать изображение (в основном, JPEG, PNG и GIF) непосредственно на стороне браузера, прежде чем загружать его? Я уверен, что JavaScript может сделать это, но я не могу найти способ достичь этого.


Вот полный сценарий, который я хотел бы реализовать:

  • пользователь заходит на мой сайт и выбирает изображение с помощью элемента input type="file",
  • это изображение получено с помощью JavaScript, мы проводим некоторую проверку, такую ​​как правильный формат файла, максимальный размер файла и т. д.,
  • если все в порядке, предварительный просмотр изображения отображается на странице,
  • пользователь может выполнять некоторые базовые операции, например поворачивать изображение на 90 °/-90 °, обрезать его в соответствии с заранее заданным соотношением и т. д., или пользователь может загрузить другое изображение и вернуться к шагу 1,
  • когда пользователь удовлетворен, отредактированное изображение затем сжимается и «сохраняется» локально (не сохраняется в файл, но в память/страницу браузера), -
  • пользователь заполняет форму с данными, такими как имя, возраст и т. д.,
  • пользователь нажимает кнопку «Готово», после чего на сервер отправляется форма, содержащая данные + сжатое изображение (без AJAX),

Полный процесс до последнего шага должен выполняться клиентом и должен быть совместим с последними версиями Chrome и Firefox, Safari 5+ и IE 8+. Если возможно, следует использовать только JavaScript (но я уверен, что это невозможно).

Я сейчас ничего не кодирую, но уже думал об этом. Локальное чтение файла возможно через File API , предварительный просмотр и редактирование изображения можно выполнить с помощью элемента Canvas , но я не могу найти способ выполнить сжатие изображения ,.

Согласно html5please.com и caniuse.com , поддержка этих браузеров довольно сложна (благодаря IE), но это можно сделать с помощью полифилла, такого как FlashCanvas и FileReader .

На самом деле цель состоит в том, чтобы уменьшить размер файла, поэтому я рассматриваю сжатие изображений как решение. Но я знаю, что загруженные изображения будут отображаться на моем веб-сайте каждый раз в одном и том же месте, и я знаю размер этой области отображения (например, 200x400). Таким образом, я мог изменить размер изображения, чтобы соответствовать этим размерам, таким образом уменьшая размер файла. Я понятия не имею, какой будет степень сжатия для этой техники.

Как вы думаете ? Есть ли у вас какие-либо советы, чтобы сказать мне? Знаете ли вы какой-либо способ сжатия изображения на стороне браузера в JavaScript? Спасибо за ваши ответы.

58
pomeh

Короче:

  • Прочитайте файлы, используя HTML5 FileReader API с .readAsArrayBuffer
  • Создайте e Blob с данными файла и получите его URL с помощью window.URL.createObjectURL (blob)
  • Создайте новый элемент Image и установите его src в URL файла blob 
  • Отправьте изображение на холст. Размер холста установлен на желаемый выходной размер
  • Получить уменьшенные данные обратно с холста через canvas.toDataURL ("image/jpeg", 0.7) (установить свой собственный формат вывода и качество)
  • Прикрепите новые скрытые входные данные к исходной форме и передайте изображения dataURI в основном как обычный текст
  • На сервере прочитайте dataURI, декодируйте из Base64 и сохраните его

Источник: код .

108
psychowood

Ответ @PsychoWoods хороший. Я хотел бы предложить свое собственное решение. Эта функция Javascript берет URL-адрес данных изображения и ширину, масштабирует его до новой ширины и возвращает новый URL-адрес данных.

// Take an image URL, downscale it to the given width, and return a new image URL.
function downscaleImage(dataUrl, newWidth, imageType, imageArguments) {
    "use strict";
    var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;

    // Provide default values
    imageType = imageType || "image/jpeg";
    imageArguments = imageArguments || 0.7;

    // Create a temporary image so that we can compute the height of the downscaled image.
    image = new Image();
    image.src = dataUrl;
    oldWidth = image.width;
    oldHeight = image.height;
    newHeight = Math.floor(oldHeight / oldWidth * newWidth)

    // Create a temporary canvas to draw the downscaled image on.
    canvas = document.createElement("canvas");
    canvas.width = newWidth;
    canvas.height = newHeight;

    // Draw the downscaled image on the canvas and return the new data URL.
    ctx = canvas.getContext("2d");
    ctx.drawImage(image, 0, 0, newWidth, newHeight);
    newDataUrl = canvas.toDataURL(imageType, imageArguments);
    return newDataUrl;
}

Этот код можно использовать везде, где у вас есть URL-адрес данных и вы хотите URL-адрес данных для уменьшенного изображения.

11
Daniel Allen Langdon

Я вижу две вещи, отсутствующие в других ответах:

  • canvas.toBlob (если доступно) более производительный, чем canvas.toDataURL, а также асинхронный.
  • файл -> изображение -> холст -> преобразование файла теряет данные EXIF; в частности, данные о повороте изображения обычно задаются современными телефонами/планшетами.

Следующий скрипт имеет дело с обоими пунктами:

// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari:
if (!HTMLCanvasElement.prototype.toBlob) {
    Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
        value: function(callback, type, quality) {

            var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
                len = binStr.length,
                arr = new Uint8Array(len);

            for (var i = 0; i < len; i++) {
                arr[i] = binStr.charCodeAt(i);
            }

            callback(new Blob([arr], {type: type || 'image/png'}));
        }
    });
}

window.URL = window.URL || window.webkitURL;

// Modified from https://stackoverflow.com/a/32490603, cc by-sa 3.0
// -2 = not jpeg, -1 = no data, 1..8 = orientations
function getExifOrientation(file, callback) {
    // Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/:
    if (file.slice) {
        file = file.slice(0, 131072);
    } else if (file.webkitSlice) {
        file = file.webkitSlice(0, 131072);
    }

    var reader = new FileReader();
    reader.onload = function(e) {
        var view = new DataView(e.target.result);
        if (view.getUint16(0, false) != 0xFFD8) {
            callback(-2);
            return;
        }
        var length = view.byteLength, offset = 2;
        while (offset < length) {
            var marker = view.getUint16(offset, false);
            offset += 2;
            if (marker == 0xFFE1) {
                if (view.getUint32(offset += 2, false) != 0x45786966) {
                    callback(-1);
                    return;
                }
                var little = view.getUint16(offset += 6, false) == 0x4949;
                offset += view.getUint32(offset + 4, little);
                var tags = view.getUint16(offset, little);
                offset += 2;
                for (var i = 0; i < tags; i++)
                    if (view.getUint16(offset + (i * 12), little) == 0x0112) {
                        callback(view.getUint16(offset + (i * 12) + 8, little));
                        return;
                    }
            }
            else if ((marker & 0xFF00) != 0xFF00) break;
            else offset += view.getUint16(offset, false);
        }
        callback(-1);
    };
    reader.readAsArrayBuffer(file);
}

// Derived from https://stackoverflow.com/a/40867559, cc by-sa
function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) {
    var canvas = document.createElement('canvas');
    if (orientation > 4) {
        canvas.width = rawHeight;
        canvas.height = rawWidth;
    } else {
        canvas.width = rawWidth;
        canvas.height = rawHeight;
    }

    if (orientation > 1) {
        console.log("EXIF orientation = " + orientation + ", rotating picture");
    }

    var ctx = canvas.getContext('2d');
    switch (orientation) {
        case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break;
        case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break;
        case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break;
        case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
        case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break;
        case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break;
        case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break;
    }
    ctx.drawImage(img, 0, 0, rawWidth, rawHeight);
    return canvas;
}

function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) {
    if (file.size <= acceptFileSize) {
        callback(file);
        return;
    }
    var img = new Image();
    img.onerror = function() {
        URL.revokeObjectURL(this.src);
        callback(file);
    };
    img.onload = function() {
        URL.revokeObjectURL(this.src);
        getExifOrientation(file, function(orientation) {
            var w = img.width, h = img.height;
            var scale = (orientation > 4 ?
                Math.min(maxHeight / w, maxWidth / h, 1) :
                Math.min(maxWidth / w, maxHeight / h, 1));
            h = Math.round(h * scale);
            w = Math.round(w * scale);

            var canvas = imgToCanvasWithOrientation(img, w, h, orientation);
            canvas.toBlob(function(blob) {
                console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB");
                callback(blob);
            }, 'image/jpeg', quality);
        });
    };
    img.src = URL.createObjectURL(file);
}

Пример использования:

inputfile.onchange = function() {
    // If file size > 500kB, resize such that width <= 1000, quality = 0.9
    reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => {
        let body = new FormData();
        body.set('file', blob, blob.name || "file.jpg");
        fetch('/upload-image', {method: 'POST', body}).then(...);
    });
};
9
Simon Lindholm

Правка: Согласно комментарию Mr Me к этому ответу, похоже, что сжатие теперь доступно для форматов JPG/WebP (см. https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/ toDataURL ).

Насколько я знаю, вы не можете сжимать изображения, используя canvas, вместо этого вы можете изменить его размер. Использование canvas.toDataURL не позволит вам выбрать степень сжатия для использования. Вы можете взглянуть на canimage, который делает именно то, что вы хотите: https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js

На самом деле, часто достаточно просто изменить размер изображения, чтобы уменьшить его размер, но если вы хотите пойти дальше, вам придется использовать недавно введенный метод file.readAsArrayBuffer, чтобы получить буфер, содержащий данные изображения.

Затем просто используйте DataView для чтения его содержимого в соответствии со спецификацией формата изображения ( http://en.wikipedia.org/wiki/JPEG или http://en.wikipedia.org/wiki/Portable_Network_Graphics ).

Будет сложно справиться со сжатием данных изображений, но попытка хуже. С другой стороны, вы можете попытаться удалить заголовки PNG или exif-данные JPEG, чтобы сделать ваше изображение меньше, это должно быть легче сделать.

Вам нужно будет создать еще один DataWiew в другом буфере и заполнить его содержимым отфильтрованного изображения. Затем вам просто нужно кодировать содержимое вашего изображения в DataURI с помощью window.btoa.

Дайте мне знать, если вы реализуете что-то подобное, будет интересно пройтись по коду.

3
nfroidure

У меня была проблема с функцией downscaleImage(), опубликованной выше @ daniel-allen-langdon в том, что свойства image.width и image.height не доступны сразу, потому что загрузка изображения - асинхронная .

Пожалуйста, посмотрите обновленный пример TypeScript ниже, который учитывает это, использует функции async и изменяет размер изображения, основываясь на самом длинном измерении, а не только на ширине

function getImage(dataUrl: string): Promise<HTMLImageElement> 
{
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.src = dataUrl;
        image.onload = () => {
            resolve(image);
        };
        image.onerror = (el: any, err: ErrorEvent) => {
            reject(err.error);
        };
    });
}

export async function downscaleImage(
        dataUrl: string,  
        imageType: string,  // e.g. 'image/jpeg'
        resolution: number,  // max width/height in pixels
        quality: number   // e.g. 0.9 = 90% quality
    ): Promise<string> {

    // Create a temporary image so that we can compute the height of the image.
    const image = await getImage(dataUrl);
    const oldWidth = image.naturalWidth;
    const oldHeight = image.naturalHeight;
    console.log('dims', oldWidth, oldHeight);

    const longestDimension = oldWidth > oldHeight ? 'width' : 'height';
    const currentRes = longestDimension == 'width' ? oldWidth : oldHeight;
    console.log('longest dim', longestDimension, currentRes);

    if (currentRes > resolution) {
        console.log('need to resize...');

        // Calculate new dimensions
        const newSize = longestDimension == 'width'
            ? Math.floor(oldHeight / oldWidth * resolution)
            : Math.floor(oldWidth / oldHeight * resolution);
        const newWidth = longestDimension == 'width' ? resolution : newSize;
        const newHeight = longestDimension == 'height' ? resolution : newSize;
        console.log('new width / height', newWidth, newHeight);

        // Create a temporary canvas to draw the downscaled image on.
        const canvas = document.createElement('canvas');
        canvas.width = newWidth;
        canvas.height = newHeight;

        // Draw the downscaled image on the canvas and return the new data URL.
        const ctx = canvas.getContext('2d')!;
        ctx.drawImage(image, 0, 0, newWidth, newHeight);
        const newDataUrl = canvas.toDataURL(imageType, quality);
        return newDataUrl;
    }
    else {
        return dataUrl;
    }

}
1
Russell Briggs

Вы можете взглянуть на преобразование изображения , попробуйте здесь -> демонстрационная страница

 enter image description here

1
王玉略

Для сжатия изображений JPG вы можете использовать лучший метод сжатия, называемый JIC (Сжатие изображений Javascript). Это вам определенно поможет -> https://github.com/brunobar79/J-I-C

0
Aravind Bhat K