#include "config.h"
#include "ImageBitmap.h"
#include "BitmapImage.h"
#include "Blob.h"
#include "CachedImage.h"
#include "ExceptionOr.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 "JSImageBitmap.h"
#include "LayoutSize.h"
#include "RenderElement.h"
#include "SharedBuffer.h"
#include <wtf/StdLibExtras.h>
namespace WebCore {
#if USE(IOSURFACE_CANVAS_BACKING_STORE) || ENABLE(ACCELERATED_2D_CANVAS)
static RenderingMode bufferRenderingMode = Accelerated;
#else
static RenderingMode bufferRenderingMode = Unaccelerated;
#endif
Ref<ImageBitmap> ImageBitmap::create(IntSize size)
{
auto imageBitmap = adoptRef(*new ImageBitmap);
imageBitmap->m_bitmapData = ImageBuffer::create(FloatSize(size.width(), size.height()), bufferRenderingMode);
return imageBitmap;
}
Ref<ImageBitmap> ImageBitmap::create()
{
return adoptRef(*new ImageBitmap);
}
void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, ImageBitmap::Source&& source, ImageBitmapOptions&& options, ImageBitmap::Promise&& promise)
{
WTF::switchOn(source,
[&] (auto& specificSource) {
createPromise(scriptExecutionContext, specificSource, WTFMove(options), std::nullopt, WTFMove(promise));
}
);
}
void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, ImageBitmap::Source&& source, ImageBitmapOptions&& options, int sx, int sy, int sw, int sh, ImageBitmap::Promise&& promise)
{
if (!sw || !sh) {
promise.reject(RangeError, "Cannot create ImageBitmap with a width or height of 0");
return;
}
if (sw < 0 || sh < 0) {
promise.reject(RangeError, "Cannot create ImageBitmap with a negative width or height");
return;
}
WTF::switchOn(source,
[&] (auto& specificSource) {
createPromise(scriptExecutionContext, specificSource, WTFMove(options), IntRect { sx, sy, sw, sh }, WTFMove(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;
}
static ExceptionOr<IntRect> croppedSourceRectangleWithFormatting(IntSize inputSize, ImageBitmapOptions& options, std::optional<IntRect> rect)
{
if ((options.resizeWidth && options.resizeWidth.value() <= 0) || (options.resizeHeight && options.resizeHeight.value() <= 0))
return Exception { InvalidStateError, "Invalid resize dimensions" };
auto sourceRectangle = rect.value_or(IntRect { 0, 0, inputSize.width(), inputSize.height() });
sourceRectangle.setWidth(std::min(sourceRectangle.width(), inputSize.width()));
sourceRectangle.setHeight(std::min(sourceRectangle.height(), inputSize.height()));
return { WTFMove(sourceRectangle) };
}
static IntSize outputSizeForSourceRectangle(IntRect sourceRectangle, ImageBitmapOptions& options)
{
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();
}();
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 { outputWidth, outputHeight };
}
static InterpolationQuality interpolationQualityForResizeQuality(ImageBitmapOptions::ResizeQuality resizeQuality)
{
switch (resizeQuality) {
case ImageBitmapOptions::ResizeQuality::Pixelated:
return InterpolationNone;
case ImageBitmapOptions::ResizeQuality::Low:
return InterpolationDefault; case ImageBitmapOptions::ResizeQuality::Medium:
return InterpolationMedium;
case ImageBitmapOptions::ResizeQuality::High:
return InterpolationHigh;
}
ASSERT_NOT_REACHED();
return InterpolationDefault;
}
void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<HTMLImageElement>& imageElement, ImageBitmapOptions&& options, std::optional<IntRect> rect, ImageBitmap::Promise&& promise)
{
auto* cachedImage = imageElement->cachedImage();
if (!cachedImage || !imageElement->complete()) {
promise.reject(InvalidStateError, "Cannot create ImageBitmap that is not completely available");
return;
}
auto imageSize = cachedImage->imageSizeForRenderer(imageElement->renderer(), 1.0f);
if ((!imageSize.width() || !imageSize.height()) && (!options.resizeWidth || !options.resizeHeight)) {
promise.reject(InvalidStateError, "Cannot create ImageBitmap from a source with no intrinsic size without providing resize dimensions");
return;
}
if (!imageSize.width() && !imageSize.height()) {
imageSize.setWidth(options.resizeWidth.value());
imageSize.setHeight(options.resizeHeight.value());
}
if (!rect && (!imageSize.width() || !imageSize.height())) {
promise.reject(InvalidStateError, "Cannot create ImageBitmap from a source with no intrinsic size without providing dimensions");
return;
}
auto imageBitmap = create();
auto sourceRectangle = croppedSourceRectangleWithFormatting(roundedIntSize(imageSize), options, WTFMove(rect));
if (sourceRectangle.hasException()) {
promise.reject(sourceRectangle.releaseException());
return;
}
auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
auto imageForRender = cachedImage->imageForRenderer(imageElement->renderer());
if (!imageForRender) {
promise.reject(InvalidStateError, "Cannot create ImageBitmap from image that can't be rendered");
return;
}
FloatRect destRect(FloatPoint(), outputSize);
ImagePaintingOptions paintingOptions;
paintingOptions.m_interpolationQuality = interpolationQualityForResizeQuality(options.resizeQuality);
bitmapData->context().drawImage(*imageForRender, destRect, sourceRectangle.releaseReturnValue(), paintingOptions);
imageBitmap->m_bitmapData = WTFMove(bitmapData);
imageBitmap->m_originClean = !taintsOrigin(*cachedImage);
promise.resolve(WTFMove(imageBitmap));
}
void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<HTMLCanvasElement>& canvasElement, ImageBitmapOptions&& options, std::optional<IntRect> rect, ImageBitmap::Promise&& promise)
{
auto size = canvasElement->size();
if (!size.width() || !size.height()) {
promise.reject(InvalidStateError, "Cannot create ImageBitmap from a canvas that has zero width or height");
return;
}
auto imageBitmap = create();
auto sourceRectangle = croppedSourceRectangleWithFormatting(size, options, WTFMove(rect));
if (sourceRectangle.hasException()) {
promise.reject(sourceRectangle.releaseException());
return;
}
auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
auto imageForRender = canvasElement->copiedImage();
if (!imageForRender) {
promise.reject(InvalidStateError, "Cannot create ImageBitmap from canvas that can't be rendered");
return;
}
FloatRect destRect(FloatPoint(), outputSize);
ImagePaintingOptions paintingOptions;
paintingOptions.m_interpolationQuality = interpolationQualityForResizeQuality(options.resizeQuality);
bitmapData->context().drawImage(*imageForRender, destRect, sourceRectangle.releaseReturnValue(), paintingOptions);
imageBitmap->m_bitmapData = WTFMove(bitmapData);
imageBitmap->m_originClean = canvasElement->originClean();
promise.resolve(WTFMove(imageBitmap));
}
#if ENABLE(VIDEO)
void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<HTMLVideoElement>& videoElement, ImageBitmapOptions&& options, std::optional<IntRect> rect, ImageBitmap::Promise&& promise)
{
UNUSED_PARAM(videoElement);
UNUSED_PARAM(options);
UNUSED_PARAM(rect);
promise.reject(TypeError, "createImageBitmap with HTMLVideoElement is not implemented");
}
#endif
void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<ImageBitmap>& existingImageBitmap, ImageBitmapOptions&& options, std::optional<IntRect> rect, ImageBitmap::Promise&& promise)
{
if (existingImageBitmap->isDetached() || !existingImageBitmap->buffer()) {
promise.reject(InvalidStateError, "Cannot create ImageBitmap from a detached ImageBitmap");
return;
}
auto imageBitmap = create();
auto sourceRectangle = croppedSourceRectangleWithFormatting(existingImageBitmap->buffer()->logicalSize(), options, WTFMove(rect));
if (sourceRectangle.hasException()) {
promise.reject(sourceRectangle.releaseException());
return;
}
auto outputSize = outputSizeForSourceRectangle(sourceRectangle.returnValue(), options);
auto bitmapData = ImageBuffer::create(FloatSize(outputSize.width(), outputSize.height()), bufferRenderingMode);
auto imageForRender = existingImageBitmap->buffer()->copyImage();
FloatRect destRect(FloatPoint(), outputSize);
ImagePaintingOptions paintingOptions;
paintingOptions.m_interpolationQuality = interpolationQualityForResizeQuality(options.resizeQuality);
bitmapData->context().drawImage(*imageForRender, destRect, sourceRectangle.releaseReturnValue(), paintingOptions);
imageBitmap->m_bitmapData = WTFMove(bitmapData);
imageBitmap->m_originClean = existingImageBitmap->originClean();
promise.resolve(WTFMove(imageBitmap));
}
class PendingImageBitmap final : public ActiveDOMObject, public FileReaderLoaderClient {
public:
static void fetch(ScriptExecutionContext& scriptExecutionContext, RefPtr<Blob>&& blob, ImageBitmapOptions&& options, std::optional<IntRect> rect, ImageBitmap::Promise&& promise)
{
auto pendingImageBitmap = new PendingImageBitmap(scriptExecutionContext, WTFMove(blob), WTFMove(options), WTFMove(rect), WTFMove(promise));
pendingImageBitmap->start(scriptExecutionContext);
}
private:
PendingImageBitmap(ScriptExecutionContext& scriptExecutionContext, RefPtr<Blob>&& blob, ImageBitmapOptions&& options, std::optional<IntRect> rect, ImageBitmap::Promise&& promise)
: ActiveDOMObject(&scriptExecutionContext)
, m_blobLoader(FileReaderLoader::ReadAsArrayBuffer, this)
, m_blob(WTFMove(blob))
, m_options(WTFMove(options))
, m_rect(WTFMove(rect))
, m_promise(WTFMove(promise))
{
suspendIfNeeded();
}
void start(ScriptExecutionContext& scriptExecutionContext)
{
m_blobLoader.start(&scriptExecutionContext, *m_blob);
}
const char* activeDOMObjectName() const override
{
return "PendingImageBitmap";
}
bool canSuspendForDocumentSuspension() const override
{
return false;
}
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(RefPtr<ArrayBuffer> arrayBuffer)
{
UNUSED_PARAM(arrayBuffer);
m_promise.reject(TypeError, "createImageBitmap with ArrayBuffer or Blob is not implemented");
}
FileReaderLoader m_blobLoader;
RefPtr<Blob> m_blob;
ImageBitmapOptions m_options;
std::optional<IntRect> m_rect;
ImageBitmap::Promise m_promise;
};
void ImageBitmap::createPromise(ScriptExecutionContext& scriptExecutionContext, RefPtr<Blob>& blob, ImageBitmapOptions&& options, std::optional<IntRect> rect, ImageBitmap::Promise&& promise)
{
PendingImageBitmap::fetch(scriptExecutionContext, WTFMove(blob), WTFMove(options), WTFMove(rect), WTFMove(promise));
}
void ImageBitmap::createPromise(ScriptExecutionContext&, RefPtr<ImageData>& imageData, ImageBitmapOptions&& options, std::optional<IntRect> rect, ImageBitmap::Promise&& promise)
{
UNUSED_PARAM(imageData);
UNUSED_PARAM(options);
UNUSED_PARAM(rect);
promise.reject(TypeError, "createImageBitmap with ImageData is not implemented");
}
ImageBitmap::ImageBitmap() = default;
ImageBitmap::~ImageBitmap() = default;
unsigned ImageBitmap::width() const
{
if (m_detached || !m_bitmapData)
return 0;
return m_bitmapData->logicalSize().width();
}
unsigned ImageBitmap::height() const
{
if (m_detached || !m_bitmapData)
return 0;
return m_bitmapData->logicalSize().height();
}
void ImageBitmap::close()
{
m_detached = true;
m_bitmapData = nullptr;
}
std::unique_ptr<ImageBuffer> ImageBitmap::transferOwnershipAndClose()
{
m_detached = true;
return WTFMove(m_bitmapData);
}
}