Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 537
ProgressiveMonkey Avatar asked Nov 05 '15 07:11

ProgressiveMonkey


2 Answers

There are 2 approaches:

1. Client-side heavy

Prerequisite

  • the image server has to support the range HTTP header
    • eg. Range: bytes=0-1024 means you are requesting only the first 1024 bytes
  • you have to know in advance how many bytes you want to request
    • you can say 1/8th of full size, if you push that value from server side
    • so the exact byte number should be known in the client side
  • if the Range is not valid or not supported, then the server will return the whole image, which is a good natural "fallback"

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

  • Access-Control-Allow-Headers: "range" has to be set

Apache .htaccess example on image server:

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "*"
    Header set Access-Control-Allow-Headers: "range"
</IfModule>
  • If the browser fails to make the request due to improper CROS settings, the code falls back to set the data-src attribute to the src attribute

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.stack.imgur.com/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: trim the image data based on an url parameter
    • if you want to know exactly where the first scan ends, please read this wonderful answer: How many bytes are needed for a complete thumbnail of a progressive JPEG?
  • client side: change that url parameter as you wish

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.

like image 154
Viktor Tabori Avatar answered Sep 22 '22 13:09

Viktor Tabori


It would be possible. You would just need to modify a decoder to do it.

You can control the loading as long as you have access to the data stream.

like image 34
user3344003 Avatar answered Sep 22 '22 13:09

user3344003