2012-04-26 8 views
154

Вот лапша-скручиватель.HTML5 Предварительный просмотр перед загрузкой фотографий

Принимая во внимание, что у нас есть локальное хранилище HTML5 и xhr v2, а что нет. Мне было интересно, может ли кто-нибудь найти рабочий пример или даже дать мне ответ «да» или «нет» по этому вопросу:

Возможно ли предварительное размер изображения с использованием нового локального хранилища (или любого другого), чтобы пользователь, который не имеет понятия об изменении размера изображения, может перетащить свое изображение 10 МБ на мой сайт, он изменит его размер с помощью нового localstorage, а THEN загрузит его меньшим размером.

Я знаю, что вы можете сделать это с помощью Flash, Java-апплетов, активных X ... Вопрос в том, можете ли вы сделать с Javascript + Html5.

Ожидание ответа на этот вопрос.

Ta на данный момент.

ответ

153

Да, используйте File API, затем вы можете обрабатывать изображения с помощью canvas element.

This Mozilla Hacks blog post проводит вас через большую часть процесса. Для справки здесь собранный исходный код из поста в блоге:

// from an input element 
var filesToUpload = input.files; 
var file = filesToUpload[0]; 

var img = document.createElement("img"); 
var reader = new FileReader(); 
reader.onload = function(e) {img.src = e.target.result} 
reader.readAsDataURL(file); 

var ctx = canvas.getContext("2d"); 
ctx.drawImage(img, 0, 0); 

var MAX_WIDTH = 800; 
var MAX_HEIGHT = 600; 
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); 

var dataurl = canvas.toDataURL("image/png"); 

//Post dataurl to the server with AJAX 
+4

Почему спасибо сударь. Сегодня вечером у меня будет игра ... С файлом api, который есть. Я получил загрузку перетаскивания на работу, и я понял, что это тоже будет очень хорошая функция для включения. Yippee. – Jimmyt1988

+0

Спасибо, сэр! На мой взгляд, эти две строки не нужны: var ctx = canvas.getContext ("2d"); ctx.drawImage (img, 0, 0); ---- как вы уже получили контекст и drawImage в конце ... – Qlimax

+3

Не нужно использовать этот mycanvas.toDataURL («image/jpeg», 0.5), чтобы убедиться, что качество приведено для уменьшения размер файла. Я наткнулся на этот комментарий на сообщение в блоге mozilla и увидел здесь ошибку. Https://bugzilla.mozilla.org/show_bug.cgi?id=564388 – sbose

8

Если вы не хотите изобретать велосипед, вы можете попробовать plupload.com

+1

Спасибо. Вы пробовали это? Выглядит отлично! – sbose

+3

Я также использую plupload для изменения размера клиентской стороны. Самое лучшее в этом - это использовать flash, html5, silverlight и т. Д. Независимо от того, какой пользователь доступен, потому что у каждого пользователя есть другая настройка. Если вы хотите только html5, то я думаю, что он не будет работать над некоторыми старыми браузерами и Opera. – jnl

20

Исправление выше:

<img src="" id="image"> 
<input id="input" type="file" onchange="handleFiles()"> 
<script> 

function handleFiles() 
{ 
    var filesToUpload = document.getElementById('input').files; 
    var file = filesToUpload[0]; 

    // Create an image 
    var img = document.createElement("img"); 
    // Create a file reader 
    var reader = new FileReader(); 
    // Set the image once loaded into file reader 
    reader.onload = function(e) 
    { 
     img.src = e.target.result; 

     var canvas = document.createElement("canvas"); 
     //var canvas = $("<canvas>", {"id":"testing"})[0]; 
     var ctx = canvas.getContext("2d"); 
     ctx.drawImage(img, 0, 0); 

     var MAX_WIDTH = 400; 
     var MAX_HEIGHT = 300; 
     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); 

     var dataurl = canvas.toDataURL("image/png"); 
     document.getElementById('image').src = dataurl;  
    } 
    // Load files into file reader 
    reader.readAsDataURL(file); 


    // Post the data 
    /* 
    var fd = new FormData(); 
    fd.append("name", "some_filename.jpg"); 
    fd.append("image", dataurl); 
    fd.append("info", "lah_de_dah"); 
    */ 
}</script> 
+2

Как вы обрабатываете часть PHP после добавления в форму FormData()? Вы бы не искали $ _FILES ['name'] ['tmp_name'] [$ i], например? Я пытаюсь, если (isset ($ _ POST ['image'])) ... но 'dataurl' не существует. – denikov

+1

не работает над mozilla firefox – gadss

+1

он не работает на firefox при первой попытке загрузить изображение. В следующий раз, когда вы попробуете, это сработает. Как это исправить? – Fred

90

я столкнулся эта проблема несколько лет назад и загрузила мое решение в github как https://github.com/rossturner/HTML5-ImageUploader

Ответ robertc использует решение, предложенное в Mozilla Hacks blog post, однако я обнаружил, что при изменении размера до шкалы, которая не была 2: 1 (или несколько ее), это дало очень низкое качество изображения. Я начал экспериментировать с различными алгоритмами изменения размера изображения, хотя большинство из них оказались довольно медленными, а также не отличались высоким качеством.

Наконец-то я придумал решение, которое, как я считаю, выполняется быстро и имеет довольно хорошую производительность - поскольку решение для копирования Mozilla с одного холста на другое работает быстро и без потери качества изображения при соотношении 2: 1, данный мишенью х пикселей в ширину и у пикселей в высоту, я использую этот метод изменения размера холста до тех пор, пока изображение не находится между х и 2 х и у и 2 у. Затем я перехожу к алгоритмическому изменению размера изображения для окончательного «шага» изменения размера до целевого размера. После попытки несколько различных алгоритмов я остановился на билинейной интерполяции, взятой из блога, который не находится в сети больше, но accessible via the Internet Archive, что дает хорошие результаты, вот применимый код:

ImageUploader.prototype.scaleImage = function(img, completionCallback) { 
    var canvas = document.createElement('canvas'); 
    canvas.width = img.width; 
    canvas.height = img.height; 
    canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height); 

    while (canvas.width >= (2 * this.config.maxWidth)) { 
     canvas = this.getHalfScaleCanvas(canvas); 
    } 

    if (canvas.width > this.config.maxWidth) { 
     canvas = this.scaleCanvasWithAlgorithm(canvas); 
    } 

    var imageData = canvas.toDataURL('image/jpeg', this.config.quality); 
    this.performUpload(imageData, completionCallback); 
}; 

ImageUploader.prototype.scaleCanvasWithAlgorithm = function(canvas) { 
    var scaledCanvas = document.createElement('canvas'); 

    var scale = this.config.maxWidth/canvas.width; 

    scaledCanvas.width = canvas.width * scale; 
    scaledCanvas.height = canvas.height * scale; 

    var srcImgData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height); 
    var destImgData = scaledCanvas.getContext('2d').createImageData(scaledCanvas.width, scaledCanvas.height); 

    this.applyBilinearInterpolation(srcImgData, destImgData, scale); 

    scaledCanvas.getContext('2d').putImageData(destImgData, 0, 0); 

    return scaledCanvas; 
}; 

ImageUploader.prototype.getHalfScaleCanvas = function(canvas) { 
    var halfCanvas = document.createElement('canvas'); 
    halfCanvas.width = canvas.width/2; 
    halfCanvas.height = canvas.height/2; 

    halfCanvas.getContext('2d').drawImage(canvas, 0, 0, halfCanvas.width, halfCanvas.height); 

    return halfCanvas; 
}; 

ImageUploader.prototype.applyBilinearInterpolation = function(srcCanvasData, destCanvasData, scale) { 
    function inner(f00, f10, f01, f11, x, y) { 
     var un_x = 1.0 - x; 
     var un_y = 1.0 - y; 
     return (f00 * un_x * un_y + f10 * x * un_y + f01 * un_x * y + f11 * x * y); 
    } 
    var i, j; 
    var iyv, iy0, iy1, ixv, ix0, ix1; 
    var idxD, idxS00, idxS10, idxS01, idxS11; 
    var dx, dy; 
    var r, g, b, a; 
    for (i = 0; i < destCanvasData.height; ++i) { 
     iyv = i/scale; 
     iy0 = Math.floor(iyv); 
     // Math.ceil can go over bounds 
     iy1 = (Math.ceil(iyv) > (srcCanvasData.height - 1) ? (srcCanvasData.height - 1) : Math.ceil(iyv)); 
     for (j = 0; j < destCanvasData.width; ++j) { 
      ixv = j/scale; 
      ix0 = Math.floor(ixv); 
      // Math.ceil can go over bounds 
      ix1 = (Math.ceil(ixv) > (srcCanvasData.width - 1) ? (srcCanvasData.width - 1) : Math.ceil(ixv)); 
      idxD = (j + destCanvasData.width * i) * 4; 
      // matrix to vector indices 
      idxS00 = (ix0 + srcCanvasData.width * iy0) * 4; 
      idxS10 = (ix1 + srcCanvasData.width * iy0) * 4; 
      idxS01 = (ix0 + srcCanvasData.width * iy1) * 4; 
      idxS11 = (ix1 + srcCanvasData.width * iy1) * 4; 
      // overall coordinates to unit square 
      dx = ixv - ix0; 
      dy = iyv - iy0; 
      // I let the r, g, b, a on purpose for debugging 
      r = inner(srcCanvasData.data[idxS00], srcCanvasData.data[idxS10], srcCanvasData.data[idxS01], srcCanvasData.data[idxS11], dx, dy); 
      destCanvasData.data[idxD] = r; 

      g = inner(srcCanvasData.data[idxS00 + 1], srcCanvasData.data[idxS10 + 1], srcCanvasData.data[idxS01 + 1], srcCanvasData.data[idxS11 + 1], dx, dy); 
      destCanvasData.data[idxD + 1] = g; 

      b = inner(srcCanvasData.data[idxS00 + 2], srcCanvasData.data[idxS10 + 2], srcCanvasData.data[idxS01 + 2], srcCanvasData.data[idxS11 + 2], dx, dy); 
      destCanvasData.data[idxD + 2] = b; 

      a = inner(srcCanvasData.data[idxS00 + 3], srcCanvasData.data[idxS10 + 3], srcCanvasData.data[idxS01 + 3], srcCanvasData.data[idxS11 + 3], dx, dy); 
      destCanvasData.data[idxD + 3] = a; 
     } 
    } 
}; 

Это масштабирует изображение до ширины config.maxWidth, сохраняя исходное соотношение сторон. На момент разработки это работало на iPad/iPhone Safari в дополнение к основным настольным браузерам (IE9 +, Firefox, Chrome), поэтому я ожидаю, что он будет по-прежнему совместим, учитывая более широкое восприятие HTML5 сегодня. Обратите внимание, что холст.toDataURL() принимает тип mime и качество изображения, что позволит вам контролировать качество и формат выходного файла (потенциально отличающийся от ввода, если хотите).

Единственное, что не охватывает, это сохранение информации о ориентации, без знания этих метаданных, размер изображения изменяется и сохраняется как есть, теряя любые метаданные в изображении для ориентации, что означает, что изображения, сделанные на планшетном устройстве " вверх ногами ", были показаны как таковые, хотя они были бы перевернуты в видоискателе камеры устройства. Если это вызывает беспокойство, у this blog post есть хороший пример руководства и кода о том, как это сделать, что, я уверен, можно было бы интегрировать с вышеуказанным кодом.

+3

Призвал вас за щедрость, потому что я чувствую, что он добавляет кучу ответа, нужно, чтобы эта информация была интегрирована, хотя :) –

+1

Очень полезная душа теперь слилась в некоторые функциональные возможности для метаданных ориентации :) –

7

Модификация к answer by Justin, что работает для меня:

  1. Добавлено img.onload
  2. Развернуть запрос POST с реальным, например

function handleFiles() 
{ 
    var dataurl = null; 
    var filesToUpload = document.getElementById('photo').files; 
    var file = filesToUpload[0]; 

    // Create an image 
    var img = document.createElement("img"); 
    // Create a file reader 
    var reader = new FileReader(); 
    // Set the image once loaded into file reader 
    reader.onload = function(e) 
    { 
     img.src = e.target.result; 

     img.onload = function() { 
      var canvas = document.createElement("canvas"); 
      var ctx = canvas.getContext("2d"); 
      ctx.drawImage(img, 0, 0); 

      var MAX_WIDTH = 800; 
      var MAX_HEIGHT = 600; 
      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); 

      dataurl = canvas.toDataURL("image/jpeg"); 

      // Post the data 
      var fd = new FormData(); 
      fd.append("name", "some_filename.jpg"); 
      fd.append("image", dataurl); 
      fd.append("info", "lah_de_dah"); 
      $.ajax({ 
       url: '/ajax_photo', 
       data: fd, 
       cache: false, 
       contentType: false, 
       processData: false, 
       type: 'POST', 
       success: function(data){ 
        $('#form_photo')[0].reset(); 
        location.reload(); 
       } 
      }); 
     } // img.onload 
    } 
    // Load files into file reader 
    reader.readAsDataURL(file); 
} 
5
fd.append("image", dataurl); 

Это не будет работать , На стороне PHP вы не можете сохранить файл с этим.

Используйте этот код вместо:

var blobBin = atob(dataurl.split(',')[1]); 
var array = []; 
for(var i = 0; i < blobBin.length; i++) { 
    array.push(blobBin.charCodeAt(i)); 
} 
var file = new Blob([new Uint8Array(array)], {type: 'image/png', name: "avatar.png"}); 

fd.append("image", file); // blob file 
+0

Супер полезно !! –

5

Принятого ответ работает, но логика изменения размера игнорирует тот случай, когда изображение больше, чем максимум только одной из осей (например, высота> maxHeight, но ширина < = maxWidth).

Я думаю, что следующий код заботится о всех случаях в более прямой вперед и функционально (игнорировать аннотацию типа машинопись, если использовать обычный JavaScript):

private scaleDownSize(width: number, height: number, maxWidth: number, maxHeight: number): {width: number, height: number} { 
    if (width <= maxWidth && height <= maxHeight) 
     return { width, height }; 
    else if (width/maxWidth > height/maxHeight) 
     return { width: maxWidth, height: height * maxWidth/width}; 
    else 
     return { width: width * maxHeight/height, height: maxHeight }; 
    } 
2

Изменение размера изображения в брезентовой элемент, как правило, плохая идея, поскольку она использует самую дешевую интерполяцию коробки. Полученное изображение заметно ухудшает качество. Я бы рекомендовал использовать http://nodeca.github.io/pica/demo/, который может выполнять преобразование Lanczos вместо этого. На демонстрационной странице выше показана разница между холстом и подходами Lanczos.

Он также использует веб-работников для изменения размера изображений параллельно. Существует также реализация WEBGL.

Есть некоторые онлайн resizers изображения, которые используют пику для выполнения работы, как https://myimageresizer.com

Смежные вопросы