<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><br><div><div>On May 11, 2010, at 11:58 AM, Sterling Swigart wrote:</div><br class="Apple-interchange-newline"><blockquote type="cite"><meta charset="utf-8"><span class="Apple-style-span" style="font-family: arial, sans-serif; white-space: pre-wrap; "><div>I'm working with David Levin, and based on the feedback received regarding offscreen canvas, the proposal has been changed to address more specific scenarios. The main use case was resizing images, so we are proposing an asynchronous image resizing API. If you are curious about how we arrived at our API below, take a look at the "appendix" to view the alternatives we considered.</div></span></blockquote><div><br></div><div>I'm not clear on why this API is needed. Mozilla's feedback on the offscreen canvas proposal was that image resizing is plenty fast enough with no new API, if you use something like Direct2D to accelerate it, using only current Canvas APIs. This API seems much less general than offscreen canvas, so it's subject to the same criticism and you can't even make the argument that it also serves other use cases.</div><div><br></div><div>Regards,</div><div>Maciej</div><br><blockquote type="cite"><span class="Apple-style-span" style="font-family: arial, sans-serif; white-space: pre-wrap; "><div><br></div><div>Let us know what you think. Thanks!</div><div>Sterling</div><div><br></div><h1>Use Cases:</h1><div><br class="webkit-block-placeholder"></div><p>Begin with a user giving a local image file to a webpage. Then:</p><div><br class="webkit-block-placeholder"></div><p>1. In real-time chat, quickly give other users a thumbnail view of the image file.</p><p>2. Or, limit the size of an image file before uploading it to a web server.</p><div><br class="webkit-block-placeholder"></div><p><span style="font-size: 1.75em; font-weight: bold; ">Proposed Solution:</span></p><div><br class="webkit-block-placeholder"></div><p>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:</p><div><br class="webkit-block-placeholder"></div><p>getBlob(mimeType /* req */, width /* req */, height /* req */, successEvent /* req */, errorEvent /* op */, qualityLevel /* op */, preserveAspectRatio /* op */, rotateExif /* op */);</p><div><br class="webkit-block-placeholder"></div><p>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.</p><p>All EXIF metadata will be retained except for any saved thumbnails, and the EXIF rotation property will be appropriately modified.</p><p><span style="font-weight: bold; font-size: 1.75em; ">Se</span><span style="font-size: 1.75em; font-weight: bold; ">curity:</span></p><div><br class="webkit-block-placeholder"></div><p>If the image source is of a different origin than the script context, then getBlob raises a <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">SECURITY_ERR</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> exception.</p><div><br class="webkit-block-placeholder"></div><p><span style="font-size: 1.75em; font-weight: bold; ">Sample Code:</span></p><div><br class="webkit-block-placeholder"></div><p>// url contains location of an image file</p><p>Image i = new Image();</p><p>i.src = url;</p><div><br class="webkit-block-placeholder"></div><p>var successEvt = function (newBlob) { myDiv.innerHTML += "<img src='" + newBlob.url + "' />"; };</p><p>var errEvt = function (err) { alert(err); };</p><p>i.getBlob("image/jpeg", 300, 350, successEvt, errEvt, .55);</p><p>// Image will retain aspect ratio and correct for EXIF rotation. If the source image was 700x700,</p><p>//   the blob will represent a new image that is 300x300.</p><div><br class="webkit-block-placeholder"></div><div><br class="webkit-block-placeholder"></div><h1>That's all!</h1><h1></h1><h1></h1><h3 style="font-family: arial, sans-serif; font-size: small; ">Appendix: Alternatives considered </h3><p>For reference, we've also included a list of other designs that we thought of along with the reasons why they were dropped</p><div><br class="webkit-block-placeholder"></div><h4>Creating a new object for resizing</h4><p style="margin-left: 17px; "><span style="font-style: italic; ">Summary of approach:</span> </p><p style="margin-left: 34px; ">[NamedConstructor=ImageResizer(),</p><p style="margin-left: 34px; "> NamedConstructor=ImageResizer(blob, onsuccess),</p><p style="margin-left: 34px; "> NamedConstructor=ImageResizer(blob, onsuccess, onerror),</p><p style="margin-left: 34px; "> NamedConstructor=ImageResizer(blob, onsuccess, onerror, type),</p><p style="margin-left: 34px; "> NamedConstructor=ImageResizer(blob, onsuccess, onerror, type, width, height)]</p><p style="margin-left: 34px; ">
interface ImageResizer {</p><p style="margin-left: 34px; ">    void start(); // starts resize operation</p><p style="margin-left: 34px; ">    void abort(); // aborts operation</p><div style="margin-left: 34px; ">    <br class="webkit-block-placeholder"></div><p style="margin-left: 34px; ">
    attribute Blob blob;</p><p style="margin-left: 34px; ">    attribute DOMString type; // default "image/png"</p><p style="margin-left: 34px; ">    attribute unsigned long width; </p><p style="margin-left: 34px; ">
    attribute unsigned long height; </p><p style="margin-left: 34px; ">    attribute float qualityLevel; // default 1.0, must be 0.0 to 1.0, else reverts to default</p><div style="margin-left: 34px; ">    <br class="webkit-block-placeholder"></div><p style="margin-left: 34px; ">
    readonly attribute unsigned short started; // default 0</p><div style="margin-left: 34px; ">    <br class="webkit-block-placeholder"></div><p style="margin-left: 34px; ">    attribute Function onsuccess;</p><p style="margin-left: 34px; ">    attribute Function onerror;</p><p style="margin-left: 34px; ">};</p><div style="margin-left: 34px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-style: italic; ">W</span><span style="font-style: italic; ">h</span><span style="font-style: italic; ">y it</span><span style="font-style: italic; "> was</span><span style="font-style: italic; ">n't chosen:</span></p><p style="margin-left: 17px; ">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.</p><div><br class="webkit-block-placeholder"></div>
<h4>Returning a SizelessBlob immediately from a method on image</h4><p style="margin-left: 17px; "><span style="font-style: italic; ">S</span><span style="font-style: italic; ">u</span><span style="font-style: italic; ">mm</span><span style="font-style: italic; ">ar</span><span style="font-style: italic; ">y of</span><span style="font-style: italic; "> approach:</span> </p><p style="margin-left: 17px; ">var streamingBlob = image.toStreamingBlob(mimeType /* req */, width /* req */, height /* req */, qualityLevel /* op */, preserveAspectRatio /* op */, rotateExif /* op */);</p><div style="margin-left: 17px; ">
<br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">Ne</span><span style="font-family: sans-serif; ">w Blob Interfaces:</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">in</span><span style="font-family: sans-serif; ">terface</span><span style="font-family: sans-serif; "> </span><span style="font-family: sans-serif; ">Siz</span><span style="font-family: sans-serif; ">e</span><span style="font-family: sans-serif; ">lessBlob {</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">       // moved from Blob</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">      readonly attribute DOMString type;</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">      readonly attribute DOMString url; // whatever name -- URL, urn, URN, etc.</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">}</span></p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">interface StreamingBlob : SizelessBlob {</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">                     // at most one of the following functions will be called for a single FutureBlob</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">                     attribute Function onblobready;</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">                     attribute Function onerror;</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">      readyonly attribute blob; // throws an exception if accessed before onblobready is called.</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">}</span></p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">interface Blob : SizelessBlob {</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">      readonly attribute unsigned long long size;</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">      Blob slice(in long long start,</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">                     in long long length); // raises DOMException</span></p><p style="margin-left: 17px; "><span style="font-family: sans-serif; ">};</span></p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-style: italic; ">Wh</span><span style="font-style: italic; ">y it wasn</span><span style="font-style: italic; ">'t ch</span><span style="font-style: italic; ">osen</span><span style="font-style: italic; ">:</span></p>
<ul><li>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 <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">xhr</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> is done using the resized image.)</li>
<li>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)</li><li>introduction of multiple blobs which may be confusing to developers</li>
<li>the need to change all existing specs to use SizelessBlob instead of Blob</li></ul><div><br class="webkit-block-placeholder"></div><h4>Returning a Blob immediately from a method on image</h4><p style="margin-left: 17px; "><span style="font-style: italic; ">Su</span><span style="font-style: italic; ">mmary of approach:</span> </p><p style="margin-left: 17px; ">var blob = image.toBlob(mimeType /* req */, width /* req */, height /* req */, qualityLevel /* op */, preserveAspectRatio /* op */, rotateExif /* op */);</p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; ">blob.size and blob.slice throw until the blob is complete. There would need to be a new event to say when the <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">blob's</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> size is ready.</p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-style: italic; ">W</span><span style="font-style: italic; ">h</span><span style="font-style: italic; ">y it wasn't chosen:</span></p><p style="margin-left: 17px; ">It felt like it was changing how <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">blobs</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> worked to make the size throw. Also, this has some of the same disadvantages of as the <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">SizelessBlob</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> approach.</p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><h4>Using CSS of Image to designate dimensions instead of putting it in the API</h4><p style="margin-left: 17px; "><span style="font-style: italic; ">Su</span><span style="font-style: italic; ">mm</span><span style="font-style: italic; ">ary of </span><span style="font-style: italic; ">appr</span><span style="font-style: italic; ">oach:</span> </p><p style="margin-left: 17px; ">img.getBlob(mimeType /* req */,  successEvent /* req */, errorEvent /* op */, qualityLevel /* op */);</p><div style="margin-left: 34px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-style: italic; ">Wh</span><span style="font-style: italic; ">y </span><span style="font-style: italic; ">it wasn't chosen:</span></p><p style="margin-left: 17px; ">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.</p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><h4>Using the File object</h4><p style="margin-left: 17px; "><span style="font-style: italic; ">Su</span><span style="font-style: italic; ">mm</span><span style="font-style: italic; ">ary of approach</span><span style="font-style: italic; ">:</span> </p><p style="margin-left: 17px; ">FileReader readResized(). The result would be a data url with the resized image.</p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-style: italic; ">Wh</span><span style="font-style: italic; ">y it wasn't chosen:</span></p><p style="margin-left: 17px; ">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.</p><div><br class="webkit-block-placeholder"></div><h4>Having a very specialized method on FormData to do the resize and allow upload</h4><p style="margin-left: 17px; "><span style="font-style: italic; ">Summ</span><span style="font-style: italic; ">ary of approach:</span> </p><p style="margin-left: 17px; ">FormData.<span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">appendResized</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span>(DOMString name, Blob value, mimeType /* req */, with /* req */, height /* req */, qualityLevel /* <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">op</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> */, preserveAspectRatio /* <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">op</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> */, <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">rotateExif</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> /* <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">op</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> */);</p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-style: italic; ">Why it wasn't chosen:</span></p><p style="margin-left: 17px; ">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.</p><div><br class="webkit-block-placeholder"></div><h4>Using canvas in a worker</h4><p style="margin-left: 17px; "><span style="font-style: italic; ">Su</span><span style="font-style: italic; ">mmary of approach:</span> </p><p style="margin-left: 17px; ">This is the <span style="border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(204, 0, 0); ">OffscreenCanvas</span><span style="-webkit-user-modify: read-only; "><span style="white-space: normal; "></span></span> proposal which can be seen here: <a href="http://www.mail-archive.com/whatwg@lists.whatwg.org/msg20297.html" target="_blank" style="font-family: arial, sans-serif; color: rgb(0, 62, 168); ">h</a><a href="http://www.mail-archive.com/whatwg@lists.whatwg.org/msg20297.html" target="_blank" style="font-family: arial, sans-serif; color: rgb(0, 62, 168); ">ttp</a><a href="http://www.mail-archive.com/whatwg@lists.whatwg.org/msg20297.html" target="_blank" style="font-family: arial, sans-serif; color: rgb(0, 62, 168); ">:</a><a href="http://www.mail-archive.com/whatwg@lists.whatwg.org/msg20297.html" target="_blank" style="font-family: arial, sans-serif; color: rgb(0, 62, 168); ">//www.mail-archive.com/whatwg@lists.whatwg.org/msg20297.html</a></p><div style="margin-left: 17px; "><br class="webkit-block-placeholder"></div><p style="margin-left: 17px; "><span style="font-style: italic; ">Why it wasn't chosen:</span></p><p style="margin-left: 17px; ">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.<span class="__wave_paste"></span><span> </span></p>
</span>
</blockquote></div><br></body></html>