/*
 * Copyright (C) 2017 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "ImageBitmap.h"

#include "BitmapImage.h"
#include "Blob.h"
#include "CachedImage.h"
#include "ExceptionCode.h"
#include "FileReaderLoader.h"
#include "FileReaderLoaderClient.h"
#include "GraphicsContext.h"
#include "HTMLCanvasElement.h"
#include "HTMLImageElement.h"
#include "HTMLVideoElement.h"
#include "ImageBitmapOptions.h"
#include "ImageBuffer.h"
#include "ImageData.h"
#include "IntRect.h"
#include "JSDOMPromiseDeferred.h"
#include "JSImageBitmap.h"
#include "KURL.h"
#include "LayoutSize.h"
#include "RenderElement.h"
#include "SharedBuffer.h"
#include <wtf/StdLibExtras.h>

namespace WebCore {

#if PLATFORM(ESPIAL)
RenderingMode ImageBitmap::bufferRenderingMode = RenderingMode::Unaccelerated;
#elif USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
RenderingMode ImageBitmap::bufferRenderingMode = RenderingMode::Accelerated;
#else
RenderingMode ImageBitmap::bufferRenderingMode = RenderingMode::Unaccelerated;
#endif

Ref<ImageBitmap> ImageBitmap::create(IntSize size)
{
    return create(ImageBuffer::create(FloatSize(size.width(), size.height()), 1, ColorSpaceSRGB, bufferRenderingMode));
}

Ref<ImageBitmap> ImageBitmap::create(std::pair<OwnPtr<ImageBuffer>, bool>&& buffer)
{
	Ref<ImageBitmap> imageBitmap = create(buffer.first.release());
    imageBitmap->m_originClean = buffer.second;
    return imageBitmap;
}

Ref<ImageBitmap> ImageBitmap::create(PassOwnPtr<ImageBuffer> buffer)
{
    return adoptRef(*new ImageBitmap(buffer));
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<HTMLImageElement> source, const ImageBitmapOptions& options, PassRefPtr<DeferredPromise> promise)
{
    createPromise(scriptExecutionContext, source, options, Nullopt, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<HTMLCanvasElement> source, const ImageBitmapOptions& options, PassRefPtr<DeferredPromise> promise)
{
    createPromise(scriptExecutionContext, source, options, Nullopt, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<HTMLVideoElement> source, const ImageBitmapOptions& options, PassRefPtr<DeferredPromise> promise)
{
    createPromise(scriptExecutionContext, source, options, Nullopt, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<ImageBitmap> source, const ImageBitmapOptions& options, PassRefPtr<DeferredPromise> promise)
{
    createPromise(scriptExecutionContext, source, options, Nullopt, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<Blob> source, const ImageBitmapOptions& options, PassRefPtr<DeferredPromise> promise)
{
    createPromise(scriptExecutionContext, source, options, Nullopt, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<ImageData> source, const ImageBitmapOptions& options, PassRefPtr<DeferredPromise> promise)
{
    createPromise(scriptExecutionContext, source, options, Nullopt, promise);
}

Vector<std::pair<OwnPtr<ImageBuffer>, bool>> ImageBitmap::detachBitmaps(Vector<RefPtr<ImageBitmap>>&& bitmaps)
{
    Vector<std::pair<OwnPtr<ImageBuffer>, bool>> buffers;
    for (auto& bitmap : bitmaps)
        buffers.append(std::make_pair(bitmap->transferOwnershipAndClose(), bitmap->originClean()));
    return buffers;
}

template<typename T>
inline void ImageBitmap::createPromiseInternal(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<T> source, const ImageBitmapOptions& options, int sx, int sy, int sw, int sh, PassRefPtr<DeferredPromise> promise)
{
    // 1. If either the sw or sh arguments are specified but zero, return a promise
    //    rejected with an "RangeError" DOMException and abort these steps.
    if (!sw || !sh) {
        promise->reject(RangeError, "Cannot create ImageBitmap with a width or height of 0");
        return;
    }

    int left = sw >= 0 ? sx : sx + sw;
    int top = sh >= 0 ? sy : sy + sh;
    int width = std::abs(sw);
    int height = std::abs(sh);

	createPromise(scriptExecutionContext, source, options, IntRect(left, top, width, height), promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<HTMLImageElement> source, const ImageBitmapOptions& options, int sx, int sy, int sw, int sh, PassRefPtr<DeferredPromise> promise)
{
	createPromiseInternal(scriptExecutionContext, source, options, sx, sy, sw, sh, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<HTMLCanvasElement> source, const ImageBitmapOptions& options, int sx, int sy, int sw, int sh, PassRefPtr<DeferredPromise> promise)
{
	createPromiseInternal(scriptExecutionContext, source, options, sx, sy, sw, sh, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<HTMLVideoElement> source, const ImageBitmapOptions& options, int sx, int sy, int sw, int sh, PassRefPtr<DeferredPromise> promise)
{
	createPromiseInternal(scriptExecutionContext, source, options, sx, sy, sw, sh, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<ImageBitmap> source, const ImageBitmapOptions& options, int sx, int sy, int sw, int sh, PassRefPtr<DeferredPromise> promise)
{
	createPromiseInternal(scriptExecutionContext, source, options, sx, sy, sw, sh, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<Blob> source, const ImageBitmapOptions& options, int sx, int sy, int sw, int sh, PassRefPtr<DeferredPromise> promise)
{
	createPromiseInternal(scriptExecutionContext, source, options, sx, sy, sw, sh, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<ImageData> source, const ImageBitmapOptions& options, int sx, int sy, int sw, int sh, PassRefPtr<DeferredPromise> promise)
{
	createPromiseInternal(scriptExecutionContext, source, options, sx, sy, sw, sh, promise);
}

static bool taintsOrigin(CachedImage& cachedImage)
{
    auto* image = cachedImage.image();
    if (!image)
        return false;

    if (image->sourceURL().protocolIsData())
        return false;

    if (!image->hasSingleSecurityOrigin())
        return true;

    if (!cachedImage.isCORSSameOrigin())
        return true;

    return false;
}

// https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#cropped-to-the-source-rectangle-with-formatting
static Optional<IntRect> croppedSourceRectangleWithFormatting(const IntSize& inputSize, const ImageBitmapOptions& options, Optional<IntRect> rect, DeferredPromise& promise)
{
    // 2. If either or both of resizeWidth and resizeHeight members of options are less
    //    than or equal to 0, then return a promise rejected with "InvalidStateError"
    //    DOMException and abort these steps.
    if ((options.resizeWidth && options.resizeWidth.value() <= 0) || (options.resizeHeight && options.resizeHeight.value() <= 0)) {
        promise.reject(INVALID_STATE_ERR, "Invalid resize dimensions");
        return Nullopt;
    }

    // 3. If sx, sy, sw and sh are specified, let sourceRectangle be a rectangle whose
    //    corners are the four points (sx, sy), (sx+sw, sy),(sx+sw, sy+sh), (sx,sy+sh).
    //    Otherwise let sourceRectangle be a rectangle whose corners are the four points
    //    (0,0), (width of input, 0), (width of input, height of input), (0, height of
    //    input).
    auto sourceRectangle = rect.value_or(IntRect(0, 0, inputSize.width(), inputSize.height()));

    // 4. Clip sourceRectangle to the dimensions of input.
    sourceRectangle.intersect(IntRect(0, 0, inputSize.width(), inputSize.height()));

    return sourceRectangle;
}

static IntSize outputSizeForSourceRectangle(const IntRect& sourceRectangle, const ImageBitmapOptions& options)
{
    // 5. Let outputWidth be determined as follows:
    auto outputWidth = [&] () -> int {
        if (options.resizeWidth)
            return options.resizeWidth.value();
        if (options.resizeHeight)
            return ceil(sourceRectangle.width() * static_cast<double>(options.resizeHeight.value()) / sourceRectangle.height());
        return sourceRectangle.width();
    }();

    // 6. Let outputHeight be determined as follows:
    auto outputHeight = [&] () -> int {
        if (options.resizeHeight)
            return options.resizeHeight.value();
        if (options.resizeWidth)
            return ceil(sourceRectangle.height() * static_cast<double>(options.resizeWidth.value()) / sourceRectangle.width());
        return sourceRectangle.height();
    }();

    return IntSize(outputWidth, outputHeight);
}

static InterpolationQuality interpolationQualityForResizeQuality(const ImageBitmapOptions::ResizeQuality resizeQuality)
{
    switch (resizeQuality) {
    case ImageBitmapOptions::ResizeQuality::Pixelated:
        return InterpolationNone;
    case ImageBitmapOptions::ResizeQuality::Low:
        return InterpolationDefault; // Low is the default.
    case ImageBitmapOptions::ResizeQuality::Medium:
        return InterpolationMedium;
    case ImageBitmapOptions::ResizeQuality::High:
        return InterpolationHigh;
    }
    ASSERT_NOT_REACHED();
    return InterpolationDefault;
}

// FIXME: More steps from https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#cropped-to-the-source-rectangle-with-formatting

// 7. Place input on an infinite transparent black grid plane, positioned so that its
//    top left corner is at the origin of the plane, with the x-coordinate increasing
//    to the right, and the y-coordinate increasing down, and with each pixel in the
//    input image data occupying a cell on the plane's grid.

// 8. Let output be the rectangle on the plane denoted by sourceRectangle.

// 9. Scale output to the size specified by outputWidth and outputHeight. The user
//    agent should use the value of the resizeQuality option to guide the choice of
//    scaling algorithm.

// 10. If the value of the imageOrientation member of options is "flipY", output must
//     be flipped vertically, disregarding any image orientation metadata of the source
//     (such as EXIF metadata), if any.

// 11. If image is an img element or a Blob object, let val be the value of the
//     colorSpaceConversion member of options, and then run these substeps:
//
//     1. If val is "default", the color space conversion behavior is implementation-specific,
//        and should be chosen according to the color space that the implementation uses for
//        drawing images onto the canvas.
//
//     2. If val is "none", output must be decoded without performing any color space
//        conversions. This means that the image decoding algorithm must ignore color profile
//        metadata embedded in the source data as well as the display device color profile.

// 12. Let val be the value of premultiplyAlpha member of options, and then run these substeps:
//
//     1. If val is "default", the alpha premultiplication behavior is implementation-specific,
//        and should be chosen according to implementation deems optimal for drawing images
//        onto the canvas.
//
//     2. If val is "premultiply", the output that is not premultiplied by alpha must have its
//        color components multiplied by alpha and that is premultiplied by alpha must be left
//        untouched.
//
//     3. If val is "none", the output that is not premultiplied by alpha must be left untouched
//        and that is premultiplied by alpha must have its color components divided by alpha.

// 13. Return output.

void ImageBitmap::createPromise(ScriptExecutionContext&, PassRefPtr<HTMLImageElement> imageElement, const ImageBitmapOptions& options, Optional<IntRect> rect, PassRefPtr<DeferredPromise> promise)
{
    // 2. If image is not completely available, then return a promise rejected with
    // an "InvalidStateError" DOMException and abort these steps.

    auto* cachedImage = imageElement->cachedImage();
    if (!cachedImage || !imageElement->complete()) {
        promise->reject(INVALID_STATE_ERR, "Cannot create ImageBitmap that is not completely available");
        return;
    }

    // 3. If image's media data has no intrinsic dimensions (e.g. it's a vector graphic
    //    with no specified content size), and both or either of the resizeWidth and
    //    resizeHeight options are not specified, then return a promise rejected with
    //    an "InvalidStateError" DOMException and abort these steps.

    auto imageSize = cachedImage->imageSizeForRenderer(imageElement->renderer(), 1.0f);
    if ((!imageSize.width() || !imageSize.height()) && (!options.resizeWidth || !options.resizeHeight)) {
        promise->reject(INVALID_STATE_ERR, "Cannot create ImageBitmap from a source with no intrinsic size without providing resize dimensions");
        return;
    }

    // 4. If image's media data has no intrinsic dimensions (e.g. it's a vector graphics
    //    with no specified content size), it should be rendered to a bitmap of the size
    //    specified by the resizeWidth and the resizeHeight options.

    if (!imageSize.width() && !imageSize.height()) {
        imageSize.setWidth(options.resizeWidth.value());
        imageSize.setHeight(options.resizeHeight.value());
    }

    // 5. If the sw and sh arguments are not specified and image's media data has both or
    //    either of its intrinsic width and intrinsic height values equal to 0, then return
    //    a promise rejected with an "InvalidStateError" DOMException and abort these steps.
    // 6. If the sh argument is not specified and image's media data has an intrinsic height
    //    of 0, then return a promise rejected with an "InvalidStateError" DOMException and
    //    abort these steps.

    // FIXME: It's unclear how these steps can happen, since step 4 required setting a
    // width and height for the image.

    if (!rect && (!imageSize.width() || !imageSize.height())) {
        promise->reject(INVALID_STATE_ERR, "Cannot create ImageBitmap from a source with no intrinsic size without providing dimensions");
        return;
    }

    // 8. Let the ImageBitmap object's bitmap data be a copy of image's media data, cropped to
    //    the source rectangle with formatting. If this is an animated image, the ImageBitmap
    //    object's bitmap data must only be taken from the default image of the animation (the
    //    one that the format defines is to be used when animation is not supported or is disabled),
    //    or, if there is no such image, the first frame of the animation.

    auto sourceRectangle = croppedSourceRectangleWithFormatting(roundedIntSize(imageSize), options, rect, *promise);
	if (!sourceRectangle) {
		//promise.reject(sourceRectangle.releaseException());
		return;
	}

	auto outputSize = outputSizeForSourceRectangle(sourceRectangle.value(), options);
	OwnPtr<ImageBuffer> bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), 1, ColorSpaceSRGB, bufferRenderingMode);
	if (!bitmapData) {
		promise->reject(INVALID_STATE_ERR, "Cannot create ImageBitmap from image that can't be allocated");
		return;
	}

    auto* imageForRender = cachedImage->imageForRenderer(imageElement->renderer());
    if (!imageForRender) {
        promise->reject(INVALID_STATE_ERR, "Cannot create ImageBitmap from image that can't be rendered");
        return;
    }

    FloatRect destRect(FloatPoint(), outputSize);
    auto previousInterpolationQuality = bitmapData->context()->imageInterpolationQuality();
    bitmapData->context()->setImageInterpolationQuality(interpolationQualityForResizeQuality(options.resizeQuality));
    bitmapData->context()->drawImage(imageForRender, destRect, sourceRectangle.value());
    bitmapData->context()->setImageInterpolationQuality(previousInterpolationQuality);

    // 7. Create a new ImageBitmap object.
    auto imageBitmap = create(bitmapData.release());


    // 9. If the origin of image's image is not the same origin as the origin specified by the
    //    entry settings object, then set the origin-clean flag of the ImageBitmap object's
    //    bitmap to false.

    imageBitmap->m_originClean = !taintsOrigin(*cachedImage);

    // 10. Return a new promise, but continue running these steps in parallel.
    // 11. Resolve the promise with the new ImageBitmap object as the value.

    promise->resolve(imageBitmap.ptr());
}

void ImageBitmap::createPromise(ScriptExecutionContext&, PassRefPtr<HTMLCanvasElement> canvasElement, const ImageBitmapOptions& options, Optional<IntRect> rect, PassRefPtr<DeferredPromise> promise)
{
    // 2. If the canvas element's bitmap has either a horizontal dimension or a vertical
    //    dimension equal to zero, then return a promise rejected with an "InvalidStateError"
    //    DOMException and abort these steps.
    auto size = canvasElement->size();
    if (!size.width() || !size.height()) {
        promise->reject(INVALID_STATE_ERR, "Cannot create ImageBitmap from a canvas that has zero width or height");
        return;
    }

    // 4. Let the ImageBitmap object's bitmap data be a copy of the canvas element's bitmap
    //    data, cropped to the source rectangle with formatting.

    auto sourceRectangle = croppedSourceRectangleWithFormatting(size, options, rect, *promise);
    if (!sourceRectangle) {
        //promise.reject(sourceRectangle.releaseException());
        return;
    }

    auto outputSize = outputSizeForSourceRectangle(sourceRectangle.value(), options);
    OwnPtr<ImageBuffer> bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), 1, ColorSpaceSRGB, bufferRenderingMode);

    auto imageForRender = canvasElement->copiedImage();
    if (!imageForRender) {
        promise->reject(INVALID_STATE_ERR, "Cannot create ImageBitmap from canvas that can't be rendered");
        return;
    }

    FloatRect destRect(FloatPoint(), outputSize);
    auto previousInterpolationQuality = bitmapData->context()->imageInterpolationQuality();
	bitmapData->context()->setImageInterpolationQuality(interpolationQualityForResizeQuality(options.resizeQuality));

    bitmapData->context()->drawImage(imageForRender, destRect, sourceRectangle.value());

    bitmapData->context()->setImageInterpolationQuality(previousInterpolationQuality);

    // 3. Create a new ImageBitmap object.
    auto imageBitmap = create(bitmapData.release());

    // 5. Set the origin-clean flag of the ImageBitmap object's bitmap to the same value as
    //    the origin-clean flag of the canvas element's bitmap.

    imageBitmap->m_originClean = canvasElement->originClean();

    // 6. Return a new promise, but continue running these steps in parallel.
    // 7. Resolve the promise with the new ImageBitmap object as the value.

    promise->resolve(imageBitmap.ptr());
}

void ImageBitmap::createPromise(ScriptExecutionContext&, PassRefPtr<HTMLVideoElement> videoElement, const ImageBitmapOptions& options, Optional<IntRect> rect, PassRefPtr<DeferredPromise> promise)
{
    UNUSED_PARAM(videoElement);
    UNUSED_PARAM(options);
    UNUSED_PARAM(rect);

    // 2. If the video element's networkState attribute is NETWORK_EMPTY, then return
    //    a promise rejected with an "InvalidStateError" DOMException and abort these
    //    steps.

    // 3. If the video element's readyState attribute is either HAVE_NOTHING or
    //    HAVE_METADATA, then return a promise rejected with an "InvalidStateError"
    //    DOMException and abort these steps.

    // 4. Create a new ImageBitmap object.

    // 5. Let the ImageBitmap object's bitmap data be a copy of the frame at the current
    //    playback position, at the media resource's intrinsic width and intrinsic height
    //    (i.e. after any aspect-ratio correction has been applied), cropped to the source
    //    rectangle with formatting.

    // 6. If the origin of the video element is not the same origin as the origin specified
    //    by the entry settings object, then set the origin-clean flag of the ImageBitmap
    //    object's bitmap to false.

    // 7. Return a new promise, but continue running these steps in parallel.

    // 8. Resolve the promise with the new ImageBitmap object as the value.
    promise->reject(TypeError, "createImageBitmap with HTMLVideoElement is not implemented");
}

void ImageBitmap::createPromise(ScriptExecutionContext&, PassRefPtr<ImageBitmap> existingImageBitmap, const ImageBitmapOptions& options, Optional<IntRect> rect, PassRefPtr<DeferredPromise> promise)
{
    // 2. If image's [[Detached]] internal slot value is true, return a promise
    //    rejected with an "InvalidStateError" DOMException and abort these steps.
	if (existingImageBitmap->isDetached() || !existingImageBitmap->buffer()) {
		promise->reject(INVALID_STATE_ERR, "Cannot create ImageBitmap from a detached ImageBitmap");
		return;
	}

    // 4. Let the ImageBitmap object's bitmap data be a copy of the image argument's
    //    bitmap data, cropped to the source rectangle with formatting.
    auto sourceRectangle = croppedSourceRectangleWithFormatting(existingImageBitmap->buffer()->logicalSize(), options, rect, *promise);
	if (!sourceRectangle) {
		//promise.reject(sourceRectangle.releaseException());
		return;
	}

	auto outputSize = outputSizeForSourceRectangle(sourceRectangle.value(), options);
	OwnPtr<ImageBuffer> bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), 1, ColorSpaceSRGB, bufferRenderingMode);

	RefPtr<Image> imageForRender = existingImageBitmap->buffer()->copyImage();
    if (!imageForRender) {
        promise->reject(INVALID_STATE_ERR, "Cannot create ImageBitmap from ImageBitmap that can't be rendered");
        return;
    }

	FloatRect destRect(FloatPoint(), outputSize);
    auto previousInterpolationQuality = bitmapData->context()->imageInterpolationQuality();
	bitmapData->context()->setImageInterpolationQuality(interpolationQualityForResizeQuality(options.resizeQuality));

	bitmapData->context()->drawImage(imageForRender.get(), destRect, sourceRectangle.value());

    bitmapData->context()->setImageInterpolationQuality(previousInterpolationQuality);

    // 3. Create a new ImageBitmap object.
    auto imageBitmap = create(bitmapData.release());

	// 5. Set the origin-clean flag of the ImageBitmap object's bitmap to the same
	//    value as the origin-clean flag of the bitmap of the image argument.
	imageBitmap->m_originClean = existingImageBitmap->originClean();

	// 6. Return a new promise, but continue running these steps in parallel.
	// 7. Resolve the promise with the new ImageBitmap object as the value.
    promise->resolve(imageBitmap.ptr());
}

class ImageBitmapImageObserver final : public ImageObserver {
public:
    static Ref<ImageBitmapImageObserver> create(String mimeType, long long expectedContentLength, const KURL& sourceUrl)
    {
        return adoptRef(*new ImageBitmapImageObserver(mimeType, expectedContentLength, sourceUrl));
    }

    KURL sourceUrl() const override { return m_sourceUrl; }
    String mimeType() const override { return m_mimeType; }
    long long expectedContentLength() const override { return m_expectedContentLength; }

    void decodedSizeChanged(const Image*, int) override { }

    void didDraw(const Image*) override { }

    virtual void animationAdvanced(const Image*) { }

    bool canDestroyDecodedData(const Image*) override { return true; }
    void changedInRect(const Image*, const IntRect&) override { }
    virtual void scheduleRenderingUpdate(const Image&) override { }

private:
    ImageBitmapImageObserver(String mimeType, long long expectedContentLength, const KURL& sourceUrl)
        : m_mimeType(mimeType)
        , m_expectedContentLength(expectedContentLength)
        , m_sourceUrl(sourceUrl)
    { }

    String m_mimeType;
    long long m_expectedContentLength;
    KURL m_sourceUrl;
};

class PendingImageBitmap final : public ActiveDOMObject, public FileReaderLoaderClient {
public:
    static void fetch(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<Blob> blob, const ImageBitmapOptions& options, Optional<IntRect> rect, PassRefPtr<DeferredPromise> promise)
    {
        auto pendingImageBitmap = new PendingImageBitmap(scriptExecutionContext, blob, options, rect, promise);
        pendingImageBitmap->start(scriptExecutionContext);
    }

private:
    PendingImageBitmap(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<Blob> blob, const ImageBitmapOptions& options, Optional<IntRect> rect, PassRefPtr<DeferredPromise> promise)
        : ActiveDOMObject(&scriptExecutionContext)
        , m_blobLoader(FileReaderLoader::ReadAsArrayBuffer, this)
        , m_blob(blob)
        , m_options(options)
        , m_rect(rect)
        , m_promise(promise)
    {
        suspendIfNeeded();
    }

    void start(ScriptExecutionContext& scriptExecutionContext)
    {
        m_blobLoader.start(&scriptExecutionContext, *m_blob);
    }

    // ActiveDOMObject

    const char* activeDOMObjectName() const override
    {
        return "PendingImageBitmap";
    }

    bool canSuspendForDocumentSuspension() const override
    {
        // FIXME: Deal with suspension.
        return false;
    }

    // FileReaderLoaderClient

    void didStartLoading() override
    {
    }

    void didReceiveData() override
    {
    }

    void didFinishLoading() override
    {
        createImageBitmap(m_blobLoader.arrayBufferResult());
        delete this;
    }

    void didFail(int) override
    {
        createImageBitmap(nullptr);
        delete this;
    }

    void createImageBitmap(PassRefPtr<ArrayBuffer> arrayBuffer)
    {
        if (!arrayBuffer) {
            m_promise->reject(INVALID_STATE_ERR, "An error occured reading the Blob argument to createImageBitmap");
            return;
        }

        ImageBitmap::createFromBuffer(arrayBuffer.releaseNonNull(), m_blob->type(), m_blob->size(), m_blobLoader.url(), m_options, m_rect, m_promise.release());
    }

    FileReaderLoader m_blobLoader;
    RefPtr<Blob> m_blob;
    ImageBitmapOptions m_options;
    Optional<IntRect> m_rect;
    RefPtr<DeferredPromise> m_promise;
};

void ImageBitmap::createFromBuffer(
    PassRefPtr<ArrayBuffer> arrayBuffer,
    String mimeType,
    long long expectedContentLength,
    const URL& sourceUrl,
    const ImageBitmapOptions& options,
    Optional<IntRect> rect,
    PassRefPtr<DeferredPromise> promise)
{
    if (!arrayBuffer->byteLength()) {
        promise->reject(INVALID_STATE_ERR, "Cannot create an ImageBitmap from an empty buffer");
        return;
    }

    auto sharedBuffer = SharedBuffer::create(static_cast<const char*>(arrayBuffer->data()), arrayBuffer->byteLength());
    auto observer = ImageBitmapImageObserver::create(mimeType, expectedContentLength, sourceUrl);
    RefPtr<Image> image = Image::create(observer.get());
    if (!image) {
        promise->reject(INVALID_STATE_ERR, "The type of the argument to createImageBitmap is not supported");
        return;
    }

    bool result = image->setData(sharedBuffer.copyRef(), true);
    if (!result || image->isNull()) {
        promise->reject(INVALID_STATE_ERR, "Cannot decode the data in the argument to createImageBitmap");
        return;
    }

    auto sourceRectangle = croppedSourceRectangleWithFormatting(roundedIntSize(image->size()), options, rect, *promise);
    if (!sourceRectangle) {
        //promise.reject(sourceRectangle.releaseException());
        return;
    }

    auto outputSize = outputSizeForSourceRectangle(sourceRectangle.value(), options);
    OwnPtr<ImageBuffer> bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), 1, ColorSpaceSRGB, bufferRenderingMode);
    if (!bitmapData) {
        promise->reject(INVALID_STATE_ERR, "Cannot create an image buffer from the argument to createImageBitmap");
        return;
    }

    FloatRect destRect(FloatPoint(), outputSize);
    auto previousInterpolationQuality = bitmapData->context()->imageInterpolationQuality();
	bitmapData->context()->setImageInterpolationQuality(interpolationQualityForResizeQuality(options.resizeQuality));

    bitmapData->context()->drawImage(image.get(), destRect, sourceRectangle.value());

    bitmapData->context()->setImageInterpolationQuality(previousInterpolationQuality);

    auto imageBitmap = create(bitmapData.release());

    promise->resolve(imageBitmap.ptr());
}

void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, PassRefPtr<Blob> blob, const ImageBitmapOptions& options, Optional<IntRect> rect, PassRefPtr<DeferredPromise> promise)
{
    // 2. Return a new promise, but continue running these steps in parallel.
    PendingImageBitmap::fetch(scriptExecutionContext, blob, options, rect, promise);
}

void ImageBitmap::createPromise(ScriptExecutionContext&, PassRefPtr<ImageData> imageData, const ImageBitmapOptions& options, Optional<IntRect> rect, PassRefPtr<DeferredPromise> promise)
{
    UNUSED_PARAM(imageData);
    UNUSED_PARAM(options);
    UNUSED_PARAM(rect);

    // 2. If the image object's data attribute value's [[Detached]] internal slot value
    //    is true, return a promise rejected with an "InvalidStateError" DOMException
    //    and abort these steps.

    // 3. Create a new ImageBitmap object.

    // 4. Let the ImageBitmap object's bitmap data be the image data given by the ImageData
    //    object, cropped to the source rectangle with formatting.

    // 5. Return a new promise, but continue running these steps in parallel.
    // 6. Resolve the promise with the new ImageBitmap object as the value.
    promise->reject(TypeError, "createImageBitmap with ImageData is not implemented");
}

ImageBitmap::ImageBitmap(PassOwnPtr<ImageBuffer> buffer)
	: m_bitmapData(buffer)
{
	ASSERT(m_bitmapData);
}

ImageBitmap::~ImageBitmap() = default;

unsigned ImageBitmap::width() const
{
    if (m_detached || !m_bitmapData)
        return 0;

    // FIXME: Is this the right width?
    return m_bitmapData->logicalSize().width();
}

unsigned ImageBitmap::height() const
{
    if (m_detached || !m_bitmapData)
        return 0;

    // FIXME: Is this the right height?
    return m_bitmapData->logicalSize().height();
}

void ImageBitmap::close()
{
    m_detached = true;
    m_bitmapData = nullptr;
}

PassOwnPtr<ImageBuffer> ImageBitmap::transferOwnershipAndClose()
{
    m_detached = true;
    return m_bitmapData.release();
}

}
