Prevent progressive jpeg from loading completely
Asked Answered
B

2

7

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.

Bancroft answered 5/11, 2015 at 7:56 Comment(4)
If you don't have control of the decoder, just trim the file to only include the first scan and you'll get just the DC values (1/8 size) for your image.Declinatory
I am looking to do the same thing. How do you mean trim the file? Could expand your comment into a possible answer?Thiazine
It seems that I have been taking this from the wrong direction: the server should stop sending the data, not the client receiving. Still not sure how to accomplish it though.Bancroft
Please see my answer below, I have updated it to include a client side and server side solution.Certifiable
C
7

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.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.

Certifiable answered 4/5, 2017 at 22:34 Comment(0)
B
1

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.

Bernitabernj answered 6/11, 2015 at 0:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.