From b23a9763fba042f154f57960b52fe2d8e5105ffc Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Tue, 14 Mar 2023 13:31:37 +0000 Subject: [PATCH] Bug 1816559 - Add ability to encode gfx surface as stream or bytes r=mstange Differential Revision: https://phabricator.services.mozilla.com/D170513 --- gfx/thebes/gfxUtils.cpp | 123 +++++++++++++++++++++++++--------------- gfx/thebes/gfxUtils.h | 40 +++++++++++++ 2 files changed, 118 insertions(+), 45 deletions(-) diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index 97582f1889d9..ab141d716702 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -40,7 +40,6 @@ #include "mozilla/StaticPrefs_layout.h" #include "mozilla/UniquePtrExtensions.h" #include "mozilla/Unused.h" -#include "mozilla/Vector.h" #include "mozilla/webrender/webrender_ffi.h" #include "nsAppRunner.h" #include "nsComponentManagerUtils.h" @@ -1070,20 +1069,15 @@ const gfx::DeviceColor& gfxUtils::GetColorForFrameNumber( return colors[aFrameNumber % sNumFrameColors]; } -/* static */ -nsresult gfxUtils::EncodeSourceSurface(SourceSurface* aSurface, - const ImageType aImageType, - const nsAString& aOutputOptions, - BinaryOrData aBinaryOrData, FILE* aFile, - nsACString* aStrOut) { - MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut, - "Copying binary encoding to clipboard not currently supported"); - +// static +nsresult gfxUtils::EncodeSourceSurfaceAsStream(SourceSurface* aSurface, + const ImageType aImageType, + const nsAString& aOutputOptions, + nsIInputStream** aOutStream) { const IntSize size = aSurface->GetSize(); if (size.IsEmpty()) { - return NS_ERROR_INVALID_ARG; + return NS_ERROR_FAILURE; } - const Size floatSize(size.width, size.height); RefPtr dataSurface; if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) { @@ -1132,53 +1126,91 @@ nsresult gfxUtils::EncodeSourceSurface(SourceSurface* aSurface, size.width, size.height, map.mStride, imgIEncoder::INPUT_FORMAT_HOSTARGB, aOutputOptions); dataSurface->Unmap(); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } nsCOMPtr imgStream(encoder); if (!imgStream) { return NS_ERROR_FAILURE; } + imgStream.forget(aOutStream); + + return NS_OK; +} + +// static +Maybe> gfxUtils::EncodeSourceSurfaceAsBytes( + SourceSurface* aSurface, const ImageType aImageType, + const nsAString& aOutputOptions) { + nsCOMPtr imgStream; + nsresult rv = EncodeSourceSurfaceAsStream( + aSurface, aImageType, aOutputOptions, getter_AddRefs(imgStream)); + if (NS_FAILED(rv)) { + return Nothing(); + } + uint64_t bufSize64; rv = imgStream->Available(&bufSize64); - NS_ENSURE_SUCCESS(rv, rv); - - NS_ENSURE_TRUE(bufSize64 < UINT32_MAX - 16, NS_ERROR_FAILURE); - - uint32_t bufSize = (uint32_t)bufSize64; - - // ...leave a little extra room so we can call read again and make sure we - // got everything. 16 bytes for better padding (maybe) - bufSize += 16; - uint32_t imgSize = 0; - Vector imgData; - if (!imgData.initCapacity(bufSize)) { - return NS_ERROR_OUT_OF_MEMORY; + if (NS_FAILED(rv) || bufSize64 > UINT32_MAX) { + return Nothing(); } - uint32_t numReadThisTime = 0; - while ((rv = imgStream->Read(imgData.begin() + imgSize, bufSize - imgSize, - &numReadThisTime)) == NS_OK && - numReadThisTime > 0) { - // Update the length of the vector without overwriting the new data. - if (!imgData.growByUninitialized(numReadThisTime)) { - return NS_ERROR_OUT_OF_MEMORY; + + uint32_t bytesLeft = static_cast(bufSize64); + + nsTArray imgData; + imgData.SetLength(bytesLeft); + uint8_t* bytePtr = imgData.Elements(); + + while (bytesLeft > 0) { + uint32_t bytesRead = 0; + rv = imgStream->Read(reinterpret_cast(bytePtr), bytesLeft, + &bytesRead); + if (NS_FAILED(rv) || bytesRead == 0) { + return Nothing(); } - imgSize += numReadThisTime; - if (imgSize == bufSize) { - // need a bigger buffer, just double - bufSize *= 2; - if (!imgData.resizeUninitialized(bufSize)) { - return NS_ERROR_OUT_OF_MEMORY; - } - } + bytePtr += bytesRead; + bytesLeft -= bytesRead; } - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(!imgData.empty(), NS_ERROR_FAILURE); + +#ifdef DEBUG + + // Currently, all implementers of imgIEncoder report their exact size through + // nsIInputStream::Available(), but let's explicitly state that we rely on + // that behavior for the algorithm above. + + char dummy = 0; + uint32_t bytesRead = 0; + rv = imgStream->Read(&dummy, 1, &bytesRead); + MOZ_ASSERT(NS_SUCCEEDED(rv) && bytesRead == 0); + +#endif + + return Some(std::move(imgData)); +} + +/* static */ +nsresult gfxUtils::EncodeSourceSurface(SourceSurface* aSurface, + const ImageType aImageType, + const nsAString& aOutputOptions, + BinaryOrData aBinaryOrData, FILE* aFile, + nsACString* aStrOut) { + MOZ_ASSERT(aBinaryOrData == gfxUtils::eDataURIEncode || aFile || aStrOut, + "Copying binary encoding to clipboard not currently supported"); + + auto maybeImgData = + EncodeSourceSurfaceAsBytes(aSurface, aImageType, aOutputOptions); + if (!maybeImgData) { + return NS_ERROR_FAILURE; + } + + nsTArray& imgData = *maybeImgData; if (aBinaryOrData == gfxUtils::eBinaryEncode) { if (aFile) { - Unused << fwrite(imgData.begin(), 1, imgSize, aFile); + Unused << fwrite(imgData.Elements(), 1, imgData.Length(), aFile); } return NS_OK; } @@ -1208,7 +1240,8 @@ nsresult gfxUtils::EncodeSourceSurface(SourceSurface* aSurface, } dataURI.AppendLiteral(";base64,"); - rv = Base64EncodeAppend(imgData.begin(), imgSize, dataURI); + nsresult rv = Base64EncodeAppend(reinterpret_cast(imgData.Elements()), + imgData.Length(), dataURI); NS_ENSURE_SUCCESS(rv, rv); if (aFile) { diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index 67e92b15e113..1eefea051ddd 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -12,6 +12,7 @@ #include "ImageTypes.h" #include "imgIContainer.h" #include "mozilla/gfx/2D.h" +#include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" #include "nsColor.h" @@ -325,6 +326,45 @@ class gfxUtils { BinaryOrData aBinaryOrData, FILE* aFile, nsACString* aString = nullptr); + /** + * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder + * and returns the result as an nsIInputStream. + * + * @param aSurface The source surface to encode + * + * @param aImageType The image type that the surface is to be encoded to. + * Used to create an appropriate imgIEncoder instance to do the encoding. + * + * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as + * the value of the |outputOptions| parameter. Callers are responsible + * for making sure that this is a sane value for the passed MIME-type + * (i.e. for the type of encoder that will be created). + * + * @param aOutStream pointer to the output stream + * + */ + static nsresult EncodeSourceSurfaceAsStream(SourceSurface* aSurface, + const ImageType aImageType, + const nsAString& aOutputOptions, + nsIInputStream** aOutStream); + + /** + * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder + * and returns the result as a vector of bytes + * + * @param aImageType The image type that the surface is to be encoded to. + * Used to create an appropriate imgIEncoder instance to do the encoding. + * + * @param aOutputOptions Passed directly to imgIEncoder::InitFromData as + * the value of the |outputOptions| parameter. Callers are responsible + * for making sure that this is a sane value for the passed MIME-type + * (i.e. for the type of encoder that will be created). + * + */ + static mozilla::Maybe> EncodeSourceSurfaceAsBytes( + SourceSurface* aSurface, const ImageType aImageType, + const nsAString& aOutputOptions); + /** * Write as a PNG file to the path aFile. */