1/*
2 * Copyright (C) 2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ImageBitmap.h"
28
29#include "BitmapImage.h"
30#include "Blob.h"
31#include "CachedImage.h"
32#include "ExceptionOr.h"
33#include "FileReaderLoader.h"
34#include "FileReaderLoaderClient.h"
35#include "GraphicsContext.h"
36#include "HTMLCanvasElement.h"
37#include "HTMLImageElement.h"
38#include "HTMLVideoElement.h"
39#include "ImageBitmapOptions.h"
40#include "ImageBuffer.h"
41#include "ImageData.h"
42#include "IntRect.h"
43#include "JSImageBitmap.h"
44#include "LayoutSize.h"
45#include "RenderElement.h"
46#include "SharedBuffer.h"
47#include "TypedOMCSSImageValue.h"
48#include <wtf/IsoMallocInlines.h>
49#include <wtf/Optional.h>
50#include <wtf/StdLibExtras.h>
51#include <wtf/Variant.h>
52
53namespace WebCore {
54
55WTF_MAKE_ISO_ALLOCATED_IMPL(ImageBitmap);
56
57#if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
58static RenderingMode bufferRenderingMode = Accelerated;
59#else
60static RenderingMode bufferRenderingMode = Unaccelerated;
61#endif
62
63Ref<ImageBitmap> ImageBitmap::create(IntSize size)
64{
65 return create(ImageBuffer::create(FloatSize(size.width(), size.height()), bufferRenderingMode));
66}
67
68Ref<ImageBitmap> ImageBitmap::create(std::pair<std::unique_ptr<ImageBuffer>, bool>&& buffer)
69{
70 auto imageBitmap = create(WTFMove(buffer.first));
71 imageBitmap->m_originClean = buffer.second;
72 return imageBitmap;
73}
74
75Ref<ImageBitmap> ImageBitmap::create(std::unique_ptr<ImageBuffer>&& buffer)
76{
77 return adoptRef(*new ImageBitmap(WTFMove(buffer)));
78}
79
80void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, ImageBitmap::Source&& source, ImageBitmapOptions&& options, ImageBitmap::Promise&& promise)
81{
82 WTF::switchOn(source,
83 [&] (auto& specificSource) {
84 createPromise(scriptExecutionContext, specificSource, WTFMove(options), WTF::nullopt, WTFMove(promise));
85 }
86 );
87}
88
89Vector<std::pair<std::unique_ptr<ImageBuffer>, bool>> ImageBitmap::detachBitmaps(Vector<RefPtr<ImageBitmap>>&& bitmaps)
90{
91 Vector<std::pair<std::unique_ptr<ImageBuffer>, bool>> buffers;
92 for (auto& bitmap : bitmaps)
93 buffers.append(std::make_pair(bitmap->transferOwnershipAndClose(), bitmap->originClean()));
94 return buffers;
95}
96
97
98void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, ImageBitmap::Source&& source, ImageBitmapOptions&& options, int sx, int sy, int sw, int sh, ImageBitmap::Promise&& promise)
99{
100 // 1. If either the sw or sh arguments are specified but zero, return a promise
101 // rejected with an "RangeError" DOMException and abort these steps.
102 if (!sw || !sh) {
103 promise.reject(RangeError, "Cannot create ImageBitmap with a width or height of 0");
104 return;
105 }
106
107 auto left = sw >= 0 ? sx : sx + sw;
108 auto top = sh >= 0 ? sy : sy + sh;
109 auto width = std::abs(sw);
110 auto height = std::abs(sh);
111
112 WTF::switchOn(source,
113 [&] (auto& specificSource) {
114 createPromise(scriptExecutionContext, specificSource, WTFMove(options), IntRect { left, top, width, height }, WTFMove(promise));
115 }
116 );
117}
118
119static bool taintsOrigin(CachedImage& cachedImage)
120{
121 auto* image = cachedImage.image();
122 if (!image)
123 return false;
124
125 if (image->sourceURL().protocolIsData())
126 return false;
127
128 if (!image->hasSingleSecurityOrigin())
129 return true;
130
131 if (!cachedImage.isCORSSameOrigin())
132 return true;
133
134 return false;
135}
136
137#if ENABLE(VIDEO)
138static bool taintsOrigin(SecurityOrigin* origin, HTMLVideoElement& video)
139{
140 if (!video.hasSingleSecurityOrigin())
141 return true;
142
143 if (video.player()->didPassCORSAccessCheck())
144 return false;
145
146 auto url = video.currentSrc();
147 if (url.protocolIsData())
148 return false;
149
150 return !origin->canRequest(url);
151}
152#endif
153
154// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#cropped-to-the-source-rectangle-with-formatting
155static ExceptionOr<IntRect> croppedSourceRectangleWithFormatting(IntSize inputSize, ImageBitmapOptions& options, Optional<IntRect> rect)
156{
157 // 2. If either or both of resizeWidth and resizeHeight members of options are less
158 // than or equal to 0, then return a promise rejected with "InvalidStateError"
159 // DOMException and abort these steps.
160 if ((options.resizeWidth && options.resizeWidth.value() <= 0) || (options.resizeHeight && options.resizeHeight.value() <= 0))
161 return Exception { InvalidStateError, "Invalid resize dimensions" };
162
163 // 3. If sx, sy, sw and sh are specified, let sourceRectangle be a rectangle whose
164 // corners are the four points (sx, sy), (sx+sw, sy),(sx+sw, sy+sh), (sx,sy+sh).
165 // Otherwise let sourceRectangle be a rectangle whose corners are the four points
166 // (0,0), (width of input, 0), (width of input, height of input), (0, height of
167 // input).
168 auto sourceRectangle = rect.valueOr(IntRect { 0, 0, inputSize.width(), inputSize.height() });
169
170 // 4. Clip sourceRectangle to the dimensions of input.
171 sourceRectangle.intersect(IntRect { 0, 0, inputSize.width(), inputSize.height() });
172
173 return { WTFMove(sourceRectangle) };
174}
175
176static IntSize outputSizeForSourceRectangle(IntRect sourceRectangle, ImageBitmapOptions& options)
177{
178 // 5. Let outputWidth be determined as follows:
179 auto outputWidth = [&] () -> int {
180 if (options.resizeWidth)
181 return options.resizeWidth.value();
182 if (options.resizeHeight)
183 return ceil(sourceRectangle.width() * static_cast<double>(options.resizeHeight.value()) / sourceRectangle.height());
184 return sourceRectangle.width();
185 }();
186
187 // 6. Let outputHeight be determined as follows:
188 auto outputHeight = [&] () -> int {
189 if (options.resizeHeight)
190 return options.resizeHeight.value();
191 if (options.resizeWidth)
192 return ceil(sourceRectangle.height() * static_cast<double>(options.resizeWidth.value()) / sourceRectangle.width());
193 return sourceRectangle.height();
194 }();
195
196 return { outputWidth, outputHeight };
197}
198
199static InterpolationQuality interpolationQualityForResizeQuality(ImageBitmapOptions::ResizeQuality resizeQuality)
200{
201 switch (resizeQuality) {
202 case ImageBitmapOptions::ResizeQuality::Pixelated:
203 return InterpolationNone;
204 case ImageBitmapOptions::ResizeQuality::Low:
205 return InterpolationDefault; // Low is the default.
206 case ImageBitmapOptions::ResizeQuality::Medium:
207 return InterpolationMedium;
208 case ImageBitmapOptions::ResizeQuality::High:
209 return InterpolationHigh;
210 }
211 ASSERT_NOT_REACHED();
212 return InterpolationDefault;
213}
214
215// FIXME: More steps from https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#cropped-to-the-source-rectangle-with-formatting
216
217// 7. Place input on an infinite transparent black grid plane, positioned so that its
218// top left corner is at the origin of the plane, with the x-coordinate increasing
219// to the right, and the y-coordinate increasing down, and with each pixel in the
220// input image data occupying a cell on the plane's grid.
221
222// 8. Let output be the rectangle on the plane denoted by sourceRectangle.
223
224// 9. Scale output to the size specified by outputWidth and outputHeight. The user
225// agent should use the value of the resizeQuality option to guide the choice of
226// scaling algorithm.
227
228// 10. If the value of the imageOrientation member of options is "flipY", output must
229// be flipped vertically, disregarding any image orientation metadata of the source
230// (such as EXIF metadata), if any.
231
232// 11. If image is an img element or a Blob object, let val be the value of the
233// colorSpaceConversion member of options, and then run these substeps:
234//
235// 1. If val is "default", the color space conversion behavior is implementation-specific,
236// and should be chosen according to the color space that the implementation uses for
237// drawing images onto the canvas.
238//
239// 2. If val is "none", output must be decoded without performing any color space
240// conversions. This means that the image decoding algorithm must ignore color profile
241// metadata embedded in the source data as well as the display device color profile.
242
243// 12. Let val be the value of premultiplyAlpha member of options, and then run these substeps:
244//
245// 1. If val is "default", the alpha premultiplication behavior is implementation-specific,
246// and should be chosen according to implementation deems optimal for drawing images
247// onto the canvas.
248//
249// 2. If val is "premultiply", the output that is not premultiplied by alpha must have its
250// color components multiplied by alpha and that is premultiplied by alpha must be left
251// untouched.
252//
253// 3. If val is "none", the output that is not premultiplied by alpha must be left untouched
254// and that is premultiplied by alpha must have its color components divided by alpha.
255
256// 13. Return output.
257
258void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<HTMLImageElement>& imageElement, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
259{
260 // 2. If image is not completely available, then return a promise rejected with
261 // an "InvalidStateError" DOMException and abort these steps.
262
263 auto* cachedImage = imageElement->cachedImage();
264 if (!cachedImage || !imageElement->complete()) {
265 promise.reject(InvalidStateError, "Cannot create ImageBitmap that is not completely available");
266 return;
267 }
268
269 // 3. If image's media data has no intrinsic dimensions (e.g. it's a vector graphic
270 // with no specified content size), and both or either of the resizeWidth and
271 // resizeHeight options are not specified, then return a promise rejected with
272 // an "InvalidStateError" DOMException and abort these steps.
273
274 auto imageSize = cachedImage->imageSizeForRenderer(imageElement->renderer(), 1.0f);
275 if ((!imageSize.width() || !imageSize.height()) && (!options.resizeWidth || !options.resizeHeight)) {
276 promise.reject(InvalidStateError, "Cannot create ImageBitmap from a source with no intrinsic size without providing resize dimensions");
277 return;
278 }
279
280 // 4. If image's media data has no intrinsic dimensions (e.g. it's a vector graphics
281 // with no specified content size), it should be rendered to a bitmap of the size
282 // specified by the resizeWidth and the resizeHeight options.
283
284 if (!imageSize.width() && !imageSize.height()) {
285 imageSize.setWidth(options.resizeWidth.value());
286 imageSize.setHeight(options.resizeHeight.value());
287 }
288
289 // 5. If the sw and sh arguments are not specified and image's media data has both or
290 // either of its intrinsic width and intrinsic height values equal to 0, then return
291 // a promise rejected with an "InvalidStateError" DOMException and abort these steps.
292 // 6. If the sh argument is not specified and image's media data has an intrinsic height
293 // of 0, then return a promise rejected with an "InvalidStateError" DOMException and
294 // abort these steps.
295
296 // FIXME: It's unclear how these steps can happen, since step 4 required setting a
297 // width and height for the image.
298
299 if (!rect && (!imageSize.width() || !imageSize.height())) {
300 promise.reject(InvalidStateError, "Cannot create ImageBitmap from a source with no intrinsic size without providing dimensions");
301 return;
302 }
303
304 // 8. Let the ImageBitmap object's bitmap data be a copy of image's media data, cropped to
305 // the source rectangle with formatting. If this is an animated image, the ImageBitmap
306 // object's bitmap data must only be taken from the default image of the animation (the
307 // one that the format defines is to be used when animation is not supported or is disabled),
308 // or, if there is no such image, the first frame of the animation.
309
310 auto sourceRectangle = croppedSourceRectangleWithFormatting(roundedIntSize(imageSize), options, WTFMove(rect));
311 if (sourceRectangle.hasException()) {
312 promise.reject(sourceRectangle.releaseException());
313 return;
314 }
315
316 auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
317 auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
318
319 auto imageForRender = cachedImage->imageForRenderer(imageElement->renderer());
320 if (!imageForRender) {
321 promise.reject(InvalidStateError, "Cannot create ImageBitmap from image that can't be rendered");
322 return;
323 }
324
325 FloatRect destRect(FloatPoint(), outputSize);
326 ImagePaintingOptions paintingOptions;
327 paintingOptions.m_interpolationQuality = interpolationQualityForResizeQuality(options.resizeQuality);
328
329 bitmapData->context().drawImage(*imageForRender, destRect, sourceRectangle.releaseReturnValue(), paintingOptions);
330
331 // 7. Create a new ImageBitmap object.
332 auto imageBitmap = create(WTFMove(bitmapData));
333
334 // 9. If the origin of image's image is not the same origin as the origin specified by the
335 // entry settings object, then set the origin-clean flag of the ImageBitmap object's
336 // bitmap to false.
337
338 imageBitmap->m_originClean = !taintsOrigin(*cachedImage);
339
340 // 10. Return a new promise, but continue running these steps in parallel.
341 // 11. Resolve the promise with the new ImageBitmap object as the value.
342
343 promise.resolve(WTFMove(imageBitmap));
344}
345
346void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<HTMLCanvasElement>& canvasElement, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
347{
348 // 2. If the canvas element's bitmap has either a horizontal dimension or a vertical
349 // dimension equal to zero, then return a promise rejected with an "InvalidStateError"
350 // DOMException and abort these steps.
351 auto size = canvasElement->size();
352 if (!size.width() || !size.height()) {
353 promise.reject(InvalidStateError, "Cannot create ImageBitmap from a canvas that has zero width or height");
354 return;
355 }
356
357 // 4. Let the ImageBitmap object's bitmap data be a copy of the canvas element's bitmap
358 // data, cropped to the source rectangle with formatting.
359
360 auto sourceRectangle = croppedSourceRectangleWithFormatting(size, options, WTFMove(rect));
361 if (sourceRectangle.hasException()) {
362 promise.reject(sourceRectangle.releaseException());
363 return;
364 }
365
366 auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
367 auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
368
369 auto imageForRender = canvasElement->copiedImage();
370 if (!imageForRender) {
371 promise.reject(InvalidStateError, "Cannot create ImageBitmap from canvas that can't be rendered");
372 return;
373 }
374
375 FloatRect destRect(FloatPoint(), outputSize);
376 ImagePaintingOptions paintingOptions;
377 paintingOptions.m_interpolationQuality = interpolationQualityForResizeQuality(options.resizeQuality);
378
379 bitmapData->context().drawImage(*imageForRender, destRect, sourceRectangle.releaseReturnValue(), paintingOptions);
380
381 // 3. Create a new ImageBitmap object.
382 auto imageBitmap = create(WTFMove(bitmapData));
383
384 // 5. Set the origin-clean flag of the ImageBitmap object's bitmap to the same value as
385 // the origin-clean flag of the canvas element's bitmap.
386
387 imageBitmap->m_originClean = canvasElement->originClean();
388
389 // 6. Return a new promise, but continue running these steps in parallel.
390 // 7. Resolve the promise with the new ImageBitmap object as the value.
391
392 promise.resolve(WTFMove(imageBitmap));
393}
394
395#if ENABLE(VIDEO)
396void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, RefPtr<HTMLVideoElement>& video, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
397{
398 // https://html.spec.whatwg.org/multipage/#dom-createimagebitmap
399 // WHATWG HTML 2102913b313078cd8eeac7e81e6a8756cbd3e773
400 // Steps 3-7.
401 // (Step 3 is handled in croppedSourceRectangleWithFormatting.)
402
403 // 4. Check the usability of the image argument. If this throws an exception
404 // or returns bad, then return p rejected with an "InvalidStateError"
405 // DOMException.
406 if (video->readyState() == HTMLMediaElement::HAVE_NOTHING || video->readyState() == HTMLMediaElement::HAVE_METADATA) {
407 promise.reject(InvalidStateError, "Cannot create ImageBitmap before the HTMLVideoElement has data");
408 return;
409 }
410
411 // 6.1. If image's networkState attribute is NETWORK_EMPTY, then return p
412 // rejected with an "InvalidStateError" DOMException.
413 if (video->networkState() == HTMLMediaElement::NETWORK_EMPTY) {
414 promise.reject(InvalidStateError, "Cannot create ImageBitmap before the HTMLVideoElement has data");
415 return;
416 }
417
418 // 6.2. Set imageBitmap's bitmap data to a copy of the frame at the current
419 // playback position, at the media resource's intrinsic width and
420 // intrinsic height (i.e., after any aspect-ratio correction has been
421 // applied), cropped to the source rectangle with formatting.
422 auto size = video->player() ? roundedIntSize(video->player()->naturalSize()) : IntSize();
423 auto maybeSourceRectangle = croppedSourceRectangleWithFormatting(size, options, WTFMove(rect));
424 if (maybeSourceRectangle.hasException()) {
425 promise.reject(maybeSourceRectangle.releaseException());
426 return;
427 }
428 auto sourceRectangle = maybeSourceRectangle.releaseReturnValue();
429
430 auto outputSize = outputSizeForSourceRectangle(sourceRectangle, options);
431 auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
432
433 {
434 GraphicsContext& c = bitmapData->context();
435 GraphicsContextStateSaver stateSaver(c);
436 c.clip(FloatRect(FloatPoint(), outputSize));
437 auto scaleX = float(outputSize.width()) / float(sourceRectangle.width());
438 auto scaleY = float(outputSize.height()) / float(sourceRectangle.height());
439 c.scale(FloatSize(scaleX, scaleY));
440 c.translate(-sourceRectangle.location());
441 video->paintCurrentFrameInContext(c, FloatRect(FloatPoint(), size));
442 }
443
444 // 5. Let imageBitmap be a new ImageBitmap object.
445 auto imageBitmap = create(WTFMove(bitmapData));
446
447 // 6.3. If the origin of image's video is not same origin with entry
448 // settings object's origin, then set the origin-clean flag of
449 // image's bitmap to false.
450 imageBitmap->m_originClean = !taintsOrigin(scriptExecutionContext.securityOrigin(), *video);
451
452 // 6.4.1. Resolve p with imageBitmap.
453 promise.resolve(WTFMove(imageBitmap));
454}
455#endif
456
457#if ENABLE(CSS_TYPED_OM)
458void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<TypedOMCSSImageValue>&, ImageBitmapOptions&&, Optional<IntRect>, ImageBitmap::Promise&& promise)
459{
460 promise.reject(InvalidStateError, "Not implemented");
461}
462#endif
463
464void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<ImageBitmap>& existingImageBitmap, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
465{
466 // 2. If image's [[Detached]] internal slot value is true, return a promise
467 // rejected with an "InvalidStateError" DOMException and abort these steps.
468 if (existingImageBitmap->isDetached() || !existingImageBitmap->buffer()) {
469 promise.reject(InvalidStateError, "Cannot create ImageBitmap from a detached ImageBitmap");
470 return;
471 }
472
473 // 4. Let the ImageBitmap object's bitmap data be a copy of the image argument's
474 // bitmap data, cropped to the source rectangle with formatting.
475 auto sourceRectangle = croppedSourceRectangleWithFormatting(existingImageBitmap->buffer()->logicalSize(), options, WTFMove(rect));
476 if (sourceRectangle.hasException()) {
477 promise.reject(sourceRectangle.releaseException());
478 return;
479 }
480
481 auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
482 auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
483
484 auto imageForRender = existingImageBitmap->buffer()->copyImage();
485
486 FloatRect destRect(FloatPoint(), outputSize);
487 ImagePaintingOptions paintingOptions;
488 paintingOptions.m_interpolationQuality = interpolationQualityForResizeQuality(options.resizeQuality);
489
490 bitmapData->context().drawImage(*imageForRender, destRect, sourceRectangle.releaseReturnValue(), paintingOptions);
491
492 // 3. Create a new ImageBitmap object.
493 auto imageBitmap = create(WTFMove(bitmapData));
494
495 // 5. Set the origin-clean flag of the ImageBitmap object's bitmap to the same
496 // value as the origin-clean flag of the bitmap of the image argument.
497 imageBitmap->m_originClean = existingImageBitmap->originClean();
498
499 // 6. Return a new promise, but continue running these steps in parallel.
500 // 7. Resolve the promise with the new ImageBitmap object as the value.
501 promise.resolve(WTFMove(imageBitmap));
502}
503
504class ImageBitmapImageObserver final : public RefCounted<ImageBitmapImageObserver>, public ImageObserver {
505public:
506 static Ref<ImageBitmapImageObserver> create(String mimeType, long long expectedContentLength, const URL& sourceUrl)
507 {
508 return adoptRef(*new ImageBitmapImageObserver(mimeType, expectedContentLength, sourceUrl));
509 }
510
511 URL sourceUrl() const override { return m_sourceUrl; }
512 String mimeType() const override { return m_mimeType; }
513 long long expectedContentLength() const override { return m_expectedContentLength; }
514
515 void decodedSizeChanged(const Image&, long long) override { }
516
517 void didDraw(const Image&) override { }
518
519 bool canDestroyDecodedData(const Image&) override { return true; }
520 void imageFrameAvailable(const Image&, ImageAnimatingState, const IntRect* = nullptr, DecodingStatus = DecodingStatus::Invalid) override { }
521 void changedInRect(const Image&, const IntRect* = nullptr) override { }
522
523private:
524 ImageBitmapImageObserver(String mimeType, long long expectedContentLength, const URL& sourceUrl)
525 : m_mimeType(mimeType)
526 , m_expectedContentLength(expectedContentLength)
527 , m_sourceUrl(sourceUrl)
528 { }
529
530 String m_mimeType;
531 long long m_expectedContentLength;
532 URL m_sourceUrl;
533};
534
535class PendingImageBitmap final : public ActiveDOMObject, public FileReaderLoaderClient {
536public:
537 static void fetch(ScriptExecutionContext& scriptExecutionContext, RefPtr<Blob>&& blob, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
538 {
539 auto pendingImageBitmap = new PendingImageBitmap(scriptExecutionContext, WTFMove(blob), WTFMove(options), WTFMove(rect), WTFMove(promise));
540 pendingImageBitmap->start(scriptExecutionContext);
541 }
542
543private:
544 PendingImageBitmap(ScriptExecutionContext& scriptExecutionContext, RefPtr<Blob>&& blob, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
545 : ActiveDOMObject(&scriptExecutionContext)
546 , m_blobLoader(FileReaderLoader::ReadAsArrayBuffer, this)
547 , m_blob(WTFMove(blob))
548 , m_options(WTFMove(options))
549 , m_rect(WTFMove(rect))
550 , m_promise(WTFMove(promise))
551 {
552 suspendIfNeeded();
553 }
554
555 void start(ScriptExecutionContext& scriptExecutionContext)
556 {
557 m_blobLoader.start(&scriptExecutionContext, *m_blob);
558 }
559
560 // ActiveDOMObject
561
562 const char* activeDOMObjectName() const override
563 {
564 return "PendingImageBitmap";
565 }
566
567 bool canSuspendForDocumentSuspension() const override
568 {
569 // FIXME: Deal with suspension.
570 return false;
571 }
572
573 // FileReaderLoaderClient
574
575 void didStartLoading() override
576 {
577 }
578
579 void didReceiveData() override
580 {
581 }
582
583 void didFinishLoading() override
584 {
585 createImageBitmap(m_blobLoader.arrayBufferResult());
586 delete this;
587 }
588
589 void didFail(int) override
590 {
591 createImageBitmap(nullptr);
592 delete this;
593 }
594
595 void createImageBitmap(RefPtr<ArrayBuffer>&& arrayBuffer)
596 {
597 if (!arrayBuffer) {
598 m_promise.reject(InvalidStateError, "An error occured reading the Blob argument to createImageBitmap");
599 return;
600 }
601
602 ImageBitmap::createFromBuffer(arrayBuffer.releaseNonNull(), m_blob->type(), m_blob->size(), m_blobLoader.url(), WTFMove(m_options), WTFMove(m_rect), WTFMove(m_promise));
603 }
604
605 FileReaderLoader m_blobLoader;
606 RefPtr<Blob> m_blob;
607 ImageBitmapOptions m_options;
608 Optional<IntRect> m_rect;
609 ImageBitmap::Promise m_promise;
610};
611
612void ImageBitmap::createFromBuffer(
613 Ref<ArrayBuffer>&& arrayBuffer,
614 String mimeType,
615 long long expectedContentLength,
616 const URL& sourceUrl,
617 ImageBitmapOptions&& options,
618 Optional<IntRect> rect,
619 ImageBitmap::Promise&& promise)
620{
621 if (!arrayBuffer->byteLength()) {
622 promise.reject(InvalidStateError, "Cannot create an ImageBitmap from an empty buffer");
623 return;
624 }
625
626 auto sharedBuffer = SharedBuffer::create(static_cast<const char*>(arrayBuffer->data()), arrayBuffer->byteLength());
627 auto observer = ImageBitmapImageObserver::create(mimeType, expectedContentLength, sourceUrl);
628 auto image = Image::create(observer.get());
629 if (!image) {
630 promise.reject(InvalidStateError, "The type of the argument to createImageBitmap is not supported");
631 return;
632 }
633
634 auto result = image->setData(sharedBuffer.copyRef(), true);
635 if (result != EncodedDataStatus::Complete) {
636 promise.reject(InvalidStateError, "Cannot decode the data in the argument to createImageBitmap");
637 return;
638 }
639
640 auto sourceRectangle = croppedSourceRectangleWithFormatting(roundedIntSize(image->size()), options, rect);
641 if (sourceRectangle.hasException()) {
642 promise.reject(sourceRectangle.releaseException());
643 return;
644 }
645
646 auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
647 auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
648 if (!bitmapData) {
649 promise.reject(InvalidStateError, "Cannot create an image buffer from the argument to createImageBitmap");
650 return;
651 }
652
653 FloatRect destRect(FloatPoint(), outputSize);
654 ImagePaintingOptions paintingOptions;
655 paintingOptions.m_interpolationQuality = interpolationQualityForResizeQuality(options.resizeQuality);
656
657 bitmapData->context().drawImage(*image, destRect, sourceRectangle.releaseReturnValue(), paintingOptions);
658
659 auto imageBitmap = create(WTFMove(bitmapData));
660
661 promise.resolve(WTFMove(imageBitmap));
662}
663
664void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, RefPtr<Blob>& blob, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
665{
666 // 2. Return a new promise, but continue running these steps in parallel.
667 PendingImageBitmap::fetch(scriptExecutionContext, WTFMove(blob), WTFMove(options), WTFMove(rect), WTFMove(promise));
668}
669
670void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<ImageData>& imageData, ImageBitmapOptions&& options, Optional<IntRect> rect, ImageBitmap::Promise&& promise)
671{
672 UNUSED_PARAM(imageData);
673 UNUSED_PARAM(options);
674 UNUSED_PARAM(rect);
675
676 // 2. If the image object's data attribute value's [[Detached]] internal slot value
677 // is true, return a promise rejected with an "InvalidStateError" DOMException
678 // and abort these steps.
679
680 // 3. Create a new ImageBitmap object.
681
682 // 4. Let the ImageBitmap object's bitmap data be the image data given by the ImageData
683 // object, cropped to the source rectangle with formatting.
684
685 // 5. Return a new promise, but continue running these steps in parallel.
686 // 6. Resolve the promise with the new ImageBitmap object as the value.
687 promise.reject(TypeError, "createImageBitmap with ImageData is not implemented");
688}
689
690ImageBitmap::ImageBitmap(std::unique_ptr<ImageBuffer>&& buffer)
691 : m_bitmapData(WTFMove(buffer))
692{
693 ASSERT(m_bitmapData);
694}
695
696ImageBitmap::~ImageBitmap() = default;
697
698unsigned ImageBitmap::width() const
699{
700 if (m_detached || !m_bitmapData)
701 return 0;
702
703 // FIXME: Is this the right width?
704 return m_bitmapData->logicalSize().width();
705}
706
707unsigned ImageBitmap::height() const
708{
709 if (m_detached || !m_bitmapData)
710 return 0;
711
712 // FIXME: Is this the right height?
713 return m_bitmapData->logicalSize().height();
714}
715
716void ImageBitmap::close()
717{
718 m_detached = true;
719 m_bitmapData = nullptr;
720}
721
722std::unique_ptr<ImageBuffer> ImageBitmap::transferOwnershipAndClose()
723{
724 m_detached = true;
725 return WTFMove(m_bitmapData);
726}
727
728}
729