2016-03-04 71 views
1

我有一個html應用程序,我正在處理大量大圖像。我們正在談論可能5,000張每張大約3-5MB的照片。如何在本地文件系統中以html/javascript加速顯示大圖像

到目前爲止,我正在測試大約1000個圖像,事情已經變得非常緩慢。

我使用拖放和一個的FileReader加載圖像然後設定的FileReader結果作爲圖像的來源:

private loadImageFromDisk(image: IImage): Rx.Observable<IImage> { 

     return Rx.Observable.defer(() => { 
      console.log(`loading ${image.file.name} from disc`); 
      console.time(`file ${image.file.name} loaded from file system`); 

      const reader = new FileReader(); 
      setTimeout(() => reader.readAsDataURL(image.file), 0) ; 

      const subject = new Rx.Subject(); 

      reader.onload = event => { 
       subject.onNext(reader.result); 
       subject.onCompleted(); 
      } 

      return subject 
       .safeApply(
        this.$rootScope, 
        result => { 
         console.timeEnd(`file ${image.file.name} loaded from file system`); 
         image.content = reader.result; 
        } 
       ) 
       .flatMap(result => Rx.Observable.return(image)); 
     }); 
    } 

HTML:

 <div 
      ng-repeat="photo in controller.pendingPhotos" 
      class="mdl-card photo-frame mdl-card--border mdl-shadow--4dp"> 
      <div class="mdl-card__title"> 
       {{photo.file.name}} 
      </div> 

      <div class="img-placeholder mdl-card__media"> 
       <div 
        ng-if="!photo.content" 
        class="mdl-spinner mdl-js-spinner is-active" 
        mdl-upgrade 
        ></div> 

       <img class="img-preview" ng-if="photo.content" ng-src="{{photo.content}}"/> 
      </div> 

      <div class="mdl-card__supporting-text" ng-if="photo.response"> 
       {{controller.formatResponse(photo.response)}} 
      </div> 

     </div> 

我知道NG-重複可能是一個性能問題,我會對其進行排序,但此時即使顯示一個圖像也可能需要幾秒鐘的時間。如果我從光盤加載圖像但實際上沒有顯示它,則從光盤加載每張圖像只需要大約50-100毫秒。如果我顯示它,速度會變慢。

我懷疑減速是瀏覽器(鉻)不得不調整圖像大小。

在一個測試中,我使用了70張圖片,我將它們全部加載到瀏覽器中,並且在所有內容都被加載並呈現之後,滾動性能很慢,前後幾次我在頁面上下滾動,然後很平滑。

這些圖像大約是2,000像素×2,000。我將其大小調整爲200像素長以顯示它們。

加快速度的最佳方法是什麼?

+0

老實說,它可能RxJS放慢你失望。 JS中的功能反應式編程非常好,但效率不高。 [我跑了一個測試](https://jsfiddle.net/pb2ra0c4/)使用[非常大的圖像](https://upload.wikimedia.org/wikipedia/commons/4/43/Very_Large_Array,_2012.jpg) (7360x4912或21.6 MB),並且在不使用任何其他框架時顯示大約需要1.5秒。 –

+1

您可以上傳文件,創建縮略圖並顯示縮略圖,而不是將圖像大小調整爲200像素 – Ronnie

+0

上傳文件在哪裏?通過網絡發送這些圖像到遠程服務器將是緩慢的...然後服務器將不得不調整它們,這將不會很快... – Roaders

回答

1

我前段時間面對同樣的問題(在爲攝影師服務時,使用角度)。

問題不是關於RxJS或角度,而是關於瀏覽器本身 - 它不是針對以這種方式顯示大量大圖像進行優化的。

首先,如果你需要顯示大量的圖片(沒關係是本地或遠程文件):

  1. 顯示(加載速度更快,不需要調整尺寸,更低的內存消耗)之前調整它們的大小。
  2. 如果可以 - 只顯示可見圖像(否則頁面會非常慢,直到所有圖像都會被加載)。檢查這個答案:How do I get the x and y positions of an element in an AngularJS directive原本trackVisibility被寫爲僅當它們變得可見時才顯示圖像。

關於從本地文件顯示圖像,事情更加複雜:

在你的情況要裝入文件數據的URL,並有一個問題:你3 MB提到的70張圖片每個會消耗至少2.1 Gb的RAM(實際上更多,並且不經意間會影響性能)

第一個建議是 - 如果您可以: 。第二:如果您只需要縮略圖 - 在顯示圖像之前在本地調整圖像大小(使用畫布)。如果對你來說很重要的話,會出現抗鋸齒問題 - 請看下面描述的降壓技術:Html5 canvas drawImage: how to apply antialiasing如果你支持iOS,那麼畫布尺寸限制可能會有問題,所以你需要以某種方式檢測它。 (這兩個問題都在下面的例子中解決)

最後一個:如果你需要爲許多圖像創建縮略圖 - 不要一次做這個,而是 - 安排工作事件循環(否則瀏覽器不會在調整圖像大小時有反應)。爲了獲得更好的性能,請按順序執行此操作(對於所有映像不要並行執行),這可能聽起來很奇怪 - 但速度會更快(由於內存消耗過低,同時讀取的磁盤空間更少)。

總結:

  1. 使用上述trackVisibility指令,只顯示可見光圖像
  2. 不要使用,數據的URL特別是對於大的圖像。
  3. 創建調整大小的縮略圖,顯示他們

庫之前,你可能會發現實現這個有用的:這樣做縮略圖

粗糙的代碼示例(大部分代碼都是從工作項目中複製而來的 - 所以預計會有效。canvasToJpegBlobmakeThumbnail剛纔寫,並沒有經過測試,所以可以有小的失誤):

function loadImage(imagePath) { 
    return Rx.Observable.create(function(observer) { 
    var img = new Image(); 
    img.src = imagePath; 
    image.onload = function() { 
     observer.onNext(image); 
     observer.onCompleted(); 
    } 
    image.onError = function(err) { 
     observer.onError(err); 
    } 
    }); 
} 

// canvas edge cases detection 
var maxDimm = 32000; 
var ios5 = false, ios3 = false; 
(function() { 
    if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) { 
    maxDimm = 8000; 
    } else { 
    var canvas = document.createElement('canvas'); 
    canvas.width = 1024 * 3; 
    canvas.height = 1025; 
    if (canvas.toDataURL('image/jpeg') === 'data:,') { 
     ios3 = true; 
    } else { 
     canvas = document.createElement('canvas'); 
     canvas.width = 1024 * 5; 
     canvas.height = 1025; 
     if (canvas.toDataURL('image/jpeg') === 'data:,') { 
     ios5 = true; 
     } 
    } 
    } 
}()); 

function stepDown(src, width, height) { 
    var 
    steps, 
    resultCanvas = document.createElement('canvas'), 
    srcWidth = src.width, 
    srcHeight = src.height, 
    context; 

    resultCanvas.width = width; 
    resultCanvas.height = height; 

    if ((srcWidth/width) > (srcHeight/height)) { 
    steps = Math.ceil(Math.log(srcWidth/width)/Math.log(2)); 
    } else { 
    steps = Math.ceil(Math.log(srcHeight/height)/Math.log(2)); 
    } 

    if (steps <= 1) { 
    context = resultCanvas.getContext('2d'); 
    context.drawImage(src, 0, 0, width, height); 
    } else { 
    var tmpCanvas = document.createElement('canvas'); 

    var 
     currentWidth = width * Math.pow(2, steps - 1), 
     currentHeight = height * Math.pow(2, steps - 1), 
     newWidth = currentWidth, 
     newHeight = currentHeight; 

    if (ios3 && currentWidth * currentHeight > 3 * 1024 * 1024) { 
     newHeight = 1024 * Math.sqrt(3 * srcHeight/srcWidth); 
     newWidth = newHeight * srcWidth/srcHeight; 
    } else { 
     if (ios5 && currentWidth * currentHeight > 5 * 1024 * 1024) { 
     newHeight = 1024 * Math.sqrt(5 * srcHeight/srcWidth); 
     newWidth = newHeight * srcWidth/srcHeight; 
     } else { 
     if (currentWidth > maxDimm || currentHeight > maxDimm) { 
      if (currentHeight > currentWidth) { 
      newHeight = maxDimm; 
      newWidth = maxDimm * currentWidth/currentHeight; 
      } else { 
      newWidth = maxDimm; 
      newHeight = maxDimm * currentWidth/currentHeight; 
      } 
     } 
     } 
    } 

    currentWidth = newWidth; 
    currentHeight = newHeight; 

    if ((currentWidth/width) > (currentHeight/height)) { 
     steps = Math.ceil(Math.log(currentWidth/width)/Math.log(2)); 
    } else { 
     steps = Math.ceil(Math.log(currentHeight/height)/Math.log(2)); 
    } 


    context = tmpCanvas.getContext('2d'); 
    tmpCanvas.width = Math.ceil(currentWidth); 
    tmpCanvas.height = Math.ceil(currentHeight); 

    context.drawImage(src, 0, 0, srcWidth, srcHeight, 0, 0, currentWidth, currentHeight); 

    while (steps > 1) { 
     newWidth = currentWidth * 0.5; 
     newHeight = currentHeight * 0.5; 

     context.drawImage(tmpCanvas, 0, 0, currentWidth, currentHeight, 0, 0, newWidth, newHeight); 
     steps -= 1; 
     currentWidth = newWidth; 
     currentHeight = newHeight; 
    } 

    context = resultCanvas.getContext('2d'); 
    context.drawImage(tmpCanvas, 0, 0, currentWidth, currentHeight, 0, 0, width, height); 
    } 
    return resultCanvas; 
} 

function canvasToJpegBlob(canvas) { 
    return Rx.Observable.create(function(observer) { 
    try { 
     canvas.toBlob(function(blob) { 
     observer.onNext(blob); 
     observer.onCompleted(); 
     }, 'image/jpeg'); 
    } catch (err) { 
     observer.onError(err); 
    } 
    }); 
} 

function makeThumbnail(file) { 
    return Observable.defer(()=> { 
    const fileUrl = URL.createObjectURL(file); 
    return loadImage(fileUrl) 
     .map(image => { 
     const width = 200; 
     const height = image.height * width/image.width; 
     const thumbnailCanvas = stepDown(image, width, height); 
     URL.revokeObjectURL(fileUrl); 
     return thubnailCanvas; 
     }) 
     .flatMap(canvasToJpegBlob) 
     .map(canvasBlob=>URL.createObjectURL(canvasBlob)) 
     .map(thumbnailUrl => { 
     return { 
      file, 
      thumbnailUrl 
     } 
     }) 
    }); 
    } 
相關問題