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 | |
53 | namespace WebCore { |
54 | |
55 | WTF_MAKE_ISO_ALLOCATED_IMPL(ImageBitmap); |
56 | |
57 | #if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS) |
58 | static RenderingMode bufferRenderingMode = Accelerated; |
59 | #else |
60 | static RenderingMode bufferRenderingMode = Unaccelerated; |
61 | #endif |
62 | |
63 | Ref<ImageBitmap> ImageBitmap::create(IntSize size) |
64 | { |
65 | return create(ImageBuffer::create(FloatSize(size.width(), size.height()), bufferRenderingMode)); |
66 | } |
67 | |
68 | Ref<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 | |
75 | Ref<ImageBitmap> ImageBitmap::create(std::unique_ptr<ImageBuffer>&& buffer) |
76 | { |
77 | return adoptRef(*new ImageBitmap(WTFMove(buffer))); |
78 | } |
79 | |
80 | void 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 | |
89 | Vector<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 | |
98 | void 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 | |
119 | static 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) |
138 | static 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 |
155 | static 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 | |
176 | static 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 | |
199 | static 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 | |
258 | void 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 | |
346 | void 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) |
396 | void 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) |
458 | void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<TypedOMCSSImageValue>&, ImageBitmapOptions&&, Optional<IntRect>, ImageBitmap::Promise&& promise) |
459 | { |
460 | promise.reject(InvalidStateError, "Not implemented" ); |
461 | } |
462 | #endif |
463 | |
464 | void 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 | |
504 | class ImageBitmapImageObserver final : public RefCounted<ImageBitmapImageObserver>, public ImageObserver { |
505 | public: |
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 | |
523 | private: |
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 | |
535 | class PendingImageBitmap final : public ActiveDOMObject, public FileReaderLoaderClient { |
536 | public: |
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 | |
543 | private: |
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 | |
612 | void 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 | |
664 | void 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 | |
670 | void 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 | |
690 | ImageBitmap::ImageBitmap(std::unique_ptr<ImageBuffer>&& buffer) |
691 | : m_bitmapData(WTFMove(buffer)) |
692 | { |
693 | ASSERT(m_bitmapData); |
694 | } |
695 | |
696 | ImageBitmap::~ImageBitmap() = default; |
697 | |
698 | unsigned 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 | |
707 | unsigned 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 | |
716 | void ImageBitmap::close() |
717 | { |
718 | m_detached = true; |
719 | m_bitmapData = nullptr; |
720 | } |
721 | |
722 | std::unique_ptr<ImageBuffer> ImageBitmap::transferOwnershipAndClose() |
723 | { |
724 | m_detached = true; |
725 | return WTFMove(m_bitmapData); |
726 | } |
727 | |
728 | } |
729 | |