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