TL; DR;
Apakah ada cara untuk mengompres gambar (kebanyakan jpeg, png dan gif) secara langsung dari sisi browser, sebelum mengunggahnya? Saya cukup yakin JavaScript dapat melakukan ini, tetapi saya tidak dapat menemukan cara untuk mencapainya.
Berikut skenario lengkap yang ingin saya terapkan:
- pengguna membuka situs web saya, dan memilih gambar melalui
input type="file"
elemen, - gambar ini diambil melalui JavaScript, kami melakukan beberapa verifikasi seperti format file yang benar, ukuran file maksimum dll,
- jika semuanya baik-baik saja, pratinjau gambar ditampilkan di halaman,
- pengguna dapat melakukan beberapa operasi dasar seperti memutar gambar sebesar 90 ° / -90 °, memotongnya mengikuti rasio yang telah ditentukan, dll, atau pengguna dapat mengunggah gambar lain dan kembali ke langkah 1,
- ketika pengguna puas, gambar yang diedit kemudian dikompresi dan "disimpan" secara lokal (tidak disimpan ke file, tetapi di memori / halaman browser), -
- pengguna mengisi formulir dengan data seperti nama, usia dll,
- pengguna mengklik tombol "Selesai", kemudian formulir yang berisi data + gambar terkompresi dikirim ke server (tanpa AJAX),
Proses penuh hingga langkah terakhir harus dilakukan di sisi klien, dan harus kompatibel dengan Chrome dan Firefox, Safari 5+, dan IE 8+ terbaru . Jika memungkinkan, hanya JavaScript yang harus digunakan (tapi saya cukup yakin ini tidak mungkin).
Saya belum membuat kode apa pun sekarang, tetapi saya sudah memikirkannya. Pembacaan file secara lokal dimungkinkan melalui File API , pratinjau dan pengeditan gambar dapat dilakukan menggunakan elemen Canvas , tetapi saya tidak dapat menemukan cara untuk melakukan bagian kompresi gambar .
Menurut html5please.com dan caniuse.com , mendukung browser tersebut cukup sulit (terima kasih kepada IE), tapi bisa dilakukan dengan menggunakan polyfill seperti FlashCanvas dan FileReader .
Sebenarnya tujuannya adalah untuk memperkecil ukuran file, jadi saya melihat kompresi gambar sebagai solusi. Tapi, saya tahu bahwa gambar yang diunggah akan ditampilkan di situs web saya, setiap saat di tempat yang sama, dan saya tahu dimensi area tampilan ini (mis. 200x400). Jadi, saya bisa mengubah ukuran gambar agar sesuai dengan dimensi tersebut, sehingga mengurangi ukuran file. Saya tidak tahu berapa rasio kompresi untuk teknik ini.
Bagaimana menurut anda ? Apakah Anda punya saran untuk memberi tahu saya? Apakah Anda tahu cara mengompresi sisi browser gambar di JavaScript? Terima kasih atas balasan Anda.
canvas.toDataURL("image/jpeg",0.7)
secara efektif mengompresnya, ia menyimpan JPEG dengan kualitas 70 (berbeda dengan default, kualitas 100).URL.createObjectUrl()
tanpa mengubah file menjadi gumpalan; file tersebut dihitung sebagai gumpalan.Saya melihat dua hal yang hilang dari jawaban lain:
canvas.toBlob
(bila tersedia) lebih berkinerja daripadacanvas.toDataURL
, dan juga asinkron.Skrip berikut membahas kedua poin tersebut:
// 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); }
Contoh penggunaan:
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(...); }); };
sumber
Jawaban @PsychoWoods bagus. Saya ingin menawarkan solusi saya sendiri. Fungsi Javascript ini mengambil URL dan lebar data gambar, menskalakannya ke lebar baru, dan mengembalikan URL data baru.
// 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; }
Kode ini dapat digunakan di mana pun Anda memiliki URL data dan menginginkan URL data untuk gambar yang diperkecil.
sumber
Anda dapat melihat konversi gambar , Coba di sini -> halaman demo
sumber
Saya mengalami masalah dengan
downscaleImage()
fungsi yang diposting di atas oleh @ daniel-allen-langdon di mana propertiimage.width
danimage.height
tidak segera tersedia karena pemuatan gambar tidak sinkron .Silakan lihat contoh TypeScript yang diperbarui di bawah ini yang mempertimbangkan hal ini, menggunakan
async
fungsi, dan mengubah ukuran gambar berdasarkan dimensi terpanjang, bukan hanya lebarnyafunction 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; } }
sumber
quality
,resolution
danimageType
(format ini)Sunting: Sesuai komentar Tuan Me pada jawaban ini, sepertinya kompresi sekarang tersedia untuk format JPG / WebP (lihat https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL ) .
Sejauh yang saya tahu, Anda tidak dapat mengompres gambar menggunakan kanvas, sebagai gantinya, Anda dapat mengubah ukurannya. Menggunakan canvas.toDataURL tidak akan membiarkan Anda memilih rasio kompresi yang akan digunakan. Anda dapat melihat canimage yang melakukan apa yang Anda inginkan: https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
Nyatanya, seringkali cukup mengubah ukuran gambar untuk memperkecil ukurannya tetapi jika Anda ingin melangkah lebih jauh, Anda harus menggunakan metode yang baru diperkenalkan file.readAsArrayBuffer untuk mendapatkan buffer yang berisi data gambar.
Kemudian, cukup gunakan DataView untuk membaca kontennya sesuai dengan spesifikasi format gambar ( http://en.wikipedia.org/wiki/JPEG atau http://en.wikipedia.org/wiki/Portable_Network_Graphics ).
Akan sulit untuk menangani kompresi data gambar, tetapi lebih buruk jika dicoba. Di sisi lain, Anda dapat mencoba untuk menghapus header PNG atau data exif JPEG untuk membuat gambar Anda lebih kecil, seharusnya lebih mudah untuk melakukannya.
Anda harus membuat DataWiew lain di buffer lain dan mengisinya dengan konten gambar yang difilter. Kemudian, Anda hanya perlu menyandikan konten gambar Anda ke DataURI menggunakan window.btoa.
Beri tahu saya jika Anda menerapkan sesuatu yang serupa, akan menarik untuk melewati kode.
sumber
Saya menemukan bahwa ada solusi yang lebih sederhana dibandingkan dengan jawaban yang diterima.
Sesuai pertanyaan Anda:
Solusi saya:
Buat blob dengan file secara langsung dengan
URL.createObjectURL(inputFileElement.files[0])
.Sama seperti jawaban yang diterima.
Sama seperti jawaban yang diterima. Perlu disebutkan bahwa, ukuran kanvas diperlukan dan digunakan
img.width
sertaimg.height
untuk mengaturcanvas.width
dancanvas.height
. Tidakimg.clientWidth
.Dapatkan gambar yang diperkecil
canvas.toBlob(callbackfunction(blob){}, 'image/jpeg', 0.5)
. Pengaturan'image/jpg'
tidak berpengaruh.image/png
juga didukung. BuatFile
objek baru di dalamcallbackfunction
tubuh denganlet compressedImageBlob = new File([blob])
.Tambahkan input tersembunyi baru atau kirim melalui javascript . Server tidak perlu memecahkan kode apa pun.
Periksa https://javascript.info/binary untuk semua informasi. Saya mendapatkan solusi setelah membaca bab ini.
Kode:
Kode ini terlihat jauh lebih menakutkan dibandingkan jawaban lainnya ..
Memperbarui:
Seseorang harus memasukkan semuanya ke dalam
img.onload
. Jikacanvas
tidak, tidak akan bisa mendapatkan lebar dan tinggi gambar dengan benar sesuai waktucanvas
yang ditentukan.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
uji kompresi file denganimage/jpeg
set argumen.|0.9| 777KB | |0.8| 383KB | |0.7| 301KB | |0.6| 251KB | |0.5| 219kB |
sumber
Untuk kompresi Gambar JPG Anda dapat menggunakan teknik kompresi terbaik yang disebut JIC (Javascript Image Compression) Ini pasti akan membantu Anda -> https://github.com/brunobar79/JIC
sumber
Saya meningkatkan fungsinya menjadi ini:
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); }); };
penggunaan itu:
Nikmati ;)
sumber