jpegprogressive

Prevent progressive jpeg from loading completely


So, let's say I have a large version of an image that gets displayed only as a thumbnail. Would it be possible to avoid having a separate file for the thumbnail by using a progressive jpeg, stopping its loading when a certain number of scans have been reached, and continue loading only when the user chooses to open it in full?

If so, how can the loading of the image be controlled?

Thanks in advance.


Solution

  • There are 2 approaches:

    1. Client-side heavy

    Prerequisite

    Cross domain requests: if html and images are on different domains

    Apache .htaccess example on image server:

    <IfModule mod_headers.c>
        Header set Access-Control-Allow-Origin "*"
        Header set Access-Control-Allow-Headers: "range"
    </IfModule>
    

    Overview

    1. request image with an AJAX request
      • set Range header of AJAX request to how many bytes you want to get back
      • set the mimeType to plaintext (so we can base64 encode it later)
    2. Base64 encode data and set it to image's src attribute (<img src="data:image/jpeg;base64,...">)
      • beware for larger images this can be quite heavy on the client
    3. If setting the src attribute fails for whatever reason (eg. improper CROS settings), then fall back to set the data-src attribute to the src attribute

    The code

    This is built on gaetanoM's awesome answer here: Get Image using jQuery.ajax() and decode it to base64

    // for each img, which has data-src and data-bytes attributes
    $('img[data-src][data-bytes]').each(function(i,e){
    	$.ajax({
    	    url: $(e).data('src'), // url of image
    	    type: 'GET',
    	    headers: {
    	    	'Range':'bytes=0-'+$(e).data('bytes') // Range header, eg. Range: bytes=0-1024
    	    },
    	    mimeType: "text/plain; charset=x-user-defined"
    	}).done(function( data, textStatus, jqXHR ) {
    	    $(e).attr('src', 'data:image/jpeg;base64,' + base64encode(data)); // on success we set the base64 encoded data to the image's src attribute
    	}).always(function(){
    	    // if setting the src failed for whatever reason, we fall back to set the data-src attribute to src attribute
    	    if(!$(e).attr('src'))
    	        $(e).attr('src', $(e).data('src'));
    	});
    });
    
    function base64encode(str) {
        var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        var out = "", i = 0, len = str.length, c1, c2, c3;
        while (i < len) {
            c1 = str.charCodeAt(i++) & 0xff;
            if (i == len) {
                out += CHARS.charAt(c1 >> 2);
                out += CHARS.charAt((c1 & 0x3) << 4);
                out += "==";
                break;
            }
            c2 = str.charCodeAt(i++);
            if (i == len) {
                out += CHARS.charAt(c1 >> 2);
                out += CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
                out += CHARS.charAt((c2 & 0xF) << 2);
                out += "=";
                break;
            }
            c3 = str.charCodeAt(i++);
            out += CHARS.charAt(c1 >> 2);
            out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
            out += CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
            out += CHARS.charAt(c3 & 0x3F);
        }
        return out;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <!-- total filesize is 35 933 bytes -->
    <img data-src="http://shoepimper.com/doklist.com-logo.jpg" data-bytes="1900">
    <img data-src="http://shoepimper.com/doklist.com-logo.jpg" data-bytes="2500">
    <img data-src="http://shoepimper.com/doklist.com-logo.jpg" data-bytes="5600">
    
    <!-- if data-bytes are erroneous the server will return the whole image -->
    <img data-src="http://shoepimper.com/doklist.com-logo.jpg" data-bytes="error">
    <!-- if CROS fails, then it falls back to set the data-src attribute to the src attribute -->
    <img data-src="https://i.sstatic.net/QOPRf.jpg" data-bytes="error">

    2. Server-side heavy

    Ellaborating on ProgressiveMonkey's comment, you can easily trim the image data with php, or any other server side programming language.

    Overview

    Server-side code

    <?php
        $div = isset($_GET['div']) && intval($_GET['div'])>1 ? intval($_GET['div']) : 1; // what fraction of the image shall we return
        $img = 'doklist.com-logo.jpg';
        $size = round(filesize($img) / $div); // calculating the size in bytes what we return
    
        // setting the headers
        header("Content-Type: image/jpeg");
        header("Content-Length: $size");
    
        $fp = fopen($img, 'r');
            echo fread($fp, $size); // returning the necessary amount of bytes
        fclose($fp);
    ?>
    

    Examples

    Please see here an example of one of our site's logo (Doklist.com)

    Please feel free to play along with this url's div parameter (also please note that my test server may not handle the increased traffic well): http://shoepimper.com/progressive-thumb.php?div=14

    Reading just 1/24th of the filesize and returning it as a whole image: <img src="http://shoepimper.com/progressive-thumb.php?div=24"> enter image description here

    Reading 1/14th of the image: <img src="http://shoepimper.com/progressive-thumb.php?div=14"> enter image description here

    Reading 1/6th of the image: <img src="http://shoepimper.com/progressive-thumb.php?div=6"> enter image description here

    Reading the whole image (1/1th) <img src="http://shoepimper.com/progressive-thumb.php?div=1"> enter image description here

    If you need help to determine whether the image is progressively encoded or not, then use this: http://codepen.io/sergejmueller/full/GJKwv

    If you don't have direct access to the images, then you should use a proxy, meaning the structure of the code itself doesn't really change, you just "fopening" a remote file.