Bug 39905 - Image Resizer API
Summary: Image Resizer API
Status: ASSIGNED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Images (show other bugs)
Version: 528+ (Nightly build)
Hardware: All All
: P2 Normal
Assignee: Nobody
URL:
Keywords:
Depends on: 41171 41218 39906 40018 40854 41116 41119 41230
Blocks:
  Show dependency treegraph
 
Reported: 2010-05-28 15:17 PDT by Sterling Swigart
Modified: 2019-01-16 08:34 PST (History)
10 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Sterling Swigart 2010-05-28 15:17:35 PDT
This API will be attached to the Image object, and its usage will be:

     Image.getBlob(mimeType /* req */, width /* req */, height /* req */, successEvent /* req */, errorEvent /* op */, qualityLevel /* op */, preserveAspectRatio /* op */, rotateExif /* op */);

Implementation will take place in the following steps:

     0. Add functionality for conditional compilation of added files.
     1. Create another thread (which will do the resizing in the future), and callback to the main thread
     2. Resize the image and place it into a Blob
     3. Expose the function to javascript
     4. Derive EXIF orientation metadata from image and rotate image accordingly if the rotateExif parameter is true
     5. Transfer all metadata (except thumbnails and in the above case, EXIF orientation)

Tests will be added at each steps, as appropriate. Each step will have its own bug, and this will be dependent on the approval of each of those steps.

Here is a full proposal for the API:


Use Cases:

Begin with a user giving a local image file to a webpage. Then:

1. In real-time chat, quickly give other users a thumbnail view of the image file.
2. Or, limit the size of an image file before uploading it to a web server.

Proposed Solution:

We propose adding image.getBlob. getBlob will be an instance function of the javascript Image object which asynchronously gets a blob of the image, resized to the given width and height, encoded into jpeg or png. The function declaration will be:

getBlob(mimeType /* req */, width /* req */, height /* req */, successEvent /* req */, errorEvent /* op */, qualityLevel /* op */, preserveAspectRatio /* op */, rotateExif /* op */);

The blob will be passed as an argument to the success callback function, or upon error, error data will be passed into the error callback function as an argument. Quality level should be between 0.0 and 1.0, and any value outside of that range will be reverted to the default, 0.85. If MIME type does not equal "image/jpeg", then quality level is ignored. If null (or a negative value) is passed in for the width or height, then the function will use the source's measurement for that dimension. Default values for preserveAspectRatio and rotateExif are true.

All EXIF metadata will be retained except for any saved thumbnails, and the EXIF rotation property will be appropriately modified.


Security:

If the image source is of a different origin than the script context, then getBlob raises a SECURITY_ERR exception.


Sample Code:

// url contains location of an image file
Image i = new Image();
i.src = url;
var successEvt = function (newBlob) { myDiv.innerHTML += "<img src='" + newBlob.url + "' />"; };
var errEvt = function (err) { alert(err); };
i.getBlob("image/jpeg", 300, 350, successEvt, errEvt, .55);
// Image will retain aspect ratio and correct for EXIF rotation. If the source image was 700x700,
// the blob will represent a new image that is 300x300.


Appendix: Alternatives considered 

For reference, we've also included a list of other designs that we thought of along with the reasons why they were dropped:


Creating a new object for resizing

Summary of approach: 

[NamedConstructor=ImageResizer(),
 NamedConstructor=ImageResizer(blob, onsuccess),
 NamedConstructor=ImageResizer(blob, onsuccess, onerror),
 NamedConstructor=ImageResizer(blob, onsuccess, onerror, type),
 NamedConstructor=ImageResizer(blob, onsuccess, onerror, type, width, height)]

interface ImageResizer {
    void start(); // starts resize operation
    void abort(); // aborts operation
    attribute Blob blob;
    attribute DOMString type; // default "image/png"
    attribute unsigned long width; 
    attribute unsigned long height; 
    attribute float qualityLevel; // default 1.0, must be 0.0 to 1.0, else reverts to default
    readonly attribute unsigned short started; // default 0
    attribute Function onsuccess;
    attribute Function onerror;
};

Why it wasn't chosen:

Creating an entirely new object for this task made the task seem more complicated and involved than necessary, and this problem could be solved via modifications to the Image object.


Returning a SizelessBlob immediately from a method on image

Summary of approach: 

var streamingBlob = image.toStreamingBlob(mimeType /* req */, width /* req */, height /* req */, qualityLevel /* op */, preserveAspectRatio /* op */, rotateExif /* op */);

New Blob Interfaces:

interface SizelessBlob {
       // moved from Blob
      readonly attribute DOMString type;
      readonly attribute DOMString url; // whatever name -- URL, urn, URN, etc.
}

interface StreamingBlob : SizelessBlob {
                     // at most one of the following functions will be called for a single FutureBlob
                     attribute Function onblobready;
                     attribute Function onerror;
      readonly attribute blob; // throws an exception if accessed before onblobready is called.
}

interface Blob : SizelessBlob {
      readonly attribute unsigned long long size;
      Blob slice(in long long start,
                     in long long length); // raises DOMException
};

Why it wasn't chosen:

the disconnect of the error from the thing that caused it making failures hard to understand (e.g. An image load may fail but that may not be detected until the xhr is done using the resized image.)

the issues that result from passing a SizelessBlob, which has a reference to a loading image, to another document and closing the original document (thus killing the image loader) introduction of multiple blobs which may be confusing to developers the need to change all existing specs to use SizelessBlob instead of Blob.


Returning a Blob immediately from a method on image

Summary of approach: 

var blob = image.toBlob(mimeType /* req */, width /* req */, height /* req */, qualityLevel /* op */, preserveAspectRatio /* op */, rotateExif /* op */);

blob.size and blob.slice throw until the blob is complete. There would need to be a new event to say when the blob's size is ready.

Why it wasn't chosen:

It felt like it was changing how blobs worked to make the size throw. Also, this has some of the same disadvantages of as the SizelessBlob approach.


Using CSS of Image to designate dimensions instead of putting it in the API

Summary of approach: 

img.getBlob(mimeType /* req */,  successEvent /* req */, errorEvent /* op */, qualityLevel /* op */);

Why it wasn't chosen:

It would have been confusing to the user if getBlob took into account some CSS attributes but not others, and using all CSS tags posed a lot of unnecessary implementation complexity without any use cases.


Using the File object

Summary of approach: 

FileReader readResized(). The result would be a data url with the resized image.

Why it wasn't chosen:

We also examined making this function an instance method of the FileReader object, but this function did not fit well in the context of the File object. FileReader's result is a string, posing a problem for large results as well as an unnecessary conversion. One way another this problem is to use a URN (like File.urn), it would have prevented us from posting the result to a server as well as having the lifetime issues that File.urn does.


Having a very specialized method on FormData to do the resize and allow upload

Summary of approach: 

FormData.appendResized(DOMString name, Blob value, mimeType /* req */, with /* req */, height /* req */, qualityLevel /* op */, preserveAspectRatio /* op */, rotateExif /* op */);

Why it wasn't chosen:

This approach was over specialized and didn't allow for using the result on the web page at all which would be useful as a preview of exactly what is being uploaded.


Using canvas in a worker

Summary of approach: 

This is the OffscreenCanvas proposal which can be seen here: http://www.mail-archive.com/whatwg@lists.whatwg.org/msg20297.html

Why it wasn't chosen:

Due to making it rather complicated for the javascript user to accomplish their goal and the code complexity and the speed trade-offs that may change due to gpu acceleration, this idea didn't get traction and many folks suggested something simpler for the intended use cases. A few more drawbacks are that this doesn't obey exif automatically and it doesn't preserve the exif metadata.
Comment 1 David Levin 2010-05-28 16:01:05 PDT
Perhaps step 3.5 (or step 6) Add something (a global queue?) to ensure that not too many threads are created for this.
Comment 2 Mark Rowe (bdash) 2010-06-10 16:23:31 PDT
This doesn’t seem like a particularly well-designed API.

The name is poor. “getBlob” does not communicate that its purpose is for resizing.

The functionality is limited and the design of the API does not make it easy to extend in the future.  The API as proposed is limited to resizing.  I would imagine that cropping and rotation are two frequently performed operations on images, but there’s no obvious way to fit those in to this API.

The rotateExif argument seems out of place.  The API as proposed is about resizing, so it’s not clear why there’s an argument present that is related to rotation.

The use of boolean arguments leads to confusing call sites.  The developer has to memorize the order of the arguments since it’s impossible to derive by reading a call site.

The quality level argument seems somewhat ambiguous.  I guess that the quality level argument is similar to the quality level argument that canvas.toDataURL takes when passed image/jpeg as the MIME type, where it controls the quality of the JPEG encoding, but it’s not really clear to me.  However, many graphics frameworks have the concept of interpolation quality to control the tradeoff between speed and fidelity when resizing an image.  That’s the first “quality level” that comes to mind for me when I’m looking at an image resizing API.
Comment 3 Sterling Swigart 2010-06-11 13:37:49 PDT
We have rethought the exposure of this API, and some of the options. It will now be exposed as:

Image.webkitGetImage(outputType /* req */, successCallback /* req */, errorCallback /* op*/, resizeOptions /* op */);

If called without resizeOptions, getImage will return a blob of the image as is, without any resizing or rotation. The possible keys and their values in resizeOptions follow:

int width;                                       /* valid: 1+                          default: image width  */
int height;                                     /* valid: 1+                          default: image height */
float compressionLevel;             /* valid: 0.0 - 1.0               default: 0.85                */
boolean preserveAspectRatio;  /*                                           default: true                 */
String orientation;                       /* valid: "auto", "none"      default: "none"             */

The compressionLevel and preserveAspectRatio arguments function the same as before. For orientation, a value of "auto" will obey the EXIF orientation value, and a value of "none" will keep the same orientation. The other parameters and options work as described in the first spec I posted.


Sample Code:

// url contains location of an image file
Image i = new Image();
i.src = url;
var successEvt = function (newBlob) { myDiv.innerHTML += "<img src='" + newBlob.url + "' />"; };
var errEvt = function (err) { alert(err); };
i.webkitGetImage("image/png", successEvt, errEvt, { width : 300, height : 1000, orientation : "auto" });
// Image will retain aspect ratio and correct for EXIF rotation. If the source image was 700x700,
// the blob will represent a new image that is 300x300.


Patches will now come in the order:

     0. Add functionality for conditional compilation of added files.
     1. Create another thread (which will do the resizing in the future), and callback to the main thread
     2. Expose the function to javascript
     3. Resize the image and return a blob to JS
     4. Derive EXIF orientation metadata from image and rotate image accordingly if orientation == "auto"
     5. Transfer much of the metadata from the old image to the new image
Comment 4 Adam Barth 2010-06-21 18:40:43 PDT
> i.webkitGetImage("image/png", successEvt, errEvt, { width : 300, height : 1000, orientation : "auto" });

How will this API work in language with strict typing?
Comment 5 Mark Rowe (bdash) 2010-06-22 22:05:22 PDT
Orientation values of “auto” vs “none” don’t make a lot of sense to me.  What does it mean to have no orientation?
Comment 6 David Levin 2010-06-22 22:17:00 PDT
(In reply to comment #5)
> Orientation values of “auto” vs “none” don’t make a lot of sense to me.  What does it mean to have no orientation?

Perhaps, "auto" vs "0" (but no other numbers for now).
Comment 7 Simon Fraser (smfr) 2019-01-16 08:34:06 PST
Is this still a thing?