TL; DR;
Czy istnieje sposób na skompresowanie obrazu (głównie jpeg, png i gif) bezpośrednio po stronie przeglądarki przed przesłaniem? Jestem prawie pewien, że JavaScript może to zrobić, ale nie mogę znaleźć sposobu, aby to osiągnąć.
Oto pełny scenariusz, który chciałbym wdrożyć:
- użytkownik przechodzi na moją stronę i wybiera obrazek za pomocą
input type="file"
elementu, - ten obraz jest pobierany przez JavaScript, przeprowadzamy weryfikacje, takie jak poprawny format pliku, maksymalny rozmiar pliku itp.
- jeśli wszystko jest w porządku, na stronie wyświetlany jest podgląd obrazu,
- użytkownik może wykonać kilka podstawowych operacji, takich jak obrót obrazu o 90 ° / -90 °, przycięcie go zgodnie z wcześniej zdefiniowanym współczynnikiem itp. lub może załadować inny obraz i powrócić do kroku 1,
- gdy użytkownik jest usatysfakcjonowany, edytowany obraz jest następnie kompresowany i „zapisywany” lokalnie (nie zapisywany do pliku, ale w pamięci / na stronie przeglądarki), -
- użytkownik wypełnia formularz danymi takimi jak imię i nazwisko, wiek itp.,
- użytkownik klika w przycisk „Zakończ”, po czym na serwer wysyłany jest formularz zawierający dane + skompresowany obraz (bez AJAX),
Cały proces aż do ostatniego kroku powinien być wykonany po stronie klienta i powinien być zgodny z najnowszymi przeglądarkami Chrome i Firefox, Safari 5+ i IE 8+ . Jeśli to możliwe, należy używać tylko JavaScript (ale jestem pewien, że nie jest to możliwe).
W tej chwili nic nie kodowałem, ale już o tym myślałem. Lokalne odczytywanie plików jest możliwe przez File API , podgląd i edycję obrazu można wykonać za pomocą elementu Canvas , ale nie mogę znaleźć sposobu na wykonanie części kompresji obrazu .
Według html5please.com i caniuse.com obsługa tych przeglądarek jest dość trudna (dzięki IE), ale można by to zrobić za pomocą polyfill, takiego jak FlashCanvas i FileReader .
Właściwie celem jest zmniejszenie rozmiaru pliku, więc uważam kompresję obrazu za rozwiązanie. Ale wiem, że wgrane obrazy będą się wyświetlać na mojej stronie za każdym razem w tym samym miejscu i znam wymiary tego obszaru wyświetlania (np. 200x400). Mogłem więc zmienić rozmiar obrazu, aby pasował do tych wymiarów, zmniejszając w ten sposób rozmiar pliku. Nie mam pojęcia, jaki byłby współczynnik kompresji dla tej techniki.
Co myślisz ? Czy masz jakieś rady, które możesz mi powiedzieć? Czy znasz jakiś sposób na skompresowanie obrazu po stronie przeglądarki w JavaScript? Dziękuję za twoje odpowiedzi.
canvas.toDataURL("image/jpeg",0.7)
skutecznie kompresuje, zapisuje JPEG z jakością 70 (w przeciwieństwie do domyślnej jakości 100).URL.createObjectUrl()
pliku bez przekształcania go w obiekt typu blob; plik liczy się jako obiekt blob.W pozostałych odpowiedziach brakuje dwóch rzeczy:
canvas.toBlob
(jeśli jest dostępny) jest bardziej wydajny niżcanvas.toDataURL
, a także asynchroniczny.Poniższy skrypt dotyczy obu punktów:
// 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); }
Przykładowe użycie:
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(...); }); };
źródło
Odpowiedź @PsychoWoods jest dobra. Chciałbym zaproponować własne rozwiązanie. Ta funkcja JavaScript pobiera adres URL danych obrazu i szerokość, skaluje je do nowej szerokości i zwraca nowy adres URL danych.
// 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; }
Ten kod może być używany w dowolnym miejscu, w którym masz adres URL danych i potrzebujesz adresu URL danych dla zmniejszonego obrazu.
źródło
Możesz rzucić okiem na konwersję obrazu , wypróbuj tutaj -> strona demonstracyjna
źródło
Miałem problem z
downscaleImage()
funkcją opublikowaną powyżej przez @ daniel-allen-langdon, ponieważ właściwościimage.width
iimage.height
nie są dostępne natychmiast, ponieważ ładowanie obrazu jest asynchroniczne .Zobacz zaktualizowany przykład TypeScript poniżej, który bierze to pod uwagę, używa
async
funkcji i zmienia rozmiar obrazu na podstawie najdłuższego wymiaru, a nie tylko szerokościfunction 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; } }
źródło
quality
,resolution
iimageType
(format to)Edycja: zgodnie z komentarzem Mr Me do tej odpowiedzi wygląda na to, że kompresja jest teraz dostępna dla formatów JPG / WebP (patrz https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL ) .
O ile wiem, nie można kompresować obrazów za pomocą płótna, zamiast tego można zmienić jego rozmiar. Korzystanie z pliku canvas.toDataURL nie pozwoli Ci wybrać współczynnika kompresji. Możesz rzucić okiem na canimage, który robi dokładnie to, co chcesz: https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
W rzeczywistości często wystarczy zmienić rozmiar obrazu, aby zmniejszyć jego rozmiar, ale jeśli chcesz pójść dalej, będziesz musiał użyć nowo wprowadzonej metody file.readAsArrayBuffer, aby uzyskać bufor zawierający dane obrazu.
Następnie wystarczy użyć DataView, aby odczytać jego zawartość zgodnie ze specyfikacją formatu obrazu ( http://en.wikipedia.org/wiki/JPEG lub http://en.wikipedia.org/wiki/Portable_Network_Graphics ).
Trudno będzie sobie poradzić z kompresją danych obrazu, ale jest to gorsza próba. Z drugiej strony możesz spróbować usunąć nagłówki PNG lub dane JPEG exif, aby zmniejszyć rozmiar obrazu, powinno to być łatwiejsze.
Będziesz musiał utworzyć kolejny DataWiew w innym buforze i wypełnić go przefiltrowaną zawartością obrazu. Następnie wystarczy zakodować zawartość obrazu do DataURI za pomocą window.btoa.
Daj mi znać, jeśli zaimplementujesz coś podobnego, ciekawie będzie przejść przez kod.
źródło
Uważam, że istnieje prostsze rozwiązanie w porównaniu z akceptowaną odpowiedzią.
Jak na twoje pytanie:
Moje rozwiązanie:
Utwórz obiekt BLOB z plikiem bezpośrednio za pomocą
URL.createObjectURL(inputFileElement.files[0])
.Taka sama, jak zaakceptowana odpowiedź.
Taka sama, jak zaakceptowana odpowiedź. Warto wspomnieć, że rozmiar płótna jest niezbędny i używany
img.width
orazimg.height
do ustawieniacanvas.width
icanvas.height
. Nieimg.clientWidth
.Uzyskaj skalowany obraz według
canvas.toBlob(callbackfunction(blob){}, 'image/jpeg', 0.5)
. Ustawienie'image/jpg'
nie ma wpływu.image/png
jest również obsługiwany. Utwórz nowyFile
obiekt wewnątrzcallbackfunction
ciała za pomocąlet compressedImageBlob = new File([blob])
.Dodaj nowe ukryte dane wejściowe lub wyślij przez JavaScript . Serwer nie musi niczego dekodować.
Sprawdź https://javascript.info/binary, aby uzyskać wszystkie informacje. Po przeczytaniu tego rozdziału znalazłem rozwiązanie.
Kod:
Ten kod wygląda znacznie mniej przerażająco niż inne odpowiedzi.
Aktualizacja:
Wszystko trzeba włożyć do środka
img.onload
. W przeciwnym raziecanvas
nie będzie w stanie uzyskać poprawnej szerokości i wysokości obrazu wcanvas
wyznaczonym czasie .function upload(){ var f = fileToUpload.files[0]; var fileName = f.name.split('.')[0]; var img = new Image(); img.src = URL.createObjectURL(f); img.onload = function(){ var canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; var ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); canvas.toBlob(function(blob){ console.info(blob.size); var f2 = new File([blob], fileName + ".jpeg"); var xhr = new XMLHttpRequest(); var form = new FormData(); form.append("fileToUpload", f2); xhr.open("POST", "upload.php"); xhr.send(form); }, 'image/jpeg', 0.5); } }
3.4MB
.png
test kompresji plików zimage/jpeg
ustawionym argumentem.|0.9| 777KB | |0.8| 383KB | |0.7| 301KB | |0.6| 251KB | |0.5| 219kB |
źródło
W przypadku kompresji JPG Image możesz użyć najlepszej techniki kompresji zwanej JIC (Javascript Image Compression) To na pewno Ci pomoże -> https://github.com/brunobar79/JIC
źródło
poprawiłem funkcję głowy, aby była taka:
var minifyImg = function(dataUrl,newWidth,imageType="image/jpeg",resolve,imageArguments=0.7){ var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl; (new Promise(function(resolve){ image = new Image(); image.src = dataUrl; log(image); resolve('Done : '); })).then((d)=>{ oldWidth = image.width; oldHeight = image.height; log([oldWidth,oldHeight]); newHeight = Math.floor(oldHeight / oldWidth * newWidth); log(d+' '+newHeight); canvas = document.createElement("canvas"); canvas.width = newWidth; canvas.height = newHeight; log(canvas); ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, newWidth, newHeight); //log(ctx); newDataUrl = canvas.toDataURL(imageType, imageArguments); resolve(newDataUrl); }); };
wykorzystanie tego:
cieszyć się ;)
źródło