Hoog geheugen-gebruik bij veel base64-afbeeldingen

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • hiekikowan
  • Registratie: Februari 2011
  • Laatst online: 07-10 22:53
Goedemiddag GoT,

Op dit moment ben ik bezig met het ontwikkelen van een webapp in Angular. Deze app zal per pagina een grote hoeveelheid foto's gaan tonen in een fotogallerij (Colorbox). Op dit moment heb ik het geheel als volgt geimplementeerd:
  1. Angular krijgt in een Ajax-request een lijst aan IDs (gewrapped in objecten) van de foto's mee
  2. Angular heeft een 'ngRepeat' voor deze lijst van IDs. Voor ieder ID worden de fotos asynchroon geladen middels ajax-requests
  3. Ieder element in de ngRepeat heeft een
    code:
    1
    
    ngSrc
    voor een thumbnail. De API waarvan de foto's geladen worden geeft de thumbnails in base64 terug. De 'ngSrc' wordt dan ook gevuld met iets als
    code:
    1
    
    data:image/jpeg;base64,[BASE64]
    waarna de thumbnails prima worden weergeven
  4. Ieder element in de ngRepeat heeft ook een anchor-tag met daarin een 'href'. Deze 'href' is gevuld met wederom een base64-datastring.
  5. Colorbox probeert op basis van de base64-string van de 'href' de afbeelding full-size te laten zien
Het geheel hierboven werkt 2, misschien 3 keer. Dat wil zeggen, ik kan op 2 tot 3 thumbnails klikken tot Chrome de tab laat chrashen met de bekende "He's dead, Jim!"-melding. Firefox crasht niet zo snel maar de browser wordt wel onwerkbaar traag, totdat mijn OS komt met de melding dat Firefox niet meer reageert, en of ik het proces wil stoppen.

Een zoektocht op Google gaf aan dat wanneer zoiets optreedt dit met het geheugengebruik te maken heeft. Het lijkt erop dat de base64-data allemaal in het geheugen vast gehouden wordt, ook na het sluiten van de grote foto's. Colorbox verwijdert dan echter wel netjes het img-element uit de DOM.

Op dit moment staar ik me een beetje blind en zie niet goed hoe nu verder. Kunnen jullie me weer een duw in de juiste richting geven? Alvast bedankt _/-\o_

Edit: wat meer context/code voor de beeldvorming

Het laden van de thumbnails doe ik met de volgende functie (fotos is vergelijkbaar, wordt alleen naar een andere property van het $photo-object geschreven):
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$scope.loadThumbnails = function() {                                                                        
  $scope.photos.forEach(function($photo){                                                        
    var request = $http({                                                                               
      method: "GET",                              
      url: [URL]                                     
    });                                                                                                 

    var fetchThumbnailSuccess = function(response) {                                                    
      console.debug("Fetched thumbnail with id " + $photo.id);                                        
      $photo.thumb_src = "data:image/jpeg;base64," + response;                                        
    }                                                                                                   
    request.success(fetchThumbnailSuccess);                                                             

    var fetchThumbnailFailure = function(response) {                                                    
      console.error("Failed fetching thumbnail with id " + $photo.id);                                
      console.debug(response);                                                                        
    }                                                                                                   
    request.error(fetchThumbnailFailure);                                                               
  });                                                                                                     
}


De ngRepeat ziet er als volgt uit:
HTML:
1
2
3
4
5
<div ng-repeat="photo in photos track by $index" id="entry_{{$index + 1}}">                            
  <a colorbox='{"transition":"fade", "photo":true, "maxWidth":"80%", "maxHeight":"80%"}' href="{{photo.photo_src}}">
    <img ng-src="{{photo.thumb_src}}" id="{{photo.id}}" />
  </a>
</div>


De colorbox-directive welke je hier ziet heb ik van StackOverflow

[ Voor 43% gewijzigd door hiekikowan op 20-05-2015 16:33 ]


Acties:
  • 0 Henk 'm!

  • Sebazzz
  • Registratie: September 2006
  • Laatst online: 12-10 15:56

Sebazzz

3dp

De base64 text zit vast aan de DOM. Die blijft in het geheugen geladen.

[Te koop: 3D printers] [Website] Agile tools: [Return: retrospectives] [Pokertime: planning poker]


Acties:
  • 0 Henk 'm!

  • hiekikowan
  • Registratie: Februari 2011
  • Laatst online: 07-10 22:53
Daar was ik al bang voor. Het probleem wat ik probeer op te lossen is het volgende:
Om de afbeeldingen te laden moet er een custom header meegestuurd worden, anders krijg je een 401 terug. Ik zie geen andere mogelijkheid om de afbeelding met zo'n custom header te laden dan door dit via een Ajax-call te doen. En vervolgens zie ik geen andere mogelijkheid om deze afbeelding te renderen dan om deze als base64 op te vragen en vervolgens in een data-URL te stoppen.

Het gaat hierbij om foto's van 2048 bij 1536 pixels (3 megapixel) welke dus behoorlijk wat geheugen in beslag nemen.

Is er iemand die hiervoor een andere oplossing kent? Ter info: de API waarvan de image gedownload wordt is gebouwd in Silex en middels middleware wordt daar voor ieder request een token gecontroleerd.




Edit: ik heb op dit moment een werkbare situatie door aan de API-kant 1 aangepaste controller te implementeren voor het ophalen van foto's. Deze controller accepteert zijn token via een query-param en niet via een header. Hierdoor is het mogelijk een 'src'-attribuut met daarin een URL met query-string toe te voegen wat mijn probleem met de base64-data wegneemt...

Heel blij ben ik met deze situatie echter niet. Mogelijk wordt in de toekomst het nodig om HTTP-basic auth toe te passen op alle requests. Iets wat met middlewares en headers prima mogelijk is maar niet met querystrings. Nu ben ik niet heel bekend met het nieuwe canvas-element maar volgens internet moet het mogelijk zijn om hier ook images op te tekenen. Kan iemand hier bevestigen danwel ontkrachten dat dit het performance-probleem wat ontstaat door de data-urls weggenomen wordt?

Ik speel mbt canvas nu met de volgende code:
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$scope.loadThumbnails = function() {
  $scope.photos.forEach(function($photo){
    $photo.canvas_th = document.querySelector('canvas#' + $photo.id + '_th');

    var request = $http({                                                                               
      method: "GET",                              
      url: [URL]                                     
    });                                                                                                 

    var fetchThumbnailSuccess = function(response) {                                                    
      console.debug("Fetched thumbnail with id " + $photo.id);
      
      // Get drawing context
      var ctx = $photo.canvas_th.getContext("2d");

      // Create image with onload drawing to context
      var img = new Image();
      img.onload = function() {
        ctx.drawImage(img, 0, 0);
      }

      // Add data to image object, triggering the onload function which in turn draws the image to the context
      img.src = 'data:image/jpeg;base64,' + response;
    }                                                                                                   
    request.success(fetchThumbnailSuccess);                                                             

    var fetchThumbnailFailure = function(response) {                                                    
      console.error("Failed fetching thumbnail with id " + $photo.id);                                
      console.debug(response);                                                                        
    }                                                                                                   
    request.error(fetchThumbnailFailure);                                                               
  });                                                                                                     
}


De bijbehorende HTML wordt dan iets van:
HTML:
1
2
3
4
5
<div ng-repeat="photo in photos track by $index" id="entry_{{$index + 1}}">                            
  <a colorbox='{"transition":"fade", "photo":true, "maxWidth":"80%", "maxHeight":"80%"}' href="{{photo.photo_src}}">
    <canvas id="{{photo.id}}_th" />
  </a>
</div>


Hoe ik de afbeelding dan in colorbox ga laten tonen weet ik nog niet. Er is vast iemand die vaker met dit bijltje gehakt heeft (hoop ik :P) ? Gaat dit mijn performance-problemen oplossen zonder dat de workaround met de querystring nodig is?

[ Voor 74% gewijzigd door hiekikowan op 20-05-2015 18:11 ]


Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 06-10 22:29
mag ik vragen waarom je uberhaupt plaatjes als base64 ophaald?
Ondersteund je backend niet anders of is dat jou keuze?

Het is niet heel efficient en je ondervind er nu dus al problemen mee. Waarom niet gewoon een image ophalen?

Mocht je persee base64 images willen gebruiken. Je kunt kijken of je het geheugen naar beneden kunt krijgen door in JS echte object deletes uit te voeren of de src van de image leeg te maken voordat je het dom element verwijderd.
Wie weet werkt dat.

Je verhaal in je tweede post gaat opeens over autorisatie en headers. :S

Wie weet had je daarmee moeten beginnen ;)

Hier het antwoord op die vraag:
http://stackoverflow.com/...with-basic-authentication (eerste hit in google)

Lees: dat moet je niet willen.

Hebben we het over een internet site of intranet?
In het geval van intranet zou ik lekker kerberos gaan gebruiken, dat is veel veiliger en je kunt er veel meer mee.
In het geval van internet: Waarom gebruik je uberhaupt basic auth?
Je kunt iemand laten inloggen op whatever manier en een cookie setten. Cookies reizen mee met elk request en die kun je dus serverside checken. Er gaan dan geen usernames/passwords over de lijn en je kunt toch elk request auth-en

[ Voor 40% gewijzigd door BasieP op 20-05-2015 22:18 ]

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

  • mcDavid
  • Registratie: April 2008
  • Laatst online: 02-10 08:45
Base64 gebruiken voor (grote) binaire objecten is sowieso ernstig inefficiënt. Afbeeldingen worden ongeveer 2x zo groot als je ze base64-encode. Het wordt ook overal ten zeerste afgeraden data: strings te gebruiken voor afbeeldingen groter dan een smiley of pictogrammetje. De oplossing is dus doodeenvoudig: geen data: strings gemisbruiken maar gewoon de afbeeldingen embedden.

Acties:
  • 0 Henk 'm!

  • hiekikowan
  • Registratie: Februari 2011
  • Laatst online: 07-10 22:53
Ophalen als base64 had ik als zodanig geimplementeerd (ik ontwikkel de API ook) omdat dit mijns inziens de enige mogelijkheid was om met javascript headers aan requests toe te voegen en de response vervolgens als afbeelding te tonen. Het hele verhaal met canvas verandert aan de situatie niets aangezien ook daar de base64-strings ergens in het geheugen gehouden moeten worden.

Het verhaal omtrent autoristatie en headers heeft te maken met de originele vraag, dat is namelijk de reden waarom ik in eerste instantie niet inzag hoe ik gewoon een 'img'-tag met 'src'-attribuut kon gebruiken. Alle requests welke naar de API gestuurd worden moeten worden voorzien van

Na verder zoekwerk op het internet afgelopen uren heb ik het geheel nu als volgt:
  • De API accepteert het token voor autorisatie nu ofwel als header, of als query-parameter
  • Bij het ophalen van foto's vanaf de API stuur ik een token mee als query-parameter. Dit doe ik door voor een 'img'-tag het 'ng-src'-attribuut als volgt op te bouwen:
    code:
    1
    
    https://api/photo/1?token=1234
  • Het verdere ophalen van de afbeeldingen wordt vervolgens geheel door de browser gedaan, waardoor zaken als caching en dergelijke gewoon blijven werken.
Zoals te zien is liep ik weer eens veel te moeilijk te denken. Het geheel performt nu weer heel redelijk. Bedankt voor het meedenken.

@BasieP aan de hand van je edit: het gaat om een internet-site en de door jouw geschetste situatie is dus eigenlijk wat ik nu doe, maar dan zonder cookie en met een waarde uit localStorage. Misschien nog wat te complex, maar dat is van later zorg...

[ Voor 8% gewijzigd door hiekikowan op 20-05-2015 22:27 ]


Acties:
  • 0 Henk 'm!

  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 06-10 22:29
Hier zou je ook nog even naar kunnen kijken:

JavaScript:
1
2
3
4
5
6
7
8
9
var url = 'https://api/photo/1';
$.ajax({ 
    url : url, 
    headers: { 'x-custom-auth-token': 'tokenvalue' }
    cache: true,
    processData : false,
}).always(function(){
    $("#IMAGE_ID").attr("src", url);
});  


Ik heb het zelf niet getest, maar in theorie zou je browser de image (met custom header) gaan ophalen. Als dat klaar is zet je dezelfde afbeelding in een img element, waardoor de browser de gecachede afbeelding gebruikt.

edit: ja het is jquery, maar de theorie zou natuurlijk ook met andere frameworks of gewoon vanila.js kunnen werken

[ Voor 14% gewijzigd door BasieP op 20-05-2015 22:44 ]

This message was sent on 100% recyclable electrons.


Acties:
  • 0 Henk 'm!

  • hiekikowan
  • Registratie: Februari 2011
  • Laatst online: 07-10 22:53
Helaas werkt dat in Chrome prima maar gooit Firefox roet in het eten. Firefox cached de afbeelding niet en gaat daardoor een nieuw request afvuren bij het setten van de 'src'-parameter. Hier komt vervolgens een 401 op terug.

Ik laat het dus maar zo, dit werkt tenminste.

Acties:
  • 0 Henk 'm!

  • n8n
  • Registratie: Juni 2007
  • Laatst online: 12-10 20:10

n8n

mcDavid schreef op woensdag 20 mei 2015 @ 22:19:
Base64 gebruiken voor (grote) binaire objecten is sowieso ernstig inefficiënt. Afbeeldingen worden ongeveer 2x zo groot als je ze base64-encode. Het wordt ook overal ten zeerste afgeraden data: strings te gebruiken voor afbeeldingen groter dan een smiley of pictogrammetje. De oplossing is dus doodeenvoudig: geen data: strings gemisbruiken maar gewoon de afbeeldingen embedden.
a) de overhead komt dichter in de buurt van 30%
b) met gzip is de overhead 1 tot 3 procent, te verwaarlozen dus

Neemt niet weg dat base64 haken en ogen heeft qua caching, geheugengebruik, dom-vervuiling en het tonen kost (iets) extra moeite tov een binary image. Ook Is resolutie-afhankelijke alternatieve presenteren lastiger en werkt het uitschakelen van afbeeldingen in de browser niet.

Acties:
  • 0 Henk 'm!

  • Tsjilp
  • Registratie: November 2002
  • Niet online

Tsjilp

RS[I]ds

Daarnaast nog een angular tipje: Voor je ID & src kun je one time binding {{::varname}} gebruiken, dat scheelt misschien ook al wat geheugen gebruik, omdat angular dan niet meer een watch op dat object zet

Raar... Is zo gek nog niet

Pagina: 1