| // 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())); |
| } |
| |
| static bool frameIsValid(const SkBitmap& frameBitmap) |
| { |
| ASSERT(!frameBitmap.isNull() && !frameBitmap.empty() && frameBitmap.isImmutable()); |
| return frameBitmap.colorType() == kN32_SkColorType; |
| } |
| |
| 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 PassRefPtr<SkImage> newSkImageFromRaster(const SkImageInfo& info, PassRefPtr<Uint8Array> imagePixels, size_t imageRowBytes) |
| { |
| SkPixmap pixmap(info, imagePixels->data(), imageRowBytes); |
| return fromSkSp(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 PassRefPtr<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 PassRefPtr<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 PassRefPtr<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()); |
| } |
| |
| PassRefPtr<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; |
| SkBitmap bitmap = frame->bitmap(); |
| if (!frameIsValid(bitmap)) |
| return nullptr; |
| return fromSkSp(SkImage::MakeFromBitmap(bitmap)); |
| } |
| |
| 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())); |
| } |
| |
| RefPtr<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) { |
| RefPtr<SkImage> croppedSkImage = fromSkSp(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(croppedSkImage.release()); |
| } |
| |
| sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(parsedOptions.resizeWidth, parsedOptions.resizeHeight); |
| if (!surface) |
| return nullptr; |
| if (srcRect.isEmpty()) |
| return StaticBitmapImage::create(fromSkSp(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.get(), drawSrcRect, drawDstRect, &paint); |
| } else { |
| surface->getCanvas()->drawImage(skiaImage.get(), dstLeft, dstTop); |
| } |
| skiaImage = fromSkSp(surface->makeImageSnapshot()); |
| |
| if (parsedOptions.premultiplyAlpha) { |
| if (imageFormat == DontPremultiplyAlpha) |
| return StaticBitmapImage::create(unPremulSkImageToPremul(skiaImage.get())); |
| return StaticBitmapImage::create(skiaImage.release()); |
| } |
| 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. |
| RefPtr<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.get(), 0, 0); |
| m_image = StaticBitmapImage::create(fromSkSp(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; |
| |
| IntRect videoRect = IntRect(IntPoint(), playerSize); |
| IntRect srcRect = intersection(parsedOptions.cropRect, videoRect); |
| std::unique_ptr<ImageBuffer> buffer = ImageBuffer::create(IntSize(parsedOptions.resizeWidth, parsedOptions.resizeHeight), NonOpaque, DoNotInitializeImagePixels); |
| if (!buffer) |
| return; |
| |
| if (parsedOptions.flipY) { |
| buffer->canvas()->translate(0, buffer->size().height()); |
| buffer->canvas()->scale(1, -1); |
| } |
| IntPoint dstPoint = IntPoint(std::max(0, -parsedOptions.cropRect.x()), std::max(0, -parsedOptions.cropRect.y())); |
| IntSize dstSize = srcRect.size(); |
| 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(); |
| dstPoint.scale(scaleRatioX, scaleRatioY); |
| paint.setFilterQuality(parsedOptions.resizeQuality); |
| dstSize.scale(scaleRatioX, scaleRatioY); |
| } |
| video->paintCurrentFrame(buffer->canvas(), IntRect(dstPoint, dstSize), parsedOptions.shouldScaleInput ? &paint : nullptr); |
| |
| RefPtr<SkImage> skiaImage = buffer->newSkImageSnapshot(PreferNoAcceleration, SnapshotReasonUnknown); |
| if (!parsedOptions.premultiplyAlpha) |
| skiaImage = premulSkImageToUnPremul(skiaImage.get()); |
| if (!skiaImage) |
| return; |
| m_image = StaticBitmapImage::create(skiaImage.release()); |
| 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(std::unique_ptr<uint8_t[]> data, uint32_t width, uint32_t height, bool isImageBitmapPremultiplied, bool isImageBitmapOriginClean) |
| { |
| SkImageInfo info = SkImageInfo::MakeN32(width, height, isImageBitmapPremultiplied ? kPremul_SkAlphaType : kUnpremul_SkAlphaType); |
| m_image = StaticBitmapImage::create(fromSkSp(SkImage::MakeRasterCopy(SkPixmap(info, data.get(), info.bytesPerPixel() * width)))); |
| if (!m_image) |
| return; |
| m_image->setPremultiplied(isImageBitmapPremultiplied); |
| m_image->setOriginClean(isImageBitmapOriginClean); |
| } |
| |
| static PassRefPtr<SkImage> scaleSkImage(PassRefPtr<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 fromSkSp(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(); |
| RefPtr<SkImage> skImage; |
| if (parsedOptions.cropRect == IntRect(IntPoint(), data->size())) { |
| swizzleImageData(srcAddr, data->size().height(), srcPixelBytesPerRow, parsedOptions.flipY); |
| skImage = fromSkSp(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); |
| RefPtr<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.get(), dstDrawRect, &paint); |
| skImage = fromSkSp(surface->makeImageSnapshot()); |
| } |
| m_image = StaticBitmapImage::create(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; |
| 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(image, cropRect, options); |
| } |
| |
| ImageBitmap* ImageBitmap::create(PassRefPtr<StaticBitmapImage> image) |
| { |
| return new ImageBitmap(image); |
| } |
| |
| ImageBitmap* ImageBitmap::create(std::unique_ptr<uint8_t[]> data, uint32_t width, uint32_t height, bool isImageBitmapPremultiplied, bool isImageBitmapOriginClean) |
| { |
| return new ImageBitmap(std::move(data), 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(fromSkSp(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::isTextureBacked() 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 |