Commit c8bfc082 authored by xlai@chromium.org's avatar xlai@chromium.org

The toBlob method in HTMLCanvasElement has been implemented.

Changes include:
(1) HTMLCanvasElement: 

The toBlob function is implemented in a similar fashion as the existing toDataURL function, taking in parameters like mimeType and qualityArgument. It has an additional argument FileCallback which accepts user-defined callback function.

(2) ImageDataBuffer:

encodeImage is made public.

(3) File:

A new create factory method is added to allow HTMLCanvasElement to create a new File object using the blob data. File object is a subtype of Blob object; it has two additional attributes -- name, last modification time -- as compared to Blob. We use empty string and current time to fill in these two attributes; FF creates the latter attribute in the same way.

(4) FileCallback: 

Moved from module/ to core/. 



BUG=67587

Review URL: https://codereview.chromium.org/1257253004

git-svn-id: svn://svn.chromium.org/blink/trunk@201327 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 20511984
......@@ -817,6 +817,16 @@ crbug.com/364614 [ Mac ] virtual/threaded/fast/scroll-behavior/overflow-scroll-r
crbug.com/364614 [ Mac ] virtual/scroll_customization/fast/scroll-behavior/overflow-scroll-root-frame-animates.html [ Skip ]
crbug.com/364614 [ Mac ] virtual/threaded_animation_timelines/fast/scroll-behavior/overflow-scroll-root-frame-animates.html [ Skip ]
crbug.com/67587 fast/canvas/canvas-toBlob-jpeg-maximum-quality.html [ NeedsRebaseline ]
crbug.com/67587 fast/canvas/canvas-toBlob-jpeg-medium-quality.html [ NeedsRebaseline ]
crbug.com/67587 fast/canvas/canvas-toBlob-webp-maximum-quality.html [ NeedsRebaseline ]
crbug.com/67587 virtual/display_list_2d_canvas/fast/canvas/canvas-toBlob-jpeg-maximum-quality.html [ NeedsRebaseline ]
crbug.com/67587 virtual/display_list_2d_canvas/fast/canvas/canvas-toBlob-jpeg-medium-quality.html [ NeedsRebaseline ]
crbug.com/67587 virtual/display_list_2d_canvas/fast/canvas/canvas-toBlob-webp-maximum-quality.html [ NeedsRebaseline ]
crbug.com/67587 virtual/gpu/fast/canvas/canvas-toBlob-jpeg-maximum-quality.html [ NeedsRebaseline ]
crbug.com/67587 virtual/gpu/fast/canvas/canvas-toBlob-jpeg-medium-quality.html [ NeedsRebaseline ]
crbug.com/67587 virtual/gpu/fast/canvas/canvas-toBlob-webp-maximum-quality.html [ NeedsRebaseline ]
crbug.com/524596 paint/invalidation/composited-non-stacking-context-descendant-change-color.html [ ImageOnlyFailure ]
crbug.com/524596 paint/invalidation/composited-non-stacking-context-descendant-move.html [ ImageOnlyFailure ]
crbug.com/524596 paint/invalidation/fixed-position-descendant-paint-offset-indirect.html [ ImageOnlyFailure ]
......
Test that toBlob(mimeType) ignores the case of 'mimeType'.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS successfullyParsed is true
TEST COMPLETE
PASS
PASS
PASS
PASS
<script src = "../../resources/js-test.js"></script>
<script type = "text/javascript">
if (window.testRunner)
{
testRunner.dumpAsText();
testRunner.waitUntilDone();
}
description("Test that toBlob(mimeType) ignores the case of 'mimeType'.");
canvas = document.createElement('canvas');
var counter;
function tryMimeType(mimeType, expectedMimeType)
{
canvas.toBlob(function(blob) {
if (blob.type === expectedMimeType) {
testPassed("");
}
else {
testFailed(blob.type + " does not match " + expectedMimeType);
}
counter = counter - 1;
if (window.testRunner) {
if (counter == 0) {
testRunner.notifyDone();
}
}
}, mimeType);
}
counter = 4;
//Note that due to the async nature of toBlob, these callbacks may complete
// at random order but they will all print PASS when they pass.
tryMimeType("image/PNG", "image/png");
tryMimeType("imaGE/jpEg", "image/jpeg");
tryMimeType("ImAgE/WeBp", "image/webp");
//Unsupported mime type falls back to png
tryMimeType("image/bmp", "image/png");
</Script>
Test that verifies whether the image data survives the toBlob process
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS successfullyParsed is true
TEST COMPLETE
PASS ctx.getImageData(0, 0, 150, 75).data[4] is ctx2.getImageData(0, 0, 150, 75).data[4]
<script src = "../../resources/js-test.js"></script>
<script type = 'text/javascript'>
description("Test that verifies whether the image data survives the toBlob process");
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0, 0, 150, 75);
var canvas2 = document.createElement("canvas");
var ctx2 = canvas2.getContext("2d");
var newImg = new Image();
newImg.onload = function() {
ctx2.drawImage(newImg, 0, 0, 150, 75);
//Randomly pick a pixel to check whether they match
shouldBe('ctx.getImageData(0, 0, 150, 75).data[4]', 'ctx2.getImageData(0, 0, 150, 75).data[4]');
if (window.testRunner)
testRunner.notifyDone();
}
canvas.toBlob(function(blob) {
url = URL.createObjectURL(blob);
newImg.src = url;
});
</script>
<canvas id="mycanvas"></canvas>
<img id="result" onload="testDone()">
<script type = 'text/javascript'>
if (window.testRunner) {
testRunner.dumpAsTextWithPixelResults();
testRunner.waitUntilDone();
}
function testDone()
{
if (window.testRunner)
testRunner.notifyDone();
}
var image = new Image();
image.onload = function() {
var canvas = document.getElementById("mycanvas");
canvas.width = image.width;
canvas.height = image.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
canvas.toBlob(function(blob) {
url = URL.createObjectURL(blob);
result.src = url;
}, "image/jpeg", 1.0);
}
image.src = "resources/letters.png";
</script>
<canvas id="mycanvas"></canvas>
<img id="result" onload="testDone()">
<script type = 'text/javascript'>
if (window.testRunner) {
testRunner.dumpAsTextWithPixelResults();
testRunner.waitUntilDone();
}
function testDone()
{
if (window.testRunner)
testRunner.notifyDone();
}
var image = new Image();
image.onload = function() {
var canvas = document.getElementById("mycanvas");
canvas.width = image.width;
canvas.height = image.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
canvas.toBlob(function(blob) {
url = URL.createObjectURL(blob);
result.src = url;
}, "image/jpeg", 0.2);
}
image.src = "resources/letters.png";
</script>
<canvas id="mycanvas"></canvas>
<img id="result" onload="testDone()">
<script type = 'text/javascript'>
if (window.testRunner) {
testRunner.dumpAsTextWithPixelResults();
testRunner.waitUntilDone();
}
function testDone()
{
if (window.testRunner)
testRunner.notifyDone();
}
var image = new Image();
image.onload = function() {
var canvas = document.getElementById("mycanvas");
canvas.width = image.width;
canvas.height = image.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
canvas.toBlob(function(blob) {
url = URL.createObjectURL(blob);
result.src = url;
}, "image/webp", 1.0);
}
image.src = "resources/letters.png";
</script>
......@@ -49,4 +49,14 @@ test(function () {
assert_unreached('toDataURL should throw');
});
}, 'toDataURL');
test(function () {
assert_throws(
"SecurityError",
function() {
context.drawImage(image, 0, 0, 100, 100);
canvas.toBlob(function(blob){});
assert_unreached('toBlob should throw');
});
}, 'toBlob');
</script>
......@@ -395,6 +395,7 @@ html element button
html element canvas
property getContext
property height
property toBlob
property toDataURL
property width
html element caption
......
......@@ -1688,6 +1688,7 @@ interface HTMLCanvasElement
getter width
method constructor
method getContext
method toBlob
method toDataURL
setter height
setter width
......
......@@ -130,6 +130,7 @@
'events/WheelEvent.idl',
'fileapi/Blob.idl',
'fileapi/File.idl',
'fileapi/FileCallback.idl',
'fileapi/FileError.idl',
'fileapi/FileList.idl',
'fileapi/FileReader.idl',
......@@ -1483,6 +1484,7 @@
'fileapi/Blob.h',
'fileapi/File.cpp',
'fileapi/File.h',
'fileapi/FileCallback.h',
'fileapi/FileError.cpp',
'fileapi/FileError.h',
'fileapi/FileList.cpp',
......
......@@ -31,6 +31,7 @@
#include "core/fileapi/FilePropertyBag.h"
#include "platform/FileMetadata.h"
#include "platform/MIMETypeRegistry.h"
#include "platform/blob/BlobData.h"
#include "public/platform/Platform.h"
#include "public/platform/WebFileUtilities.h"
#include "wtf/CurrentTime.h"
......@@ -101,7 +102,6 @@ File* File::create(const HeapVector<BlobOrStringOrArrayBufferViewOrArrayBuffer>&
lastModified = static_cast<double>(options.lastModified());
else
lastModified = currentTimeMS();
ASSERT(options.hasEndings());
bool normalizeLineEndingsToNative = options.endings() == "native";
......@@ -113,6 +113,19 @@ File* File::create(const HeapVector<BlobOrStringOrArrayBufferViewOrArrayBuffer>&
return File::create(fileName, lastModified, BlobDataHandle::create(blobData.release(), fileSize));
}
File* File::create(char* data, size_t bytes, const String& mimeType)
{
ASSERT(data);
OwnPtr<BlobData> blobData = BlobData::create();
blobData->setContentType(mimeType);
blobData->appendBytes(data, bytes);
long long blobSize = blobData->length();
// create blob as the type of file with two additional attributes -- name and lastModificationTime
return File::create("", currentTimeMS(), BlobDataHandle::create(blobData.release(), blobSize));
}
File* File::createWithRelativePath(const String& path, const String& relativePath)
{
File* file = new File(path, File::AllContentTypes, File::IsUserVisible);
......
......@@ -68,6 +68,8 @@ public:
return new File(name, modificationTime, blobDataHandle);
}
static File* create(char* data, size_t bytes, const String& mimeType);
// For deserialization.
static File* createFromSerialization(const String& path, const String& name, const String& relativePath, UserVisibility userVisibility, bool hasSnaphotData, uint64_t size, double lastModified, PassRefPtr<BlobDataHandle> blobDataHandle)
{
......
......@@ -34,6 +34,7 @@
#include "core/HTMLNames.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
#include "core/fileapi/File.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/ImageData.h"
......@@ -54,6 +55,8 @@
#include "platform/graphics/gpu/AcceleratedImageBufferSurface.h"
#include "platform/transforms/AffineTransform.h"
#include "public/platform/Platform.h"
#include "public/platform/WebTraceLocation.h"
#include "wtf/Functional.h"
#include <math.h>
#include <v8.h>
......@@ -538,6 +541,40 @@ String HTMLCanvasElement::toDataURL(const String& mimeType, const ScriptValue& q
return toDataURLInternal(mimeType, qualityPtr, BackBuffer);
}
void HTMLCanvasElement::toBlob(FileCallback* callback, const String& mimeType, const ScriptValue& qualityArgument, ExceptionState& exceptionState) const
{
if (!originClean()) {
exceptionState.throwSecurityError("Tainted canvases may not be exported.");
return;
}
File* resultBlob = nullptr;
if (!isPaintable()) {
// If the canvas element's bitmap has no pixels
return;
}
double quality;
if (!qualityArgument.isEmpty()) {
v8::Local<v8::Value> v8Value = qualityArgument.v8Value();
if (v8Value->IsNumber()) {
quality = v8Value.As<v8::Number>()->Value();
}
}
String encodingMimeType = toEncodingMimeType(mimeType);
ImageData* imageData = toImageData(BackBuffer);
ScopedDisposal<ImageData> disposer(imageData);
// Perform image encoding
Vector<char> encodedImage;
ImageDataBuffer(imageData->size(), imageData->data()->data()).encodeImage(encodingMimeType, &quality, &encodedImage);
resultBlob = File::create(encodedImage.data(), encodedImage.size(), encodingMimeType);
Platform::current()->mainThread()->postTask(FROM_HERE, bind(&FileCallback::handleEvent, callback, resultBlob));
}
SecurityOrigin* HTMLCanvasElement::securityOrigin() const
{
return document().securityOrigin();
......
......@@ -33,6 +33,7 @@
#include "core/CoreExport.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentVisibilityObserver.h"
#include "core/fileapi/FileCallback.h"
#include "core/html/HTMLElement.h"
#include "core/html/canvas/CanvasImageSource.h"
#include "platform/geometry/FloatRect.h"
......@@ -113,6 +114,9 @@ public:
String toDataURL(const String& mimeType, const ScriptValue& qualityArgument, ExceptionState&) const;
String toDataURL(const String& mimeType, ExceptionState& exceptionState) const { return toDataURL(mimeType, ScriptValue(), exceptionState); }
void toBlob(FileCallback*, const String& mimeType, const ScriptValue& qualityArgument, ExceptionState&) const;
void toBlob(FileCallback* callback, const String& mimeType, ExceptionState& exceptionState) { return toBlob(callback, mimeType, ScriptValue(), exceptionState); }
// Used for rendering
void didDraw(const FloatRect&);
void notifyObserversCanvasChanged(const FloatRect&);
......
......@@ -26,7 +26,8 @@
// https://html.spec.whatwg.org/#the-canvas-element
interface HTMLCanvasElement : HTMLElement {
interface HTMLCanvasElement : HTMLElement
{
// FIXME: width and height should be unsigned long.
attribute long width;
attribute long height;
......@@ -51,4 +52,6 @@ interface HTMLCanvasElement : HTMLElement {
// only one extra argument is actually used.
// FIXME: type should not have a default value.
[RaisesException] DOMString toDataURL(optional DOMString type = null, optional any arguments);
[RaisesException, RuntimeEnabled=ExperimentalCanvasFeatures] void toBlob(FileCallback? _callback, optional DOMString type = null, optional any arguments);
};
......@@ -31,10 +31,10 @@
#include "config.h"
#include "modules/filesystem/DOMFileSystem.h"
#include "core/fileapi/FileCallback.h"
#include "modules/filesystem/DOMFilePath.h"
#include "modules/filesystem/DirectoryEntry.h"
#include "modules/filesystem/ErrorCallback.h"
#include "modules/filesystem/FileCallback.h"
#include "modules/filesystem/FileEntry.h"
#include "modules/filesystem/FileSystemCallbacks.h"
#include "modules/filesystem/FileWriter.h"
......
......@@ -32,9 +32,9 @@
#include "modules/filesystem/FileEntry.h"
#include "core/fileapi/File.h"
#include "core/fileapi/FileCallback.h"
#include "modules/filesystem/DOMFileSystem.h"
#include "modules/filesystem/ErrorCallback.h"
#include "modules/filesystem/FileCallback.h"
#include "modules/filesystem/FileWriterCallback.h"
namespace blink {
......
......@@ -33,6 +33,7 @@
#include "core/dom/ExecutionContext.h"
#include "core/fileapi/File.h"
#include "core/fileapi/FileCallback.h"
#include "core/fileapi/FileError.h"
#include "core/html/VoidCallback.h"
#include "core/inspector/InspectorInstrumentation.h"
......@@ -44,7 +45,6 @@
#include "modules/filesystem/Entry.h"
#include "modules/filesystem/EntryCallback.h"
#include "modules/filesystem/ErrorCallback.h"
#include "modules/filesystem/FileCallback.h"
#include "modules/filesystem/FileEntry.h"
#include "modules/filesystem/FileSystemCallback.h"
#include "modules/filesystem/FileWriterBase.h"
......
......@@ -38,6 +38,7 @@
#include "core/dom/Document.h"
#include "core/events/Event.h"
#include "core/fileapi/File.h"
#include "core/fileapi/FileCallback.h"
#include "core/fileapi/FileError.h"
#include "core/fileapi/FileReader.h"
#include "core/frame/LocalFrame.h"
......@@ -53,7 +54,6 @@
#include "modules/filesystem/Entry.h"
#include "modules/filesystem/EntryCallback.h"
#include "modules/filesystem/ErrorCallback.h"
#include "modules/filesystem/FileCallback.h"
#include "modules/filesystem/FileEntry.h"
#include "modules/filesystem/FileSystemCallbacks.h"
#include "modules/filesystem/LocalFileSystem.h"
......
......@@ -72,7 +72,6 @@
'filesystem/EntryCallback.idl',
'filesystem/EntrySync.idl',
'filesystem/ErrorCallback.idl',
'filesystem/FileCallback.idl',
'filesystem/FileEntry.idl',
'filesystem/FileEntrySync.idl',
'filesystem/FileSystemCallback.idl',
......@@ -963,7 +962,6 @@
'filesystem/EntrySync.cpp',
'filesystem/EntrySync.h',
'filesystem/ErrorCallback.h',
'filesystem/FileCallback.h',
'filesystem/FileEntry.cpp',
'filesystem/FileEntry.h',
'filesystem/FileEntrySync.cpp',
......
......@@ -341,7 +341,7 @@ void ImageBuffer::putByteArray(Multiply multiplied, const unsigned char* source,
m_surface->writePixels(info, srcAddr, srcBytesPerRow, destX, destY);
}
static bool encodeImage(const ImageDataBuffer& source, const String& mimeType, const double* quality, Vector<char>* output)
bool ImageDataBuffer::encodeImage(const String& mimeType, const double* quality, Vector<char>* output) const
{
Vector<unsigned char>* encodedImage = reinterpret_cast<Vector<unsigned char>*>(output);
......@@ -349,16 +349,16 @@ static bool encodeImage(const ImageDataBuffer& source, const String& mimeType, c
int compressionQuality = JPEGImageEncoder::DefaultCompressionQuality;
if (quality && *quality >= 0.0 && *quality <= 1.0)
compressionQuality = static_cast<int>(*quality * 100 + 0.5);
if (!JPEGImageEncoder::encode(source, compressionQuality, encodedImage))
if (!JPEGImageEncoder::encode(*this, compressionQuality, encodedImage))
return false;
} else if (mimeType == "image/webp") {
int compressionQuality = WEBPImageEncoder::DefaultCompressionQuality;
if (quality && *quality >= 0.0 && *quality <= 1.0)
compressionQuality = static_cast<int>(*quality * 100 + 0.5);
if (!WEBPImageEncoder::encode(source, compressionQuality, encodedImage))
if (!WEBPImageEncoder::encode(*this, compressionQuality, encodedImage))
return false;
} else {
if (!PNGImageEncoder::encode(source, encodedImage))
if (!PNGImageEncoder::encode(*this, encodedImage))
return false;
ASSERT(mimeType == "image/png");
}
......@@ -371,7 +371,7 @@ String ImageDataBuffer::toDataURL(const String& mimeType, const double* quality)
ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
Vector<char> encodedImage;
if (!encodeImage(*this, mimeType, quality, &encodedImage))
if (!encodeImage(mimeType, quality, &encodedImage))
return "data:,";
return "data:" + mimeType + ";base64," + base64Encode(encodedImage);
......
......@@ -43,6 +43,8 @@
#include "wtf/PassOwnPtr.h"
#include "wtf/PassRefPtr.h"
#include "wtf/Uint8ClampedArray.h"
#include "wtf/Vector.h"
#include "wtf/text/WTFString.h"
namespace WTF {
......@@ -152,6 +154,7 @@ private:
struct ImageDataBuffer {
ImageDataBuffer(const IntSize& size, unsigned char* data) : m_data(data), m_size(size) { }
String PLATFORM_EXPORT toDataURL(const String& mimeType, const double* quality) const;
bool PLATFORM_EXPORT encodeImage(const String& mimeType, const double* quality, Vector<char>* output) const;
unsigned char* pixels() const { return m_data; }
int height() const { return m_size.height(); }
int width() const { return m_size.width(); }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment