| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "core/frame/ImageBitmap.h" |
| |
| #include "core/html/HTMLCanvasElement.h" |
| #include "core/html/HTMLVideoElement.h" |
| #include "core/html/ImageData.h" |
| #include "platform/graphics/skia/SkiaUtils.h" |
| #include "platform/image-decoders/ImageDecoder.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "wtf/CheckedNumeric.h" |
| #include "wtf/PtrUtil.h" |
| #include "wtf/RefPtr.h" |
| #include <memory> |
| |
| namespace blink { |
| |
| static const char* imageOrientationFlipY = "flipY"; |
| static const char* imageBitmapOptionNone = "none"; |
| |
| namespace { |
| |
| struct ParsedOptions { |
| bool flipY = false; |
| bool premultiplyAlpha = true; |
| bool shouldScaleInput = false; |
| unsigned resizeWidth = 0; |
| unsigned resizeHeight = 0; |
| IntRect cropRect; |
| SkFilterQuality resizeQuality = kLow_SkFilterQuality; |
| // This value should be changed in the future when we support |
| // createImageBitmap with higher bit depth, in the parseOptions() function. |
| // For now, it is always 4. |
| int bytesPerPixel = 4; |
| }; |
| |
| // The following two functions are helpers used in cropImage |
| static inline IntRect normalizeRect(const IntRect& rect) { |
| return IntRect(std::min(rect.x(), rect.maxX()), |
| std::min(rect.y(), rect.maxY()), |
| std::max(rect.width(), -rect.width()), |
| std::max(rect.height(), -rect.height())); |
| } |
| |
| ParsedOptions parseOptions(const ImageBitmapOptions& options, |
| Optional<IntRect> cropRect, |
| IntSize sourceSize) { |
| ParsedOptions parsedOptions; |
| if (options.imageOrientation() == imageOrientationFlipY) { |
| parsedOptions.flipY = true; |
| } else { |
| parsedOptions.flipY = false; |
| DCHECK(options.imageOrientation() == imageBitmapOptionNone); |
| } |
| if (options.premultiplyAlpha() == imageBitmapOptionNone) { |
| parsedOptions.premultiplyAlpha = false; |
| } else { |
| parsedOptions.premultiplyAlpha = true; |
| DCHECK(options.premultiplyAlpha() == "default" || |
| options.premultiplyAlpha() == "premultiply"); |
| } |
| |
| int sourceWidth = sourceSize.width(); |
| int sourceHeight = sourceSize.height(); |
| if (!cropRect) { |
| parsedOptions.cropRect = IntRect(0, 0, sourceWidth, sourceHeight); |
| } else { |
| parsedOptions.cropRect = normalizeRect(*cropRect); |
| } |
| if (!options.hasResizeWidth() && !options.hasResizeHeight()) { |
| parsedOptions.resizeWidth = parsedOptions.cropRect.width(); |
| parsedOptions.resizeHeight = parsedOptions.cropRect.height(); |
| } else if (options.hasResizeWidth() && options.hasResizeHeight()) { |
| parsedOptions.resizeWidth = options.resizeWidth(); |
| parsedOptions.resizeHeight = options.resizeHeight(); |
| } else if (options.hasResizeWidth() && !options.hasResizeHeight()) { |
| parsedOptions.resizeWidth = options.resizeWidth(); |
| parsedOptions.resizeHeight = |
| ceil(static_cast<float>(options.resizeWidth()) / |
| parsedOptions.cropRect.width() * parsedOptions.cropRect.height()); |
| } else { |
| parsedOptions.resizeHeight = options.resizeHeight(); |
| parsedOptions.resizeWidth = |
| ceil(static_cast<float>(options.resizeHeight()) / |
| parsedOptions.cropRect.height() * parsedOptions.cropRect.width()); |
| } |
| if (static_cast<int>(parsedOptions.resizeWidth) == |
| parsedOptions.cropRect.width() && |
| static_cast<int>(parsedOptions.resizeHeight) == |
| parsedOptions.cropRect.height()) { |
| parsedOptions.shouldScaleInput = false; |
| return parsedOptions; |
| } |
| parsedOptions.shouldScaleInput = true; |
| |
| if (options.resizeQuality() == "high") |
| parsedOptions.resizeQuality = kHigh_SkFilterQuality; |
| else if (options.resizeQuality() == "medium") |
| parsedOptions.resizeQuality = kMedium_SkFilterQuality; |
| else if (options.resizeQuality() == "pixelated") |
| parsedOptions.resizeQuality = kNone_SkFilterQuality; |
| else |
| parsedOptions.resizeQuality = kLow_SkFilterQuality; |
| return parsedOptions; |
| } |
| |
| bool dstBufferSizeHasOverflow(ParsedOptions options) { |
| CheckedNumeric<size_t> totalBytes = options.cropRect.width(); |
| totalBytes *= options.cropRect.height(); |
| totalBytes *= options.bytesPerPixel; |
| if (!totalBytes.IsValid()) |
| return true; |
| |
| if (!options.shouldScaleInput) |
| return false; |
| totalBytes = options.resizeWidth; |
| totalBytes *= options.resizeHeight; |
| totalBytes *= options.bytesPerPixel; |
| if (!totalBytes.IsValid()) |
| return true; |
| |
| return false; |
| } |
| |
| } // namespace |
| |
| static PassRefPtr<Uint8Array> copySkImageData(SkImage* input, |
| const SkImageInfo& info) { |
| // The function dstBufferSizeHasOverflow() is being called at the beginning of |
| // each ImageBitmap() constructor, which makes sure that doing |
| // width * height * bytesPerPixel will never overflow size_t. |
| size_t width = static_cast<size_t>(input->width()); |
| RefPtr<ArrayBuffer> dstBuffer = |
| ArrayBuffer::createOrNull(width * input->height(), info.bytesPerPixel()); |
| if (!dstBuffer) |
| return nullptr; |
| RefPtr<Uint8Array> dstPixels = |
| Uint8Array::create(dstBuffer, 0, dstBuffer->byteLength()); |
| input->readPixels(info, dstPixels->data(), width * info.bytesPerPixel(), 0, |
| 0); |
| return dstPixels; |
| } |
| |
| static sk_sp<SkImage> newSkImageFromRaster(const SkImageInfo& info, |
| PassRefPtr<Uint8Array> imagePixels, |
| size_t imageRowBytes) { |
| SkPixmap pixmap(info, imagePixels->data(), imageRowBytes); |
| return SkImage::MakeFromRaster(pixmap, |
| [](const void*, void* pixels) { |
| static_cast<Uint8Array*>(pixels)->deref(); |
| }, |
| imagePixels.leakRef()); |
| } |
| |
| static void swizzleImageData(unsigned char* srcAddr, |
| size_t height, |
| size_t bytesPerRow, |
| bool flipY) { |
| if (flipY) { |
| for (size_t i = 0; i < height / 2; i++) { |
| size_t topRowStartPosition = i * bytesPerRow; |
| size_t bottomRowStartPosition = (height - 1 - i) * bytesPerRow; |
| if (kN32_SkColorType == kBGRA_8888_SkColorType) { // needs to swizzle |
| for (size_t j = 0; j < bytesPerRow; j += 4) { |
| std::swap(srcAddr[topRowStartPosition + j], |
| srcAddr[bottomRowStartPosition + j + 2]); |
| std::swap(srcAddr[topRowStartPosition + j + 1], |
| srcAddr[bottomRowStartPosition + j + 1]); |
| std::swap(srcAddr[topRowStartPosition + j + 2], |
| srcAddr[bottomRowStartPosition + j]); |
| std::swap(srcAddr[topRowStartPosition + j + 3], |
| srcAddr[bottomRowStartPosition + j + 3]); |
| } |
| } else { |
| std::swap_ranges(srcAddr + topRowStartPosition, |
| srcAddr + topRowStartPosition + bytesPerRow, |
| srcAddr + bottomRowStartPosition); |
| } |
| } |
| } else { |
| if (kN32_SkColorType == kBGRA_8888_SkColorType) // needs to swizzle |
| for (size_t i = 0; i < height * bytesPerRow; i += 4) |
| std::swap(srcAddr[i], srcAddr[i + 2]); |
| } |
| } |
| |
| static sk_sp<SkImage> flipSkImageVertically(SkImage* input, |
| AlphaDisposition alphaOp) { |
| size_t width = static_cast<size_t>(input->width()); |
| size_t height = static_cast<size_t>(input->height()); |
| SkImageInfo info = SkImageInfo::MakeN32(input->width(), input->height(), |
| (alphaOp == PremultiplyAlpha) |
| ? kPremul_SkAlphaType |
| : kUnpremul_SkAlphaType); |
| size_t imageRowBytes = width * info.bytesPerPixel(); |
| RefPtr<Uint8Array> imagePixels = copySkImageData(input, info); |
| if (!imagePixels) |
| return nullptr; |
| for (size_t i = 0; i < height / 2; i++) { |
| size_t topFirstElement = i * imageRowBytes; |
| size_t topLastElement = (i + 1) * imageRowBytes; |
| size_t bottomFirstElement = (height - 1 - i) * imageRowBytes; |
| std::swap_ranges(imagePixels->data() + topFirstElement, |
| imagePixels->data() + topLastElement, |
| imagePixels->data() + bottomFirstElement); |
| } |
| return newSkImageFromRaster(info, std::move(imagePixels), imageRowBytes); |
| } |
| |
| static sk_sp<SkImage> premulSkImageToUnPremul(SkImage* input) { |
| SkImageInfo info = SkImageInfo::Make(input->width(), input->height(), |
| kN32_SkColorType, kUnpremul_SkAlphaType); |
| RefPtr<Uint8Array> dstPixels = copySkImageData(input, info); |
| if (!dstPixels) |
| return nullptr; |
| return newSkImageFromRaster( |
| info, std::move(dstPixels), |
| static_cast<size_t>(input->width()) * info.bytesPerPixel()); |
| } |
| |
| static sk_sp<SkImage> unPremulSkImageToPremul(SkImage* input) { |
| SkImageInfo info = SkImageInfo::Make(input->width(), input->height(), |
| kN32_SkColorType, kPremul_SkAlphaType); |
| RefPtr<Uint8Array> dstPixels = copySkImageData(input, info); |
| if (!dstPixels) |
| return nullptr; |
| return newSkImageFromRaster( |
| info, std::move(dstPixels), |
| static_cast<size_t>(input->width()) * info.bytesPerPixel()); |
| } |
| |
| sk_sp<SkImage> ImageBitmap::getSkImageFromDecoder( |
| std::unique_ptr<ImageDecoder> decoder) { |
| if (!decoder->frameCount()) |
| return nullptr; |
| ImageFrame* frame = decoder->frameBufferAtIndex(0); |
| if (!frame || frame->getStatus() != ImageFrame::FrameComplete) |
| return nullptr; |
| DCHECK(!frame->bitmap().isNull() && !frame->bitmap().empty()); |
| return frame->finalizePixelsAndGetImage(); |
| } |
| |
| bool ImageBitmap::isResizeOptionValid(const ImageBitmapOptions& options, |
| ExceptionState& exceptionState) { |
| if ((options.hasResizeWidth() && options.resizeWidth() == 0) || |
| (options.hasResizeHeight() && options.resizeHeight() == 0)) { |
| exceptionState.throwDOMException( |
| InvalidStateError, |
| "The resizeWidth or/and resizeHeight is equal to 0."); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ImageBitmap::isSourceSizeValid(int sourceWidth, |
| int sourceHeight, |
| ExceptionState& exceptionState) { |
| if (!sourceWidth || !sourceHeight) { |
| exceptionState.throwDOMException( |
| IndexSizeError, String::format("The source %s provided is 0.", |
| sourceWidth ? "height" : "width")); |
| return false; |
| } |
| return true; |
| } |
| |
| // The parameter imageFormat indicates whether the first parameter "image" is |
| // unpremultiplied or not. imageFormat = PremultiplyAlpha means the image is in |
| // premuliplied format For example, if the image is already in unpremultiplied |
| // format and we want the created ImageBitmap in the same format, then we don't |
| // need to use the ImageDecoder to decode the image. |
| static PassRefPtr<StaticBitmapImage> cropImage( |
| Image* image, |
| const ParsedOptions& parsedOptions, |
| AlphaDisposition imageFormat = PremultiplyAlpha, |
| ImageDecoder::GammaAndColorProfileOption colorSpaceOp = |
| ImageDecoder::GammaAndColorProfileApplied) { |
| ASSERT(image); |
| IntRect imgRect(IntPoint(), IntSize(image->width(), image->height())); |
| const IntRect srcRect = intersection(imgRect, parsedOptions.cropRect); |
| |
| // In the case when cropRect doesn't intersect the source image and it |
| // requires a umpremul image We immediately return a transparent black image |
| // with cropRect.size() |
| if (srcRect.isEmpty() && !parsedOptions.premultiplyAlpha) { |
| SkImageInfo info = |
| SkImageInfo::Make(parsedOptions.resizeWidth, parsedOptions.resizeHeight, |
| kN32_SkColorType, kUnpremul_SkAlphaType); |
| RefPtr<ArrayBuffer> dstBuffer = ArrayBuffer::createOrNull( |
| static_cast<size_t>(info.width()) * info.height(), |
| info.bytesPerPixel()); |
| if (!dstBuffer) |
| return nullptr; |
| RefPtr<Uint8Array> dstPixels = |
| Uint8Array::create(dstBuffer, 0, dstBuffer->byteLength()); |
| return StaticBitmapImage::create(newSkImageFromRaster( |
| info, std::move(dstPixels), |
| static_cast<size_t>(info.width()) * info.bytesPerPixel())); |
| } |
| |
| sk_sp<SkImage> skiaImage = image->imageForCurrentFrame(); |
| // Attempt to get raw unpremultiplied image data, executed only when skiaImage |
| // is premultiplied. |
| if ((((!parsedOptions.premultiplyAlpha && !skiaImage->isOpaque()) || |
| !skiaImage) && |
| image->data() && imageFormat == PremultiplyAlpha) || |
| colorSpaceOp == ImageDecoder::GammaAndColorProfileIgnored) { |
| std::unique_ptr<ImageDecoder> decoder(ImageDecoder::create( |
| image->data(), true, |
| parsedOptions.premultiplyAlpha ? ImageDecoder::AlphaPremultiplied |
| : ImageDecoder::AlphaNotPremultiplied, |
| colorSpaceOp)); |
| if (!decoder) |
| return nullptr; |
| skiaImage = ImageBitmap::getSkImageFromDecoder(std::move(decoder)); |
| if (!skiaImage) |
| return nullptr; |
| } |
| |
| if (parsedOptions.cropRect == srcRect && !parsedOptions.shouldScaleInput) { |
| sk_sp<SkImage> croppedSkImage = skiaImage->makeSubset(srcRect); |
| if (parsedOptions.flipY) |
| return StaticBitmapImage::create(flipSkImageVertically( |
| croppedSkImage.get(), parsedOptions.premultiplyAlpha |
| ? PremultiplyAlpha |
| : DontPremultiplyAlpha)); |
| // Special case: The first parameter image is unpremul but we need to turn |
| // it into premul. |
| if (parsedOptions.premultiplyAlpha && imageFormat == DontPremultiplyAlpha) |
| return StaticBitmapImage::create( |
| unPremulSkImageToPremul(croppedSkImage.get())); |
| // Call preroll to trigger image decoding. |
| croppedSkImage->preroll(); |
| return StaticBitmapImage::create(std::move(croppedSkImage)); |
| } |
| |
| sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul( |
| parsedOptions.resizeWidth, parsedOptions.resizeHeight); |
| if (!surface) |
| return nullptr; |
| if (srcRect.isEmpty()) |
| return StaticBitmapImage::create(surface->makeImageSnapshot()); |
| |
| SkScalar dstLeft = std::min(0, -parsedOptions.cropRect.x()); |
| SkScalar dstTop = std::min(0, -parsedOptions.cropRect.y()); |
| if (parsedOptions.cropRect.x() < 0) |
| dstLeft = -parsedOptions.cropRect.x(); |
| if (parsedOptions.cropRect.y() < 0) |
| dstTop = -parsedOptions.cropRect.y(); |
| if (parsedOptions.flipY) { |
| surface->getCanvas()->translate(0, surface->height()); |
| surface->getCanvas()->scale(1, -1); |
| } |
| if (parsedOptions.shouldScaleInput) { |
| SkRect drawSrcRect = SkRect::MakeXYWH( |
| parsedOptions.cropRect.x(), parsedOptions.cropRect.y(), |
| parsedOptions.cropRect.width(), parsedOptions.cropRect.height()); |
| SkRect drawDstRect = SkRect::MakeXYWH(0, 0, parsedOptions.resizeWidth, |
| parsedOptions.resizeHeight); |
| SkPaint paint; |
| paint.setFilterQuality(parsedOptions.resizeQuality); |
| surface->getCanvas()->drawImageRect(skiaImage, drawSrcRect, drawDstRect, |
| &paint); |
| } else { |
| surface->getCanvas()->drawImage(skiaImage, dstLeft, dstTop); |
| } |
| skiaImage = surface->makeImageSnapshot(); |
| |
| if (parsedOptions.premultiplyAlpha) { |
| if (imageFormat == DontPremultiplyAlpha) |
| return StaticBitmapImage::create( |
| unPremulSkImageToPremul(skiaImage.get())); |
| return StaticBitmapImage::create(std::move(skiaImage)); |
| } |
| return StaticBitmapImage::create(premulSkImageToUnPremul(skiaImage.get())); |
| } |
| |
| ImageBitmap::ImageBitmap(HTMLImageElement* image, |
| Optional<IntRect> cropRect, |
| Document* document, |
| const ImageBitmapOptions& options) { |
| RefPtr<Image> input = image->cachedImage()->getImage(); |
| ParsedOptions parsedOptions = |
| parseOptions(options, cropRect, image->bitmapSourceSize()); |
| if (dstBufferSizeHasOverflow(parsedOptions)) |
| return; |
| |
| if (options.colorSpaceConversion() == "none") |
| m_image = cropImage(input.get(), parsedOptions, PremultiplyAlpha, |
| ImageDecoder::GammaAndColorProfileIgnored); |
| else |
| m_image = cropImage(input.get(), parsedOptions, PremultiplyAlpha, |
| ImageDecoder::GammaAndColorProfileApplied); |
| if (!m_image) |
| return; |
| // In the case where the source image is lazy-decoded, m_image may not be in |
| // a decoded state, we trigger it here. |
| sk_sp<SkImage> skImage = m_image->imageForCurrentFrame(); |
| SkPixmap pixmap; |
| if (!skImage->isTextureBacked() && !skImage->peekPixels(&pixmap)) { |
| sk_sp<SkSurface> surface = |
| SkSurface::MakeRasterN32Premul(skImage->width(), skImage->height()); |
| surface->getCanvas()->drawImage(skImage, 0, 0); |
| m_image = StaticBitmapImage::create(surface->makeImageSnapshot()); |
| } |
| if (!m_image) |
| return; |
| m_image->setOriginClean( |
| !image->wouldTaintOrigin(document->getSecurityOrigin())); |
| m_image->setPremultiplied(parsedOptions.premultiplyAlpha); |
| } |
| |
| ImageBitmap::ImageBitmap(HTMLVideoElement* video, |
| Optional<IntRect> cropRect, |
| Document* document, |
| const ImageBitmapOptions& options) { |
| IntSize playerSize; |
| if (video->webMediaPlayer()) |
| playerSize = video->webMediaPlayer()->naturalSize(); |
| ParsedOptions parsedOptions = |
| parseOptions(options, cropRect, video->bitmapSourceSize()); |
| if (dstBufferSizeHasOverflow(parsedOptions)) |
| return; |
| |
| std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create( |
| IntSize(parsedOptions.resizeWidth, parsedOptions.resizeHeight), NonOpaque, |
| DoNotInitializeImagePixels); |
| if (!buffer) |
| return; |
| |
| IntPoint dstPoint = |
| IntPoint(-parsedOptions.cropRect.x(), -parsedOptions.cropRect.y()); |
| if (parsedOptions.flipY) { |
| buffer->canvas()->translate(0, buffer->size().height()); |
| buffer->canvas()->scale(1, -1); |
| } |
| SkPaint paint; |
| if (parsedOptions.shouldScaleInput) { |
| float scaleRatioX = static_cast<float>(parsedOptions.resizeWidth) / |
| parsedOptions.cropRect.width(); |
| float scaleRatioY = static_cast<float>(parsedOptions.resizeHeight) / |
| parsedOptions.cropRect.height(); |
| buffer->canvas()->scale(scaleRatioX, scaleRatioY); |
| paint.setFilterQuality(parsedOptions.resizeQuality); |
| } |
| buffer->canvas()->translate(dstPoint.x(), dstPoint.y()); |
| video->paintCurrentFrame( |
| buffer->canvas(), |
| IntRect(IntPoint(), IntSize(video->videoWidth(), video->videoHeight())), |
| parsedOptions.shouldScaleInput ? &paint : nullptr); |
| |
| sk_sp<SkImage> skiaImage = |
| buffer->newSkImageSnapshot(PreferNoAcceleration, SnapshotReasonUnknown); |
| if (!parsedOptions.premultiplyAlpha) |
| skiaImage = premulSkImageToUnPremul(skiaImage.get()); |
| if (!skiaImage) |
| return; |
| m_image = StaticBitmapImage::create(std::move(skiaImage)); |
| m_image->setOriginClean( |
| !video->wouldTaintOrigin(document->getSecurityOrigin())); |
| m_image->setPremultiplied(parsedOptions.premultiplyAlpha); |
| } |
| |
| ImageBitmap::ImageBitmap(HTMLCanvasElement* canvas, |
| Optional<IntRect> cropRect, |
| const ImageBitmapOptions& options) { |
| ASSERT(canvas->isPaintable()); |
| RefPtr<Image> input = canvas->copiedImage(BackBuffer, PreferAcceleration); |
| ParsedOptions parsedOptions = |
| parseOptions(options, cropRect, canvas->bitmapSourceSize()); |
| if (dstBufferSizeHasOverflow(parsedOptions)) |
| return; |
| |
| bool isPremultiplyAlphaReverted = false; |
| if (!parsedOptions.premultiplyAlpha) { |
| parsedOptions.premultiplyAlpha = true; |
| isPremultiplyAlphaReverted = true; |
| } |
| m_image = cropImage(input.get(), parsedOptions); |
| if (!m_image) |
| return; |
| if (isPremultiplyAlphaReverted) { |
| parsedOptions.premultiplyAlpha = false; |
| m_image = StaticBitmapImage::create( |
| premulSkImageToUnPremul(m_image->imageForCurrentFrame().get())); |
| } |
| if (!m_image) |
| return; |
| m_image->setOriginClean(canvas->originClean()); |
| m_image->setPremultiplied(parsedOptions.premultiplyAlpha); |
| } |
| |
| ImageBitmap::ImageBitmap(const void* pixelData, |
| uint32_t width, |
| uint32_t height, |
| bool isImageBitmapPremultiplied, |
| bool isImageBitmapOriginClean) { |
| SkImageInfo info = SkImageInfo::MakeN32( |
| width, height, |
| isImageBitmapPremultiplied ? kPremul_SkAlphaType : kUnpremul_SkAlphaType); |
| SkPixmap pixmap(info, pixelData, info.bytesPerPixel() * width); |
| m_image = StaticBitmapImage::create(SkImage::MakeRasterCopy(pixmap)); |
| if (!m_image) |
| return; |
| m_image->setPremultiplied(isImageBitmapPremultiplied); |
| m_image->setOriginClean(isImageBitmapOriginClean); |
| } |
| |
| static sk_sp<SkImage> scaleSkImage(sk_sp<SkImage> skImage, |
| unsigned resizeWidth, |
| unsigned resizeHeight, |
| SkFilterQuality resizeQuality) { |
| SkImageInfo resizedInfo = SkImageInfo::Make( |
| resizeWidth, resizeHeight, kN32_SkColorType, kUnpremul_SkAlphaType); |
| RefPtr<ArrayBuffer> dstBuffer = ArrayBuffer::createOrNull( |
| resizeWidth * resizeHeight, resizedInfo.bytesPerPixel()); |
| if (!dstBuffer) |
| return nullptr; |
| RefPtr<Uint8Array> resizedPixels = |
| Uint8Array::create(dstBuffer, 0, dstBuffer->byteLength()); |
| SkPixmap pixmap( |
| resizedInfo, resizedPixels->data(), |
| static_cast<size_t>(resizeWidth) * resizedInfo.bytesPerPixel()); |
| skImage->scalePixels(pixmap, resizeQuality); |
| return SkImage::MakeFromRaster(pixmap, |
| [](const void*, void* pixels) { |
| static_cast<Uint8Array*>(pixels)->deref(); |
| }, |
| resizedPixels.release().leakRef()); |
| } |
| |
| ImageBitmap::ImageBitmap(ImageData* data, |
| Optional<IntRect> cropRect, |
| const ImageBitmapOptions& options) { |
| // TODO(xidachen): implement the resize option |
| IntRect dataSrcRect = IntRect(IntPoint(), data->size()); |
| ParsedOptions parsedOptions = |
| parseOptions(options, cropRect, data->bitmapSourceSize()); |
| if (dstBufferSizeHasOverflow(parsedOptions)) |
| return; |
| IntRect srcRect = cropRect ? intersection(parsedOptions.cropRect, dataSrcRect) |
| : dataSrcRect; |
| |
| // treat non-premultiplyAlpha as a special case |
| if (!parsedOptions.premultiplyAlpha) { |
| unsigned char* srcAddr = data->data()->data(); |
| |
| // Using kN32 type, swizzle input if necessary. |
| SkImageInfo info = SkImageInfo::Make( |
| parsedOptions.cropRect.width(), parsedOptions.cropRect.height(), |
| kN32_SkColorType, kUnpremul_SkAlphaType); |
| size_t bytesPerPixel = static_cast<size_t>(info.bytesPerPixel()); |
| size_t srcPixelBytesPerRow = bytesPerPixel * data->size().width(); |
| size_t dstPixelBytesPerRow = bytesPerPixel * parsedOptions.cropRect.width(); |
| sk_sp<SkImage> skImage; |
| if (parsedOptions.cropRect == IntRect(IntPoint(), data->size())) { |
| swizzleImageData(srcAddr, data->size().height(), srcPixelBytesPerRow, |
| parsedOptions.flipY); |
| skImage = |
| SkImage::MakeRasterCopy(SkPixmap(info, srcAddr, dstPixelBytesPerRow)); |
| // restore the original ImageData |
| swizzleImageData(srcAddr, data->size().height(), srcPixelBytesPerRow, |
| parsedOptions.flipY); |
| } else { |
| RefPtr<ArrayBuffer> dstBuffer = ArrayBuffer::createOrNull( |
| static_cast<size_t>(parsedOptions.cropRect.height()) * |
| parsedOptions.cropRect.width(), |
| bytesPerPixel); |
| if (!dstBuffer) |
| return; |
| RefPtr<Uint8Array> copiedDataBuffer = |
| Uint8Array::create(dstBuffer, 0, dstBuffer->byteLength()); |
| if (!srcRect.isEmpty()) { |
| IntPoint srcPoint = IntPoint( |
| (parsedOptions.cropRect.x() > 0) ? parsedOptions.cropRect.x() : 0, |
| (parsedOptions.cropRect.y() > 0) ? parsedOptions.cropRect.y() : 0); |
| IntPoint dstPoint = IntPoint( |
| (parsedOptions.cropRect.x() >= 0) ? 0 : -parsedOptions.cropRect.x(), |
| (parsedOptions.cropRect.y() >= 0) ? 0 |
| : -parsedOptions.cropRect.y()); |
| int copyHeight = data->size().height() - srcPoint.y(); |
| if (parsedOptions.cropRect.height() < copyHeight) |
| copyHeight = parsedOptions.cropRect.height(); |
| int copyWidth = data->size().width() - srcPoint.x(); |
| if (parsedOptions.cropRect.width() < copyWidth) |
| copyWidth = parsedOptions.cropRect.width(); |
| for (int i = 0; i < copyHeight; i++) { |
| size_t srcStartCopyPosition = |
| (i + srcPoint.y()) * srcPixelBytesPerRow + |
| srcPoint.x() * bytesPerPixel; |
| size_t srcEndCopyPosition = |
| srcStartCopyPosition + copyWidth * bytesPerPixel; |
| size_t dstStartCopyPosition; |
| if (parsedOptions.flipY) |
| dstStartCopyPosition = |
| (parsedOptions.cropRect.height() - 1 - dstPoint.y() - i) * |
| dstPixelBytesPerRow + |
| dstPoint.x() * bytesPerPixel; |
| else |
| dstStartCopyPosition = (dstPoint.y() + i) * dstPixelBytesPerRow + |
| dstPoint.x() * bytesPerPixel; |
| for (size_t j = 0; j < srcEndCopyPosition - srcStartCopyPosition; |
| j++) { |
| // swizzle when necessary |
| if (kN32_SkColorType == kBGRA_8888_SkColorType) { |
| if (j % 4 == 0) |
| copiedDataBuffer->data()[dstStartCopyPosition + j] = |
| srcAddr[srcStartCopyPosition + j + 2]; |
| else if (j % 4 == 2) |
| copiedDataBuffer->data()[dstStartCopyPosition + j] = |
| srcAddr[srcStartCopyPosition + j - 2]; |
| else |
| copiedDataBuffer->data()[dstStartCopyPosition + j] = |
| srcAddr[srcStartCopyPosition + j]; |
| } else { |
| copiedDataBuffer->data()[dstStartCopyPosition + j] = |
| srcAddr[srcStartCopyPosition + j]; |
| } |
| } |
| } |
| } |
| skImage = newSkImageFromRaster(info, std::move(copiedDataBuffer), |
| dstPixelBytesPerRow); |
| } |
| if (!skImage) |
| return; |
| if (parsedOptions.shouldScaleInput) |
| m_image = StaticBitmapImage::create(scaleSkImage( |
| skImage, parsedOptions.resizeWidth, parsedOptions.resizeHeight, |
| parsedOptions.resizeQuality)); |
| else |
| m_image = StaticBitmapImage::create(skImage); |
| if (!m_image) |
| return; |
| m_image->setPremultiplied(parsedOptions.premultiplyAlpha); |
| return; |
| } |
| |
| std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create( |
| parsedOptions.cropRect.size(), NonOpaque, DoNotInitializeImagePixels); |
| if (!buffer) |
| return; |
| |
| if (srcRect.isEmpty()) { |
| m_image = StaticBitmapImage::create(buffer->newSkImageSnapshot( |
| PreferNoAcceleration, SnapshotReasonUnknown)); |
| return; |
| } |
| |
| IntPoint dstPoint = IntPoint(std::min(0, -parsedOptions.cropRect.x()), |
| std::min(0, -parsedOptions.cropRect.y())); |
| if (parsedOptions.cropRect.x() < 0) |
| dstPoint.setX(-parsedOptions.cropRect.x()); |
| if (parsedOptions.cropRect.y() < 0) |
| dstPoint.setY(-parsedOptions.cropRect.y()); |
| buffer->putByteArray(Unmultiplied, data->data()->data(), data->size(), |
| srcRect, dstPoint); |
| sk_sp<SkImage> skImage = |
| buffer->newSkImageSnapshot(PreferNoAcceleration, SnapshotReasonUnknown); |
| if (parsedOptions.flipY) |
| skImage = flipSkImageVertically(skImage.get(), PremultiplyAlpha); |
| if (!skImage) |
| return; |
| if (parsedOptions.shouldScaleInput) { |
| sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul( |
| parsedOptions.resizeWidth, parsedOptions.resizeHeight); |
| if (!surface) |
| return; |
| SkPaint paint; |
| paint.setFilterQuality(parsedOptions.resizeQuality); |
| SkRect dstDrawRect = |
| SkRect::MakeWH(parsedOptions.resizeWidth, parsedOptions.resizeHeight); |
| surface->getCanvas()->drawImageRect(skImage, dstDrawRect, &paint); |
| skImage = surface->makeImageSnapshot(); |
| } |
| m_image = StaticBitmapImage::create(std::move(skImage)); |
| } |
| |
| ImageBitmap::ImageBitmap(ImageBitmap* bitmap, |
| Optional<IntRect> cropRect, |
| const ImageBitmapOptions& options) { |
| RefPtr<Image> input = bitmap->bitmapImage(); |
| if (!input) |
| return; |
| ParsedOptions parsedOptions = parseOptions(options, cropRect, input->size()); |
| if (dstBufferSizeHasOverflow(parsedOptions)) |
| return; |
| |
| m_image = cropImage(input.get(), parsedOptions, bitmap->isPremultiplied() |
| ? PremultiplyAlpha |
| : DontPremultiplyAlpha); |
| if (!m_image) |
| return; |
| m_image->setOriginClean(bitmap->originClean()); |
| m_image->setPremultiplied(parsedOptions.premultiplyAlpha); |
| } |
| |
| ImageBitmap::ImageBitmap(PassRefPtr<StaticBitmapImage> image, |
| Optional<IntRect> cropRect, |
| const ImageBitmapOptions& options) { |
| bool originClean = image->originClean(); |
| RefPtr<Image> input = image; |
| ParsedOptions parsedOptions = parseOptions(options, cropRect, input->size()); |
| if (dstBufferSizeHasOverflow(parsedOptions)) |
| return; |
| |
| m_image = cropImage(input.get(), parsedOptions); |
| if (!m_image) |
| return; |
| |
| m_image->setOriginClean(originClean); |
| m_image->setPremultiplied(parsedOptions.premultiplyAlpha); |
| } |
| |
| ImageBitmap::ImageBitmap(PassRefPtr<StaticBitmapImage> image) { |
| m_image = image; |
| } |
| |
| PassRefPtr<StaticBitmapImage> ImageBitmap::transfer() { |
| ASSERT(!isNeutered()); |
| m_isNeutered = true; |
| m_image->transfer(); |
| return m_image.release(); |
| } |
| |
| ImageBitmap::~ImageBitmap() {} |
| |
| ImageBitmap* ImageBitmap::create(HTMLImageElement* image, |
| Optional<IntRect> cropRect, |
| Document* document, |
| const ImageBitmapOptions& options) { |
| return new ImageBitmap(image, cropRect, document, options); |
| } |
| |
| ImageBitmap* ImageBitmap::create(HTMLVideoElement* video, |
| Optional<IntRect> cropRect, |
| Document* document, |
| const ImageBitmapOptions& options) { |
| return new ImageBitmap(video, cropRect, document, options); |
| } |
| |
| ImageBitmap* ImageBitmap::create(HTMLCanvasElement* canvas, |
| Optional<IntRect> cropRect, |
| const ImageBitmapOptions& options) { |
| return new ImageBitmap(canvas, cropRect, options); |
| } |
| |
| ImageBitmap* ImageBitmap::create(ImageData* data, |
| Optional<IntRect> cropRect, |
| const ImageBitmapOptions& options) { |
| return new ImageBitmap(data, cropRect, options); |
| } |
| |
| ImageBitmap* ImageBitmap::create(ImageBitmap* bitmap, |
| Optional<IntRect> cropRect, |
| const ImageBitmapOptions& options) { |
| return new ImageBitmap(bitmap, cropRect, options); |
| } |
| |
| ImageBitmap* ImageBitmap::create(PassRefPtr<StaticBitmapImage> image, |
| Optional<IntRect> cropRect, |
| const ImageBitmapOptions& options) { |
| return new ImageBitmap(std::move(image), cropRect, options); |
| } |
| |
| ImageBitmap* ImageBitmap::create(PassRefPtr<StaticBitmapImage> image) { |
| return new ImageBitmap(std::move(image)); |
| } |
| |
| ImageBitmap* ImageBitmap::create(const void* pixelData, |
| uint32_t width, |
| uint32_t height, |
| bool isImageBitmapPremultiplied, |
| bool isImageBitmapOriginClean) { |
| return new ImageBitmap(pixelData, width, height, isImageBitmapPremultiplied, |
| isImageBitmapOriginClean); |
| } |
| |
| void ImageBitmap::close() { |
| if (!m_image || m_isNeutered) |
| return; |
| m_image.clear(); |
| m_isNeutered = true; |
| } |
| |
| // static |
| ImageBitmap* ImageBitmap::take(ScriptPromiseResolver*, sk_sp<SkImage> image) { |
| return ImageBitmap::create(StaticBitmapImage::create(std::move(image))); |
| } |
| |
| PassRefPtr<Uint8Array> ImageBitmap::copyBitmapData(AlphaDisposition alphaOp, |
| DataColorFormat format) { |
| SkImageInfo info = SkImageInfo::Make( |
| width(), height(), |
| (format == RGBAColorType) ? kRGBA_8888_SkColorType : kN32_SkColorType, |
| (alphaOp == PremultiplyAlpha) ? kPremul_SkAlphaType |
| : kUnpremul_SkAlphaType); |
| RefPtr<Uint8Array> dstPixels = |
| copySkImageData(m_image->imageForCurrentFrame().get(), info); |
| return dstPixels.release(); |
| } |
| |
| unsigned long ImageBitmap::width() const { |
| if (!m_image) |
| return 0; |
| ASSERT(m_image->width() > 0); |
| return m_image->width(); |
| } |
| |
| unsigned long ImageBitmap::height() const { |
| if (!m_image) |
| return 0; |
| ASSERT(m_image->height() > 0); |
| return m_image->height(); |
| } |
| |
| bool ImageBitmap::isAccelerated() const { |
| return m_image && (m_image->isTextureBacked() || m_image->hasMailbox()); |
| } |
| |
| IntSize ImageBitmap::size() const { |
| if (!m_image) |
| return IntSize(); |
| ASSERT(m_image->width() > 0 && m_image->height() > 0); |
| return IntSize(m_image->width(), m_image->height()); |
| } |
| |
| ScriptPromise ImageBitmap::createImageBitmap(ScriptState* scriptState, |
| EventTarget& eventTarget, |
| Optional<IntRect> cropRect, |
| const ImageBitmapOptions& options, |
| ExceptionState& exceptionState) { |
| if ((cropRect && |
| !isSourceSizeValid(cropRect->width(), cropRect->height(), |
| exceptionState)) || |
| !isSourceSizeValid(width(), height(), exceptionState)) |
| return ScriptPromise(); |
| if (!isResizeOptionValid(options, exceptionState)) |
| return ScriptPromise(); |
| return ImageBitmapSource::fulfillImageBitmap(scriptState, |
| create(this, cropRect, options)); |
| } |
| |
| PassRefPtr<Image> ImageBitmap::getSourceImageForCanvas( |
| SourceImageStatus* status, |
| AccelerationHint, |
| SnapshotReason, |
| const FloatSize&) const { |
| *status = NormalSourceImageStatus; |
| return m_image ? m_image : nullptr; |
| } |
| |
| void ImageBitmap::adjustDrawRects(FloatRect* srcRect, |
| FloatRect* dstRect) const {} |
| |
| FloatSize ImageBitmap::elementSize(const FloatSize&) const { |
| return FloatSize(width(), height()); |
| } |
| |
| DEFINE_TRACE(ImageBitmap) {} |
| |
| } // namespace blink |