michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "imgTools.h" michael@0: michael@0: #include "gfxUtils.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "mozilla/RefPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsError.h" michael@0: #include "imgLoader.h" michael@0: #include "imgICache.h" michael@0: #include "imgIContainer.h" michael@0: #include "imgIEncoder.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "ImageFactory.h" michael@0: #include "Image.h" michael@0: #include "ScriptedNotificationObserver.h" michael@0: #include "imgIScriptedNotificationObserver.h" michael@0: #include "gfxPlatform.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::image; michael@0: using namespace mozilla::gfx; michael@0: michael@0: /* ========== imgITools implementation ========== */ michael@0: michael@0: michael@0: michael@0: NS_IMPL_ISUPPORTS(imgTools, imgITools) michael@0: michael@0: imgTools::imgTools() michael@0: { michael@0: /* member initializers and constructor code */ michael@0: } michael@0: michael@0: imgTools::~imgTools() michael@0: { michael@0: /* destructor code */ michael@0: } michael@0: michael@0: NS_IMETHODIMP imgTools::DecodeImageData(nsIInputStream* aInStr, michael@0: const nsACString& aMimeType, michael@0: imgIContainer **aContainer) michael@0: { michael@0: NS_ABORT_IF_FALSE(*aContainer == nullptr, michael@0: "Cannot provide an existing image container to DecodeImageData"); michael@0: michael@0: return DecodeImage(aInStr, aMimeType, aContainer); michael@0: } michael@0: michael@0: NS_IMETHODIMP imgTools::DecodeImage(nsIInputStream* aInStr, michael@0: const nsACString& aMimeType, michael@0: imgIContainer **aContainer) michael@0: { michael@0: nsresult rv; michael@0: nsRefPtr image; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aInStr); michael@0: michael@0: // Create a new image container to hold the decoded data. michael@0: nsAutoCString mimeType(aMimeType); michael@0: image = ImageFactory::CreateAnonymousImage(mimeType); michael@0: michael@0: if (image->HasError()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Prepare the input stream. michael@0: nsCOMPtr inStream = aInStr; michael@0: if (!NS_InputStreamIsBuffered(aInStr)) { michael@0: nsCOMPtr bufStream; michael@0: rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), aInStr, 1024); michael@0: if (NS_SUCCEEDED(rv)) michael@0: inStream = bufStream; michael@0: } michael@0: michael@0: // Figure out how much data we've been passed. michael@0: uint64_t length; michael@0: rv = inStream->Available(&length); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(length <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); michael@0: michael@0: // Send the source data to the Image. michael@0: rv = image->OnImageDataAvailable(nullptr, nullptr, inStream, 0, uint32_t(length)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Let the Image know we've sent all the data. michael@0: rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // All done. michael@0: NS_ADDREF(*aContainer = image.get()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * This takes a DataSourceSurface rather than a SourceSurface because some michael@0: * of the callers have a DataSourceSurface and we don't want to call michael@0: * GetDataSurface on such surfaces since that may incure a conversion to michael@0: * SurfaceType::DATA which we don't need. michael@0: */ michael@0: static nsresult EncodeImageData(DataSourceSurface* aDataSurface, michael@0: const nsACString& aMimeType, michael@0: const nsAString& aOutputOptions, michael@0: nsIInputStream **aStream) michael@0: { michael@0: MOZ_ASSERT(aDataSurface->GetFormat() == SurfaceFormat::B8G8R8A8, michael@0: "We're assuming B8G8R8A8"); michael@0: michael@0: // Get an image encoder for the media type michael@0: nsAutoCString encoderCID( michael@0: NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType); michael@0: michael@0: nsCOMPtr encoder = do_CreateInstance(encoderCID.get()); michael@0: if (!encoder) michael@0: return NS_IMAGELIB_ERROR_NO_ENCODER; michael@0: michael@0: DataSourceSurface::MappedSurface map; michael@0: if (!aDataSurface->Map(DataSourceSurface::MapType::READ, &map)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: IntSize size = aDataSurface->GetSize(); michael@0: uint32_t dataLength = map.mStride * size.height; michael@0: michael@0: // Encode the bitmap michael@0: nsresult rv = encoder->InitFromData(map.mData, michael@0: dataLength, michael@0: size.width, michael@0: size.height, michael@0: map.mStride, michael@0: imgIEncoder::INPUT_FORMAT_HOSTARGB, michael@0: aOutputOptions); michael@0: aDataSurface->Unmap(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return CallQueryInterface(encoder, aStream); michael@0: } michael@0: michael@0: NS_IMETHODIMP imgTools::EncodeImage(imgIContainer *aContainer, michael@0: const nsACString& aMimeType, michael@0: const nsAString& aOutputOptions, michael@0: nsIInputStream **aStream) michael@0: { michael@0: // Use frame 0 from the image container. michael@0: RefPtr frame = michael@0: aContainer->GetFrame(imgIContainer::FRAME_FIRST, michael@0: imgIContainer::FLAG_SYNC_DECODE); michael@0: NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); michael@0: michael@0: RefPtr dataSurface; michael@0: michael@0: if (frame->GetFormat() == SurfaceFormat::B8G8R8A8) { michael@0: dataSurface = frame->GetDataSurface(); michael@0: } else { michael@0: // Convert format to SurfaceFormat::B8G8R8A8 michael@0: dataSurface = gfxUtils:: michael@0: CopySurfaceToDataSourceSurfaceWithFormat(frame, michael@0: SurfaceFormat::B8G8R8A8); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); michael@0: michael@0: return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream); michael@0: } michael@0: michael@0: NS_IMETHODIMP imgTools::EncodeScaledImage(imgIContainer *aContainer, michael@0: const nsACString& aMimeType, michael@0: int32_t aScaledWidth, michael@0: int32_t aScaledHeight, michael@0: const nsAString& aOutputOptions, michael@0: nsIInputStream **aStream) michael@0: { michael@0: NS_ENSURE_ARG(aScaledWidth >= 0 && aScaledHeight >= 0); michael@0: michael@0: // If no scaled size is specified, we'll just encode the image at its michael@0: // original size (no scaling). michael@0: if (aScaledWidth == 0 && aScaledHeight == 0) { michael@0: return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream); michael@0: } michael@0: michael@0: // Use frame 0 from the image container. michael@0: RefPtr frame = michael@0: aContainer->GetFrame(imgIContainer::FRAME_FIRST, michael@0: imgIContainer::FLAG_SYNC_DECODE); michael@0: NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); michael@0: michael@0: int32_t frameWidth = frame->GetSize().width; michael@0: int32_t frameHeight = frame->GetSize().height; michael@0: michael@0: // If the given width or height is zero we'll replace it with the image's michael@0: // original dimensions. michael@0: if (aScaledWidth == 0) { michael@0: aScaledWidth = frameWidth; michael@0: } else if (aScaledHeight == 0) { michael@0: aScaledHeight = frameHeight; michael@0: } michael@0: michael@0: RefPtr dataSurface = michael@0: Factory::CreateDataSourceSurface(IntSize(aScaledWidth, aScaledHeight), michael@0: SurfaceFormat::B8G8R8A8); michael@0: DataSourceSurface::MappedSurface map; michael@0: if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: RefPtr dt = michael@0: Factory::CreateDrawTargetForData(BackendType::CAIRO, michael@0: map.mData, michael@0: dataSurface->GetSize(), michael@0: map.mStride, michael@0: SurfaceFormat::B8G8R8A8); michael@0: dt->DrawSurface(frame, michael@0: Rect(0, 0, aScaledWidth, aScaledHeight), michael@0: Rect(0, 0, frameWidth, frameHeight), michael@0: DrawSurfaceOptions(), michael@0: DrawOptions(1.0f, CompositionOp::OP_SOURCE)); michael@0: michael@0: dataSurface->Unmap(); michael@0: michael@0: return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream); michael@0: } michael@0: michael@0: NS_IMETHODIMP imgTools::EncodeCroppedImage(imgIContainer *aContainer, michael@0: const nsACString& aMimeType, michael@0: int32_t aOffsetX, michael@0: int32_t aOffsetY, michael@0: int32_t aWidth, michael@0: int32_t aHeight, michael@0: const nsAString& aOutputOptions, michael@0: nsIInputStream **aStream) michael@0: { michael@0: NS_ENSURE_ARG(aOffsetX >= 0 && aOffsetY >= 0 && aWidth >= 0 && aHeight >= 0); michael@0: michael@0: // Offsets must be zero when no width and height are given or else we're out michael@0: // of bounds. michael@0: NS_ENSURE_ARG(aWidth + aHeight > 0 || aOffsetX + aOffsetY == 0); michael@0: michael@0: // If no size is specified then we'll preserve the image's original dimensions michael@0: // and don't need to crop. michael@0: if (aWidth == 0 && aHeight == 0) { michael@0: return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream); michael@0: } michael@0: michael@0: // Use frame 0 from the image container. michael@0: RefPtr frame = michael@0: aContainer->GetFrame(imgIContainer::FRAME_FIRST, michael@0: imgIContainer::FLAG_SYNC_DECODE); michael@0: NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); michael@0: michael@0: int32_t frameWidth = frame->GetSize().width; michael@0: int32_t frameHeight = frame->GetSize().height; michael@0: michael@0: // If the given width or height is zero we'll replace it with the image's michael@0: // original dimensions. michael@0: if (aWidth == 0) { michael@0: aWidth = frameWidth; michael@0: } else if (aHeight == 0) { michael@0: aHeight = frameHeight; michael@0: } michael@0: michael@0: // Check that the given crop rectangle is within image bounds. michael@0: NS_ENSURE_ARG(frameWidth >= aOffsetX + aWidth && michael@0: frameHeight >= aOffsetY + aHeight); michael@0: michael@0: RefPtr dataSurface = michael@0: Factory::CreateDataSourceSurface(IntSize(aWidth, aHeight), michael@0: SurfaceFormat::B8G8R8A8); michael@0: DataSourceSurface::MappedSurface map; michael@0: if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: RefPtr dt = michael@0: Factory::CreateDrawTargetForData(BackendType::CAIRO, michael@0: map.mData, michael@0: dataSurface->GetSize(), michael@0: map.mStride, michael@0: SurfaceFormat::B8G8R8A8); michael@0: dt->CopySurface(frame, michael@0: IntRect(aOffsetX, aOffsetY, aWidth, aHeight), michael@0: IntPoint(0, 0)); michael@0: michael@0: dataSurface->Unmap(); michael@0: michael@0: return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream); michael@0: } michael@0: michael@0: NS_IMETHODIMP imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver* aInner, michael@0: imgINotificationObserver** aObserver) michael@0: { michael@0: NS_ADDREF(*aObserver = new ScriptedNotificationObserver(aInner)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: imgTools::GetImgLoaderForDocument(nsIDOMDocument* aDoc, imgILoader** aLoader) michael@0: { michael@0: nsCOMPtr doc = do_QueryInterface(aDoc); michael@0: NS_IF_ADDREF(*aLoader = nsContentUtils::GetImgLoaderForDocument(doc)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: imgTools::GetImgCacheForDocument(nsIDOMDocument* aDoc, imgICache** aCache) michael@0: { michael@0: nsCOMPtr loader; michael@0: nsresult rv = GetImgLoaderForDocument(aDoc, getter_AddRefs(loader)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return CallQueryInterface(loader, aCache); michael@0: }