/* -*- Mode: C++; tab-width: 20; 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/. */ #ifndef GFX_UTILS_H #define GFX_UTILS_H #include "gfxMatrix.h" #include "gfxRect.h" #include "gfxTypes.h" #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" #include "nsContentUtils.h" #include "nsPrintfCString.h" #include "nsRegionFwd.h" #include "mozilla/gfx/Rect.h" #include "mozilla/CheckedInt.h" #include "mozilla/webrender/WebRenderTypes.h" #include "qcms.h" class gfxASurface; class gfxDrawable; class gfxTextRun; struct gfxQuad; class nsICookieJarSettings; class nsIInputStream; class nsIGfxInfo; namespace mozilla { namespace dom { class Element; } // namespace dom namespace layers { class WebRenderBridgeChild; class GlyphArray; struct PlanarYCbCrData; class WebRenderCommand; } // namespace layers namespace image { class ImageRegion; } // namespace image namespace wr { class DisplayListBuilder; } // namespace wr } // namespace mozilla enum class ImageType { BMP, ICO, JPEG, PNG, }; class gfxUtils { public: typedef mozilla::gfx::DataSourceSurface DataSourceSurface; typedef mozilla::gfx::DrawTarget DrawTarget; typedef mozilla::gfx::IntPoint IntPoint; typedef mozilla::gfx::Matrix Matrix; typedef mozilla::gfx::Matrix4x4 Matrix4x4; typedef mozilla::gfx::SourceSurface SourceSurface; typedef mozilla::gfx::SurfaceFormat SurfaceFormat; typedef mozilla::image::ImageRegion ImageRegion; /* * Premultiply or Unpremultiply aSourceSurface, writing the result * to aDestSurface or back into aSourceSurface if aDestSurface is null. * * If aDestSurface is given, it must have identical format, dimensions, and * stride as the source. * * If the source is not SurfaceFormat::A8R8G8B8_UINT32, no operation is * performed. If aDestSurface is given, the data is copied over. */ static bool PremultiplyDataSurface(DataSourceSurface* srcSurf, DataSourceSurface* destSurf); static bool UnpremultiplyDataSurface(DataSourceSurface* srcSurf, DataSourceSurface* destSurf); static already_AddRefed CreatePremultipliedDataSurface( DataSourceSurface* srcSurf); static already_AddRefed CreateUnpremultipliedDataSurface( DataSourceSurface* srcSurf); static void ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength); /** * Draw something drawable while working around limitations like bad support * for EXTEND_PAD, lack of source-clipping, or cairo / pixman bugs with * extreme user-space-to-image-space transforms. * * The input parameters here usually come from the output of our image * snapping algorithm in nsLayoutUtils.cpp. * This method is split from nsLayoutUtils::DrawPixelSnapped to allow for * adjusting the parameters. For example, certain images with transparent * margins only have a drawable subimage. For those images, imgFrame::Draw * will tweak the rects and transforms that it gets from the pixel snapping * algorithm before passing them on to this method. */ static void DrawPixelSnapped(gfxContext* aContext, gfxDrawable* aDrawable, const gfxSize& aImageSize, const ImageRegion& aRegion, const mozilla::gfx::SurfaceFormat aFormat, mozilla::gfx::SamplingFilter aSamplingFilter, uint32_t aImageFlags = imgIContainer::FLAG_NONE, gfxFloat aOpacity = 1.0, bool aUseOptimalFillOp = true); /** * Clip aContext to the region aRegion. */ static void ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion); /** * Clip aTarget to the region aRegion. */ static void ClipToRegion(mozilla::gfx::DrawTarget* aTarget, const nsIntRegion& aRegion); /* * Convert image format to depth value */ static int ImageFormatToDepth(gfxImageFormat aFormat); /** * Return the transform matrix that maps aFrom to the rectangle defined by * aToTopLeft/aToTopRight/aToBottomRight. aFrom must be * nonempty and the destination rectangle must be axis-aligned. */ static gfxMatrix TransformRectToRect(const gfxRect& aFrom, const gfxPoint& aToTopLeft, const gfxPoint& aToTopRight, const gfxPoint& aToBottomRight); static Matrix TransformRectToRect(const gfxRect& aFrom, const IntPoint& aToTopLeft, const IntPoint& aToTopRight, const IntPoint& aToBottomRight); /** * If aIn can be represented exactly using an gfx::IntRect (i.e. * integer-aligned edges and coordinates in the int32_t range) then we * set aOut to that rectangle, otherwise return failure. */ static bool GfxRectToIntRect(const gfxRect& aIn, mozilla::gfx::IntRect* aOut); /* Conditions this border to Cairo's max coordinate space. * The caller can check IsEmpty() after Condition() -- if it's TRUE, * the caller can possibly avoid doing any extra rendering. */ static void ConditionRect(gfxRect& aRect); /* * Transform this rectangle with aMatrix, resulting in a gfxQuad. */ static gfxQuad TransformToQuad(const gfxRect& aRect, const mozilla::gfx::Matrix4x4& aMatrix); /** * Return the smallest power of kScaleResolution (2) greater than or equal to * aVal. If aRoundDown is specified, the power of 2 will rather be less than * or equal to aVal. */ static float ClampToScaleFactor(float aVal, bool aRoundDown = false); /** * We can snap layer transforms for two reasons: * 1) To avoid unnecessary resampling when a transform is a translation * by a non-integer number of pixels. * Snapping the translation to an integer number of pixels avoids * blurring the layer and can be faster to composite. * 2) When a layer is used to render a rectangular object, we need to * emulate the rendering of rectangular inactive content and snap the * edges of the rectangle to pixel boundaries. This is both to ensure * layer rendering is consistent with inactive content rendering, and to * avoid seams. * This function implements type 1 snapping. If aTransform is a 2D * translation, and this layer's layer manager has enabled snapping * (which is the default), return aTransform with the translation snapped * to nearest pixels. Otherwise just return aTransform. Call this when the * layer does not correspond to a single rectangular content object. * This function does not try to snap if aTransform has a scale, because in * that case resampling is inevitable and there's no point in trying to * avoid it. In fact snapping can cause problems because pixel edges in the * layer's content can be rendered unpredictably (jiggling) as the scale * interacts with the snapping of the translation, especially with animated * transforms. * @param aResidualTransform a transform to apply before the result transform * in order to get the results to completely match aTransform. */ static Matrix4x4 SnapTransformTranslation(const Matrix4x4& aTransform, Matrix* aResidualTransform); static Matrix SnapTransformTranslation(const Matrix& aTransform, Matrix* aResidualTransform); static Matrix4x4 SnapTransformTranslation3D(const Matrix4x4& aTransform, Matrix* aResidualTransform); /** * See comment for SnapTransformTranslation. * This function implements type 2 snapping. If aTransform is a translation * and/or scale, transform aSnapRect by aTransform, snap to pixel boundaries, * and return the transform that maps aSnapRect to that rect. Otherwise * just return aTransform. * @param aSnapRect a rectangle whose edges should be snapped to pixel * boundaries in the destination surface. * @param aResidualTransform a transform to apply before the result transform * in order to get the results to completely match aTransform. */ static Matrix4x4 SnapTransform(const Matrix4x4& aTransform, const gfxRect& aSnapRect, Matrix* aResidualTransform); static Matrix SnapTransform(const Matrix& aTransform, const gfxRect& aSnapRect, Matrix* aResidualTransform); /** * Clears surface to aColor (which defaults to transparent black). */ static void ClearThebesSurface(gfxASurface* aSurface); static const float* YuvToRgbMatrix4x3RowMajor( mozilla::gfx::YUVColorSpace aYUVColorSpace); static const float* YuvToRgbMatrix3x3ColumnMajor( mozilla::gfx::YUVColorSpace aYUVColorSpace); static const float* YuvToRgbMatrix4x4ColumnMajor( mozilla::gfx::YUVColorSpace aYUVColorSpace); static mozilla::Maybe CicpToColorSpace( const mozilla::gfx::CICP::MatrixCoefficients, const mozilla::gfx::CICP::ColourPrimaries, mozilla::LazyLogModule& aLogger); static mozilla::Maybe CicpToColorPrimaries( const mozilla::gfx::CICP::ColourPrimaries, mozilla::LazyLogModule& aLogger); static mozilla::Maybe CicpToTransferFunction( const mozilla::gfx::CICP::TransferCharacteristics); /** * Creates a copy of aSurface, but having the SurfaceFormat aFormat. * * This function always creates a new surface. Do not call it if aSurface's * format is the same as aFormat. Such a non-conversion would just be an * unnecessary and wasteful copy (this function asserts to prevent that). * * This function is intended to be called by code that needs to access the * pixel data of the surface, but doesn't want to have lots of branches * to handle different pixel data formats (code which would become out of * date if and when new formats are added). Callers can use this function * to copy the surface to a specified format so that they only have to * handle pixel data in that one format. * * WARNING: There are format conversions that will not be supported by this * function. It very much depends on what the Moz2D backends support. If * the temporary B8G8R8A8 DrawTarget that this function creates has a * backend that supports DrawSurface() calls passing a surface with * aSurface's format it will work. Otherwise it will not. * * *** IMPORTANT PERF NOTE *** * * This function exists partly because format conversion is fraught with * non-obvious performance hazards, so we don't want Moz2D consumers to be * doing their own format conversion. Do not try to do so, or at least read * the comments in this functions implemtation. That said, the copy that * this function carries out has a cost and, although this function tries * to avoid perf hazards such as expensive uploads to/readbacks from the * GPU, it can't guarantee that it always successfully does so. Perf * critical code that can directly handle the common formats that it * encounters in a way that is cheaper than a copy-with-format-conversion * should consider doing so, and only use this function as a fallback to * handle other formats. * * XXXjwatt it would be nice if SourceSurface::GetDataSurface took a * SurfaceFormat argument (with a default argument meaning "use the * existing surface's format") and returned a DataSourceSurface in that * format. (There would then be an issue of callers maybe failing to * realize format conversion may involve expensive copying/uploading/ * readback.) */ static already_AddRefed CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface, SurfaceFormat aFormat); /** * Return a color that can be used to identify a frame with a given frame * number. The colors will cycle after sNumFrameColors. You can query colors * 0 .. sNumFrameColors-1 to get all the colors back. */ static const mozilla::gfx::DeviceColor& GetColorForFrameNumber( uint64_t aFrameNumber); static const uint32_t sNumFrameColors; enum BinaryOrData { eBinaryEncode, eDataURIEncode }; /** * Encodes the given surface to PNG/JPEG/BMP/etc. using imgIEncoder. * If both aFile and aString are null, the encoded data is copied to the * clipboard. * * @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 reasonable value for the passed MIME-type * (i.e. for the type of encoder that will be created). * * @aBinaryOrData Flag used to determine if the surface is simply encoded * to the requested binary image format, or if the binary image is * further converted to base-64 and written out as a 'data:' URI. * * @aFile If specified, the encoded data is written out to aFile. * * @aString If specified, the encoded data is written out to aString. * * TODO: Copying to the clipboard as a binary file is not currently * supported. */ static nsresult EncodeSourceSurface(SourceSurface* aSurface, const ImageType aImageType, const nsAString& aOutputOptions, 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 reasonable 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 reasonable 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. */ static void WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile); static void WriteAsPNG(SourceSurface* aSurface, const char* aFile); static void WriteAsPNG(DrawTarget* aDT, const nsAString& aFile); static void WriteAsPNG(DrawTarget* aDT, const char* aFile); /** * Dump as a PNG encoded Data URL to a FILE stream (using stdout by * default). * * Rather than giving aFile a default argument we have separate functions * to make them easier to use from a debugger. */ static void DumpAsDataURI(SourceSurface* aSourceSurface, FILE* aFile); static inline void DumpAsDataURI(SourceSurface* aSourceSurface) { DumpAsDataURI(aSourceSurface, stdout); } static void DumpAsDataURI(DrawTarget* aDT, FILE* aFile); static inline void DumpAsDataURI(DrawTarget* aDT) { DumpAsDataURI(aDT, stdout); } static nsCString GetAsDataURI(SourceSurface* aSourceSurface); static nsCString GetAsDataURI(DrawTarget* aDT); static nsCString GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface); static mozilla::UniquePtr GetImageBuffer( DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, int32_t* outFormat); static mozilla::UniquePtr GetImageBufferWithRandomNoise( DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, nsICookieJarSettings* aCookieJarSettings, int32_t* outFormat); static nsresult GetInputStream(DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, const char* aMimeType, const nsAString& aEncoderOptions, nsIInputStream** outStream); static nsresult GetInputStreamWithRandomNoise( DataSourceSurface* aSurface, bool aIsAlphaPremultiplied, const char* aMimeType, const nsAString& aEncoderOptions, nsICookieJarSettings* aCookieJarSettings, nsIInputStream** outStream); static void RemoveShaderCacheFromDiskIfNecessary(); /** * Copy to the clipboard as a PNG encoded Data URL. */ static void CopyAsDataURI(SourceSurface* aSourceSurface); static void CopyAsDataURI(DrawTarget* aDT); static bool DumpDisplayList(); static FILE* sDumpPaintFile; }; namespace mozilla { // Container for either a single element of type T, or an nsTArray. // Provides a minimal subset of nsTArray's API, just enough to support use // by ContextState for the clipsAndTransforms list, and by gfxTextRun for // its mGlyphRuns. // Using this instead of a simple nsTArray avoids an extra allocation in the // common case where no more than one element is ever added to the list. // Unlike an AutoTArray<..., 1>, this class is memmovable and therefore can // be used in ContextState without breaking its movability. template class ElementOrArray { union { T mElement; nsTArray mArray; }; enum class Tag : uint8_t { Element, Array, } mTag; // gfxTextRun::SortGlyphRuns and SanitizeGlyphRuns directly access the array. friend class ::gfxTextRun; nsTArray& Array() { MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array); return mArray; } public: // Construct as an empty array. ElementOrArray() : mTag(Tag::Array) { new (&mArray) nsTArray(); } // For now, don't support copy/move. ElementOrArray(const ElementOrArray&) = delete; ElementOrArray(ElementOrArray&&) = delete; ElementOrArray& operator=(const ElementOrArray&) = delete; ElementOrArray& operator=(ElementOrArray&&) = delete; // Destroy the appropriate variant. ~ElementOrArray() { switch (mTag) { case Tag::Element: mElement.~T(); break; case Tag::Array: mArray.~nsTArray(); break; } } size_t Length() const { return mTag == Tag::Element ? 1 : mArray.Length(); } T* AppendElement(const T& aElement) { switch (mTag) { case Tag::Element: { // Move the existing element into an array, then append the new one. T temp = std::move(mElement); mElement.~T(); mTag = Tag::Array; new (&mArray) nsTArray(); mArray.AppendElement(std::move(temp)); return mArray.AppendElement(aElement); } case Tag::Array: { // If currently empty, just store the element directly. if (mArray.IsEmpty()) { mArray.~nsTArray(); mTag = Tag::Element; new (&mElement) T(aElement); return &mElement; } // Otherwise, append it to the array. return mArray.AppendElement(aElement); } default: MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("invalid tag"); } } const T& LastElement() const { return mTag == Tag::Element ? mElement : mArray.LastElement(); } T& LastElement() { return mTag == Tag::Element ? mElement : mArray.LastElement(); } bool IsEmpty() const { return mTag == Tag::Element ? false : mArray.IsEmpty(); } void TruncateLength(uint32_t aLength = 0) { MOZ_DIAGNOSTIC_ASSERT(aLength <= Length()); switch (mTag) { case Tag::Element: if (aLength == 0) { // Destroy the single element, and convert to an empty array. mElement.~T(); mTag = Tag::Array; new (&mArray) nsTArray(); } break; case Tag::Array: mArray.TruncateLength(aLength); break; } } void Clear() { switch (mTag) { case Tag::Element: mElement.~T(); mTag = Tag::Array; new (&mArray) nsTArray(); break; case Tag::Array: mArray.Clear(); break; } } // Convert from Array to Element storage. Only to be used when the current // state is a single-element array! void ConvertToElement() { MOZ_DIAGNOSTIC_ASSERT(mTag == Tag::Array && mArray.Length() == 1); T temp = std::move(mArray[0]); mArray.~nsTArray(); mTag = Tag::Element; new (&mElement) T(std::move(temp)); } const T& operator[](uint32_t aIndex) const { MOZ_DIAGNOSTIC_ASSERT(aIndex < Length()); return mTag == Tag::Element ? mElement : mArray[aIndex]; } T& operator[](uint32_t aIndex) { MOZ_DIAGNOSTIC_ASSERT(aIndex < Length()); return mTag == Tag::Element ? mElement : mArray[aIndex]; } // Simple iterators to support range-for loops. const T* begin() const { return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin() : &mElement; } T* begin() { return mTag == Tag::Array ? mArray.IsEmpty() ? nullptr : &*mArray.begin() : &mElement; } const T* end() const { return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1; } T* end() { return mTag == Tag::Array ? begin() + mArray.Length() : &mElement + 1; } size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) { return mTag == Tag::Array ? mArray.ShallowSizeOfExcludingThis(aMallocSizeOf) : 0; } }; struct StyleAbsoluteColor; namespace gfx { /** * If the CMS mode is CMSMode::All, these functions transform the passed * color to a device color using the transform returned by * gfxPlatform::GetCMSRGBTransform(). If the CMS mode is some other value, the * color is returned unchanged (other than a type change to Moz2D Color, if * applicable). */ DeviceColor ToDeviceColor(const sRGBColor&); DeviceColor ToDeviceColor(const StyleAbsoluteColor&); DeviceColor ToDeviceColor(nscolor); sRGBColor ToSRGBColor(const StyleAbsoluteColor&); /** * Performs a checked multiply of the given width, height, and bytes-per-pixel * values. */ static inline CheckedInt SafeBytesForBitmap(uint32_t aWidth, uint32_t aHeight, unsigned aBytesPerPixel) { MOZ_ASSERT(aBytesPerPixel > 0); CheckedInt width = uint32_t(aWidth); CheckedInt height = uint32_t(aHeight); return width * height * aBytesPerPixel; } } // namespace gfx } // namespace mozilla #endif