From 27c6cb6eeb2ab677e1389e83ec5e80750555a8d7 Mon Sep 17 00:00:00 2001 From: Igor Zhukov Date: Tue, 30 Jun 2015 00:37:01 +0300 Subject: [PATCH] Caching webp converted --- app/js/directives.js | 16 +++-- app/js/lib/mtproto_wrapper.js | 37 ++++++++--- app/js/lib/ng_utils.js | 115 ++++++++++++++++++++++++++++++++++ app/js/lib/utils.js | 100 ----------------------------- app/js/services.js | 27 ++++---- 5 files changed, 160 insertions(+), 135 deletions(-) diff --git a/app/js/directives.js b/app/js/directives.js index 4314a4bf..469a90d7 100755 --- a/app/js/directives.js +++ b/app/js/directives.js @@ -1876,13 +1876,7 @@ angular.module('myApp.directives', ['myApp.filters']) .addClass(attrs.imgClass); var setSrc = function (blob) { - if (WebpManager.isWebpSupported()) { - imgElement.attr('src', FileManager.getUrl(blob, 'image/webp')); - return; - } - FileManager.getByteArray(blob).then(function (bytes) { - imgElement.attr('src', WebpManager.getPngUrlFromData(bytes)); - }); + imgElement.attr('src', FileManager.getUrl(blob)); }; imgElement.css({ @@ -1894,12 +1888,16 @@ angular.module('myApp.directives', ['myApp.filters']) height: $scope.document.thumb.height }); - var smallLocation = $scope.document.thumb.location; + var smallLocation = angular.copy($scope.document.thumb.location); + smallLocation.sticker = true; + var fullLocation = { _: 'inputDocumentFileLocation', id: $scope.document.id, access_hash: $scope.document.access_hash, - dc_id: $scope.document.dc_id + dc_id: $scope.document.dc_id, + file_name: $scope.document.file_name, + sticker: true }; diff --git a/app/js/lib/mtproto_wrapper.js b/app/js/lib/mtproto_wrapper.js index 2d678102..cd283d61 100644 --- a/app/js/lib/mtproto_wrapper.js +++ b/app/js/lib/mtproto_wrapper.js @@ -255,7 +255,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) } }) -.factory('MtpApiFileManager', function (MtpApiManager, $q, FileManager, IdbFileStorage, TmpfsFileStorage, MemoryFileStorage) { +.factory('MtpApiFileManager', function (MtpApiManager, $q, FileManager, IdbFileStorage, TmpfsFileStorage, MemoryFileStorage, WebpManager) { var cachedFs = false; var cachedFsPromise = false; @@ -318,17 +318,25 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) return 'video' + location.id + '.mp4'; case 'inputDocumentFileLocation': + var fileName = (location.file_name || '').split('.', 2); + var ext = fileName[1] || ''; + if (location.sticker && !WebpManager.isWebpSupported()) { + ext += '.png'; + } + if (fileName.length) { + return fileName[0] + '_' + location.id + '.' + ext; + } return 'doc' + location.id; case 'inputAudioFileLocation': return 'audio' + location.id; - } - if (!location.volume_id) { - console.trace('Empty location', location); + default: + if (!location.volume_id) { + console.trace('Empty location', location); + } + return location.volume_id + '_' + location.local_id + '_' + location.secret + '.jpg'; } - - return location.volume_id + '_' + location.local_id + '_' + location.secret + '.jpg'; }; function getTempFileName(file) { @@ -374,7 +382,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) } // console.log('dload small', location); var fileName = getFileName(location), - mimeType = 'image/jpeg', + mimeType = location.sticker ? 'image/webp' : 'image/jpeg', cachedPromise = cachedSavePromises[fileName] || cachedDownloadPromises[fileName]; if (cachedPromise) { @@ -403,11 +411,20 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) }); }); + var processDownloaded = function (blob) { + if (!location.sticker || WebpManager.isWebpSupported()) { + return qSync.when(blob); + } + return WebpManager.getPngBlobFromWebp(blob); + }; + return fileStorage.getFileWriter(fileName, mimeType).then(function (fileWriter) { return downloadPromise.then(function (result) { - return FileManager.write(fileWriter, result.bytes).then(function () { - return cachedDownloads[fileName] = fileWriter.finalize(); - }); + return processDownloaded.then(function (proccessedResult) { + return FileManager.write(fileWriter, proccessedResult.bytes).then(function () { + return cachedDownloads[fileName] = fileWriter.finalize(); + }); + }) }); }); }); diff --git a/app/js/lib/ng_utils.js b/app/js/lib/ng_utils.js index 3a10b9ca..60d48539 100644 --- a/app/js/lib/ng_utils.js +++ b/app/js/lib/ng_utils.js @@ -656,6 +656,121 @@ angular.module('izhukov.utils', []) }; }) +.service('WebpManager', function (qSync, $q) { + var nativeWebpSupport = false; + + var image = new Image(); + image.onload = function () { + nativeWebpSupport = this.width === 2 && this.height === 1; + }; + image.onerror = function () { + nativeWebpSupport = false; + }; + image.src = 'data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAACyAgCdASoCAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA=='; + + var canvas, context; + + function getCanvasFromWebp(data) { + var start = tsNow(); + + var decoder = new WebPDecoder(); + + var config = decoder.WebPDecoderConfig; + var buffer = config.j || config.output; + var bitstream = config.input; + + if (!decoder.WebPInitDecoderConfig(config)) { + console.error('[webpjs] Library version mismatch!'); + return false; + } + + // console.log('[webpjs] status code', decoder.VP8StatusCode); + var StatusCode = decoder.VP8StatusCode; + + status = decoder.WebPGetFeatures(data, data.length, bitstream); + if (status != (StatusCode.VP8_STATUS_OK || 0)) { + console.error('[webpjs] status error', status, StatusCode); + } + + var mode = decoder.WEBP_CSP_MODE; + buffer.colorspace = mode.MODE_RGBA; + buffer.J = 4; + + try { + status = decoder.WebPDecode(data, data.length, config); + } catch (e) { + status = e; + } + + ok = (status == 0); + if (!ok) { + console.error('[webpjs] decoding failed', status); + return false; + } + + // console.log('[webpjs] decoded: ', buffer.width, buffer.height, bitstream.has_alpha, 'Now saving...'); + var bitmap = buffer.c.RGBA.ma; + + // console.log('[webpjs] done in ', tsNow() - start); + + if (!bitmap) { + return false; + } + var biHeight = buffer.height; + var biWidth = buffer.width; + + if (!canvas || !context) { + canvas = document.createElement('canvas'); + context = canvas.getContext('2d'); + } else { + context.clearRect(0, 0, canvas.width, canvas.height); + } + canvas.height = biHeight; + canvas.width = biWidth; + + var output = context.createImageData(canvas.width, canvas.height); + var outputData = output.data; + + for (var h = 0; h < biHeight; h++) { + for (var w = 0; w < biWidth; w++) { + outputData[0+w*4+(biWidth*4)*h] = bitmap[1+w*4+(biWidth*4)*h]; + outputData[1+w*4+(biWidth*4)*h] = bitmap[2+w*4+(biWidth*4)*h]; + outputData[2+w*4+(biWidth*4)*h] = bitmap[3+w*4+(biWidth*4)*h]; + outputData[3+w*4+(biWidth*4)*h] = bitmap[0+w*4+(biWidth*4)*h]; + + }; + } + + context.putImageData(output, 0, 0); + + return true; + } + + function getPngBlobFromWebp (data) { + if (!getCanvasFromWebp(data)) { + return qSync.reject({type: 'WEBP_PROCESS_FAILEd'}); + } + if (canvas.toBlob === undefined) { + return dataUrlToBlob(canvas.toDataURL('image/png')); + } + + var deferred = $q.defer(); + canvas.toBlob(function (blob) { + deferred.resolve(blob); + }, 'image/png'); + return deferred.promise; + } + + return { + isWebpSupported: function () { + return nativeWebpSupport; + }, + getPngBlobFromWebp: getPngBlobFromWebp + } + +}) + + .service('CryptoWorker', function ($timeout, $q) { var webWorker = false, diff --git a/app/js/lib/utils.js b/app/js/lib/utils.js index 17cf99cb..fdcdc47e 100644 --- a/app/js/lib/utils.js +++ b/app/js/lib/utils.js @@ -451,103 +451,3 @@ function versionCompare (ver1, ver2) { }; })(window); - - -(function (global) { - var nativeWebpSupport = false; - - var image = new Image(); - image.onload = function () { - nativeWebpSupport = this.width === 2 && this.height === 1; - }; - image.onerror = function () { - nativeWebpSupport = false; - }; - image.src = 'data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAACyAgCdASoCAAEALmk0mk0iIiIiIgBoSygABc6zbAAA/v56QAAAAA=='; - - var canvas, context; - - function getPngUrlFromData(data) { - var start = tsNow(); - - var decoder = new WebPDecoder(); - - var config = decoder.WebPDecoderConfig; - var buffer = config.j || config.output; - var bitstream = config.input; - - if (!decoder.WebPInitDecoderConfig(config)) { - console.error('[webpjs] Library version mismatch!'); - return false; - } - - // console.log('[webpjs] status code', decoder.VP8StatusCode); - var StatusCode = decoder.VP8StatusCode; - - status = decoder.WebPGetFeatures(data, data.length, bitstream); - if (status != (StatusCode.VP8_STATUS_OK || 0)) { - console.error('[webpjs] status error', status, StatusCode); - } - - var mode = decoder.WEBP_CSP_MODE; - buffer.colorspace = mode.MODE_RGBA; - buffer.J = 4; - - try { - status = decoder.WebPDecode(data, data.length, config); - } catch (e) { - status = e; - } - - ok = (status == 0); - if (!ok) { - console.error('[webpjs] decoding failed', status); - return false; - } - - // console.log('[webpjs] decoded: ', buffer.width, buffer.height, bitstream.has_alpha, 'Now saving...'); - var bitmap = buffer.c.RGBA.ma; - - // console.log('[webpjs] done in ', tsNow() - start); - - if (!bitmap) { - return false; - } - var biHeight = buffer.height; - var biWidth = buffer.width; - - if (!canvas || !context) { - canvas = document.createElement('canvas'); - context = canvas.getContext('2d'); - } else { - context.clearRect(0, 0, canvas.width, canvas.height); - } - canvas.height = biHeight; - canvas.width = biWidth; - - var output = context.createImageData(canvas.width, canvas.height); - var outputData = output.data; - - for (var h = 0; h < biHeight; h++) { - for (var w = 0; w < biWidth; w++) { - outputData[0+w*4+(biWidth*4)*h] = bitmap[1+w*4+(biWidth*4)*h]; - outputData[1+w*4+(biWidth*4)*h] = bitmap[2+w*4+(biWidth*4)*h]; - outputData[2+w*4+(biWidth*4)*h] = bitmap[3+w*4+(biWidth*4)*h]; - outputData[3+w*4+(biWidth*4)*h] = bitmap[0+w*4+(biWidth*4)*h]; - - }; - } - - context.putImageData(output, 0, 0); - - return canvas.toDataURL('image/png'); - } - - - global.WebpManager = { - isWebpSupported: function () { - return nativeWebpSupport; - }, - getPngUrlFromData: getPngUrlFromData - } -})(window); diff --git a/app/js/services.js b/app/js/services.js index 6ebdc802..a63d605d 100755 --- a/app/js/services.js +++ b/app/js/services.js @@ -3445,7 +3445,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) inputFileLocation = { _: 'inputDocumentFileLocation', id: docID, - access_hash: doc.access_hash + access_hash: doc.access_hash, + file_name: doc.file_name }; if (historyDoc.downloaded === undefined) { @@ -3463,7 +3464,8 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) inputFileLocation = { _: 'inputDocumentFileLocation', id: docID, - access_hash: doc.access_hash + access_hash: doc.access_hash, + file_name: doc.file_name }; if (historyDoc.downloaded && !toFileEntry) { @@ -3783,20 +3785,13 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) function downloadStickerThumb (docID) { var doc = AppDocsManager.getDoc(docID); - return MtpApiFileManager.downloadSmallFile(doc.thumb.location).then(function (blob) { - if (WebpManager.isWebpSupported()) { - return { - id: doc.id, - src: FileManager.getUrl(blob, 'image/webp') - }; - } - - return FileManager.getByteArray(blob).then(function (bytes) { - return { - id: doc.id, - src: WebpManager.getPngUrlFromData(bytes) - }; - }); + var thumbLocation = angular.copy(doc.thumb.location); + thumbLocation.sticker = true; + return MtpApiFileManager.downloadSmallFile(thumbLocation).then(function (blob) { + return { + id: doc.id, + src: FileManager.getUrl(blob, 'image/webp') + }; }); }