michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- 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 "gfxUtils.h" michael@0: #include "gfxContext.h" michael@0: #include "gfxPlatform.h" michael@0: #include "gfxDrawable.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "mozilla/RefPtr.h" michael@0: #include "nsRegion.h" michael@0: #include "yuv_convert.h" michael@0: #include "ycbcr_to_rgb565.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "ImageContainer.h" michael@0: #include "gfx2DGlue.h" michael@0: michael@0: #ifdef XP_WIN michael@0: #include "gfxWindowsPlatform.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::layers; michael@0: using namespace mozilla::gfx; michael@0: michael@0: #include "DeprecatedPremultiplyTables.h" michael@0: michael@0: static const uint8_t PremultiplyValue(uint8_t a, uint8_t v) { michael@0: return gfxUtils::sPremultiplyTable[a*256+v]; michael@0: } michael@0: michael@0: static const uint8_t UnpremultiplyValue(uint8_t a, uint8_t v) { michael@0: return gfxUtils::sUnpremultiplyTable[a*256+v]; michael@0: } michael@0: michael@0: void michael@0: gfxUtils::PremultiplyImageSurface(gfxImageSurface *aSourceSurface, michael@0: gfxImageSurface *aDestSurface) michael@0: { michael@0: if (!aDestSurface) michael@0: aDestSurface = aSourceSurface; michael@0: michael@0: MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() && michael@0: aSourceSurface->Width() == aDestSurface->Width() && michael@0: aSourceSurface->Height() == aDestSurface->Height() && michael@0: aSourceSurface->Stride() == aDestSurface->Stride(), michael@0: "Source and destination surfaces don't have identical characteristics"); michael@0: michael@0: MOZ_ASSERT(aSourceSurface->Stride() == aSourceSurface->Width() * 4, michael@0: "Source surface stride isn't tightly packed"); michael@0: michael@0: // Only premultiply ARGB32 michael@0: if (aSourceSurface->Format() != gfxImageFormat::ARGB32) { michael@0: if (aDestSurface != aSourceSurface) { michael@0: memcpy(aDestSurface->Data(), aSourceSurface->Data(), michael@0: aSourceSurface->Stride() * aSourceSurface->Height()); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: uint8_t *src = aSourceSurface->Data(); michael@0: uint8_t *dst = aDestSurface->Data(); michael@0: michael@0: uint32_t dim = aSourceSurface->Width() * aSourceSurface->Height(); michael@0: for (uint32_t i = 0; i < dim; ++i) { michael@0: #ifdef IS_LITTLE_ENDIAN michael@0: uint8_t b = *src++; michael@0: uint8_t g = *src++; michael@0: uint8_t r = *src++; michael@0: uint8_t a = *src++; michael@0: michael@0: *dst++ = PremultiplyValue(a, b); michael@0: *dst++ = PremultiplyValue(a, g); michael@0: *dst++ = PremultiplyValue(a, r); michael@0: *dst++ = a; michael@0: #else michael@0: uint8_t a = *src++; michael@0: uint8_t r = *src++; michael@0: uint8_t g = *src++; michael@0: uint8_t b = *src++; michael@0: michael@0: *dst++ = a; michael@0: *dst++ = PremultiplyValue(a, r); michael@0: *dst++ = PremultiplyValue(a, g); michael@0: *dst++ = PremultiplyValue(a, b); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxUtils::UnpremultiplyImageSurface(gfxImageSurface *aSourceSurface, michael@0: gfxImageSurface *aDestSurface) michael@0: { michael@0: if (!aDestSurface) michael@0: aDestSurface = aSourceSurface; michael@0: michael@0: MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() && michael@0: aSourceSurface->Width() == aDestSurface->Width() && michael@0: aSourceSurface->Height() == aDestSurface->Height(), michael@0: "Source and destination surfaces don't have identical characteristics"); michael@0: michael@0: // Only premultiply ARGB32 michael@0: if (aSourceSurface->Format() != gfxImageFormat::ARGB32) { michael@0: if (aDestSurface != aSourceSurface) { michael@0: aDestSurface->CopyFrom(aSourceSurface); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: uint8_t *src = aSourceSurface->Data(); michael@0: uint8_t *dst = aDestSurface->Data(); michael@0: michael@0: for (int32_t i = 0; i < aSourceSurface->Height(); ++i) { michael@0: uint8_t *srcRow = src + (i * aSourceSurface->Stride()); michael@0: uint8_t *dstRow = dst + (i * aDestSurface->Stride()); michael@0: michael@0: for (int32_t j = 0; j < aSourceSurface->Width(); ++j) { michael@0: #ifdef IS_LITTLE_ENDIAN michael@0: uint8_t b = *srcRow++; michael@0: uint8_t g = *srcRow++; michael@0: uint8_t r = *srcRow++; michael@0: uint8_t a = *srcRow++; michael@0: michael@0: *dstRow++ = UnpremultiplyValue(a, b); michael@0: *dstRow++ = UnpremultiplyValue(a, g); michael@0: *dstRow++ = UnpremultiplyValue(a, r); michael@0: *dstRow++ = a; michael@0: #else michael@0: uint8_t a = *srcRow++; michael@0: uint8_t r = *srcRow++; michael@0: uint8_t g = *srcRow++; michael@0: uint8_t b = *srcRow++; michael@0: michael@0: *dstRow++ = a; michael@0: *dstRow++ = UnpremultiplyValue(a, r); michael@0: *dstRow++ = UnpremultiplyValue(a, g); michael@0: *dstRow++ = UnpremultiplyValue(a, b); michael@0: #endif michael@0: } michael@0: } michael@0: } michael@0: michael@0: TemporaryRef michael@0: gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* aSurface) michael@0: { michael@0: // Only premultiply ARGB32 michael@0: if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) { michael@0: return aSurface; michael@0: } michael@0: michael@0: DataSourceSurface::MappedSurface map; michael@0: if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: RefPtr dest = Factory::CreateDataSourceSurfaceWithStride(aSurface->GetSize(), michael@0: aSurface->GetFormat(), michael@0: map.mStride); michael@0: michael@0: DataSourceSurface::MappedSurface destMap; michael@0: if (!dest->Map(DataSourceSurface::MapType::WRITE, &destMap)) { michael@0: aSurface->Unmap(); michael@0: return nullptr; michael@0: } michael@0: michael@0: uint8_t *src = map.mData; michael@0: uint8_t *dst = destMap.mData; michael@0: michael@0: for (int32_t i = 0; i < aSurface->GetSize().height; ++i) { michael@0: uint8_t *srcRow = src + (i * map.mStride); michael@0: uint8_t *dstRow = dst + (i * destMap.mStride); michael@0: michael@0: for (int32_t j = 0; j < aSurface->GetSize().width; ++j) { michael@0: #ifdef IS_LITTLE_ENDIAN michael@0: uint8_t b = *srcRow++; michael@0: uint8_t g = *srcRow++; michael@0: uint8_t r = *srcRow++; michael@0: uint8_t a = *srcRow++; michael@0: michael@0: *dstRow++ = UnpremultiplyValue(a, b); michael@0: *dstRow++ = UnpremultiplyValue(a, g); michael@0: *dstRow++ = UnpremultiplyValue(a, r); michael@0: *dstRow++ = a; michael@0: #else michael@0: uint8_t a = *srcRow++; michael@0: uint8_t r = *srcRow++; michael@0: uint8_t g = *srcRow++; michael@0: uint8_t b = *srcRow++; michael@0: michael@0: *dstRow++ = a; michael@0: *dstRow++ = UnpremultiplyValue(a, r); michael@0: *dstRow++ = UnpremultiplyValue(a, g); michael@0: *dstRow++ = UnpremultiplyValue(a, b); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: aSurface->Unmap(); michael@0: dest->Unmap(); michael@0: return dest; michael@0: } michael@0: michael@0: void michael@0: gfxUtils::ConvertBGRAtoRGBA(gfxImageSurface *aSourceSurface, michael@0: gfxImageSurface *aDestSurface) { michael@0: if (!aDestSurface) michael@0: aDestSurface = aSourceSurface; michael@0: michael@0: MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() && michael@0: aSourceSurface->Width() == aDestSurface->Width() && michael@0: aSourceSurface->Height() == aDestSurface->Height() && michael@0: aSourceSurface->Stride() == aDestSurface->Stride(), michael@0: "Source and destination surfaces don't have identical characteristics"); michael@0: michael@0: MOZ_ASSERT(aSourceSurface->Stride() == aSourceSurface->Width() * 4, michael@0: "Source surface stride isn't tightly packed"); michael@0: michael@0: MOZ_ASSERT(aSourceSurface->Format() == gfxImageFormat::ARGB32 || aSourceSurface->Format() == gfxImageFormat::RGB24, michael@0: "Surfaces must be ARGB32 or RGB24"); michael@0: michael@0: uint8_t *src = aSourceSurface->Data(); michael@0: uint8_t *dst = aDestSurface->Data(); michael@0: michael@0: uint32_t dim = aSourceSurface->Width() * aSourceSurface->Height(); michael@0: uint8_t *srcEnd = src + 4*dim; michael@0: michael@0: if (src == dst) { michael@0: uint8_t buffer[4]; michael@0: for (; src != srcEnd; src += 4) { michael@0: buffer[0] = src[2]; michael@0: buffer[1] = src[1]; michael@0: buffer[2] = src[0]; michael@0: michael@0: src[0] = buffer[0]; michael@0: src[1] = buffer[1]; michael@0: src[2] = buffer[2]; michael@0: } michael@0: } else { michael@0: for (; src != srcEnd; src += 4, dst += 4) { michael@0: dst[0] = src[2]; michael@0: dst[1] = src[1]; michael@0: dst[2] = src[0]; michael@0: dst[3] = src[3]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength) michael@0: { michael@0: uint8_t *src = aData; michael@0: uint8_t *srcEnd = src + aLength; michael@0: michael@0: uint8_t buffer[4]; michael@0: for (; src != srcEnd; src += 4) { michael@0: buffer[0] = src[2]; michael@0: buffer[1] = src[1]; michael@0: buffer[2] = src[0]; michael@0: michael@0: src[0] = buffer[0]; michael@0: src[1] = buffer[1]; michael@0: src[2] = buffer[2]; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: IsSafeImageTransformComponent(gfxFloat aValue) michael@0: { michael@0: return aValue >= -32768 && aValue <= 32767; michael@0: } michael@0: michael@0: #ifndef MOZ_GFX_OPTIMIZE_MOBILE michael@0: /** michael@0: * This returns the fastest operator to use for solid surfaces which have no michael@0: * alpha channel or their alpha channel is uniformly opaque. michael@0: * This differs per render mode. michael@0: */ michael@0: static gfxContext::GraphicsOperator michael@0: OptimalFillOperator() michael@0: { michael@0: #ifdef XP_WIN michael@0: if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() == michael@0: gfxWindowsPlatform::RENDER_DIRECT2D) { michael@0: // D2D -really- hates operator source. michael@0: return gfxContext::OPERATOR_OVER; michael@0: } else { michael@0: #endif michael@0: return gfxContext::OPERATOR_SOURCE; michael@0: #ifdef XP_WIN michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // EXTEND_PAD won't help us here; we have to create a temporary surface to hold michael@0: // the subimage of pixels we're allowed to sample. michael@0: static already_AddRefed michael@0: CreateSamplingRestrictedDrawable(gfxDrawable* aDrawable, michael@0: gfxContext* aContext, michael@0: const gfxMatrix& aUserSpaceToImageSpace, michael@0: const gfxRect& aSourceRect, michael@0: const gfxRect& aSubimage, michael@0: const gfxImageFormat aFormat) michael@0: { michael@0: PROFILER_LABEL("gfxUtils", "CreateSamplingRestricedDrawable"); michael@0: gfxRect userSpaceClipExtents = aContext->GetClipExtents(); michael@0: // This isn't optimal --- if aContext has a rotation then GetClipExtents michael@0: // will have to do a bounding-box computation, and TransformBounds might michael@0: // too, so we could get a better result if we computed image space clip michael@0: // extents in one go --- but it doesn't really matter and this is easier michael@0: // to understand. michael@0: gfxRect imageSpaceClipExtents = michael@0: aUserSpaceToImageSpace.TransformBounds(userSpaceClipExtents); michael@0: // Inflate by one pixel because bilinear filtering will sample at most michael@0: // one pixel beyond the computed image pixel coordinate. michael@0: imageSpaceClipExtents.Inflate(1.0); michael@0: michael@0: gfxRect needed = imageSpaceClipExtents.Intersect(aSourceRect); michael@0: needed = needed.Intersect(aSubimage); michael@0: needed.RoundOut(); michael@0: michael@0: // if 'needed' is empty, nothing will be drawn since aFill michael@0: // must be entirely outside the clip region, so it doesn't michael@0: // matter what we do here, but we should avoid trying to michael@0: // create a zero-size surface. michael@0: if (needed.IsEmpty()) michael@0: return nullptr; michael@0: michael@0: nsRefPtr drawable; michael@0: gfxIntSize size(int32_t(needed.Width()), int32_t(needed.Height())); michael@0: michael@0: nsRefPtr image = aDrawable->GetAsImageSurface(); michael@0: if (image && gfxRect(0, 0, image->GetSize().width, image->GetSize().height).Contains(needed)) { michael@0: nsRefPtr temp = image->GetSubimage(needed); michael@0: drawable = new gfxSurfaceDrawable(temp, size, gfxMatrix().Translate(-needed.TopLeft())); michael@0: } else { michael@0: mozilla::RefPtr target = michael@0: gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(ToIntSize(size), michael@0: ImageFormatToSurfaceFormat(aFormat)); michael@0: if (!target) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr tmpCtx = new gfxContext(target); michael@0: tmpCtx->SetOperator(OptimalFillOperator()); michael@0: aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), true, michael@0: GraphicsFilter::FILTER_FAST, gfxMatrix().Translate(needed.TopLeft())); michael@0: drawable = new gfxSurfaceDrawable(target, size, gfxMatrix().Translate(-needed.TopLeft())); michael@0: } michael@0: michael@0: return drawable.forget(); michael@0: } michael@0: #endif // !MOZ_GFX_OPTIMIZE_MOBILE michael@0: michael@0: // working around cairo/pixman bug (bug 364968) michael@0: // Our device-space-to-image-space transform may not be acceptable to pixman. michael@0: struct MOZ_STACK_CLASS AutoCairoPixmanBugWorkaround michael@0: { michael@0: AutoCairoPixmanBugWorkaround(gfxContext* aContext, michael@0: const gfxMatrix& aDeviceSpaceToImageSpace, michael@0: const gfxRect& aFill, michael@0: const gfxASurface* aSurface) michael@0: : mContext(aContext), mSucceeded(true), mPushedGroup(false) michael@0: { michael@0: // Quartz's limits for matrix are much larger than pixman michael@0: if (!aSurface || aSurface->GetType() == gfxSurfaceType::Quartz) michael@0: return; michael@0: michael@0: if (!IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.xx) || michael@0: !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.xy) || michael@0: !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.yx) || michael@0: !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.yy)) { michael@0: NS_WARNING("Scaling up too much, bailing out"); michael@0: mSucceeded = false; michael@0: return; michael@0: } michael@0: michael@0: if (IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.x0) && michael@0: IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.y0)) michael@0: return; michael@0: michael@0: // We'll push a group, which will hopefully reduce our transform's michael@0: // translation so it's in bounds. michael@0: gfxMatrix currentMatrix = mContext->CurrentMatrix(); michael@0: mContext->Save(); michael@0: michael@0: // Clip the rounded-out-to-device-pixels bounds of the michael@0: // transformed fill area. This is the area for the group we michael@0: // want to push. michael@0: mContext->IdentityMatrix(); michael@0: gfxRect bounds = currentMatrix.TransformBounds(aFill); michael@0: bounds.RoundOut(); michael@0: mContext->Clip(bounds); michael@0: mContext->SetMatrix(currentMatrix); michael@0: mContext->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: mContext->SetOperator(gfxContext::OPERATOR_OVER); michael@0: michael@0: mPushedGroup = true; michael@0: } michael@0: michael@0: ~AutoCairoPixmanBugWorkaround() michael@0: { michael@0: if (mPushedGroup) { michael@0: mContext->PopGroupToSource(); michael@0: mContext->Paint(); michael@0: mContext->Restore(); michael@0: } michael@0: } michael@0: michael@0: bool PushedGroup() { return mPushedGroup; } michael@0: bool Succeeded() { return mSucceeded; } michael@0: michael@0: private: michael@0: gfxContext* mContext; michael@0: bool mSucceeded; michael@0: bool mPushedGroup; michael@0: }; michael@0: michael@0: static gfxMatrix michael@0: DeviceToImageTransform(gfxContext* aContext, michael@0: const gfxMatrix& aUserSpaceToImageSpace) michael@0: { michael@0: gfxFloat deviceX, deviceY; michael@0: nsRefPtr currentTarget = michael@0: aContext->CurrentSurface(&deviceX, &deviceY); michael@0: gfxMatrix currentMatrix = aContext->CurrentMatrix(); michael@0: gfxMatrix deviceToUser = gfxMatrix(currentMatrix).Invert(); michael@0: deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY)); michael@0: return gfxMatrix(deviceToUser).Multiply(aUserSpaceToImageSpace); michael@0: } michael@0: michael@0: /* These heuristics are based on Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode() */ michael@0: #ifdef MOZ_GFX_OPTIMIZE_MOBILE michael@0: static GraphicsFilter ReduceResamplingFilter(GraphicsFilter aFilter, michael@0: int aImgWidth, int aImgHeight, michael@0: float aSourceWidth, float aSourceHeight) michael@0: { michael@0: // Images smaller than this in either direction are considered "small" and michael@0: // are not resampled ever (see below). michael@0: const int kSmallImageSizeThreshold = 8; michael@0: michael@0: // The amount an image can be stretched in a single direction before we michael@0: // say that it is being stretched so much that it must be a line or michael@0: // background that doesn't need resampling. michael@0: const float kLargeStretch = 3.0f; michael@0: michael@0: if (aImgWidth <= kSmallImageSizeThreshold michael@0: || aImgHeight <= kSmallImageSizeThreshold) { michael@0: // Never resample small images. These are often used for borders and michael@0: // rules (think 1x1 images used to make lines). michael@0: return GraphicsFilter::FILTER_NEAREST; michael@0: } michael@0: michael@0: if (aImgHeight * kLargeStretch <= aSourceHeight || aImgWidth * kLargeStretch <= aSourceWidth) { michael@0: // Large image tiling detected. michael@0: michael@0: // Don't resample if it is being tiled a lot in only one direction. michael@0: // This is trying to catch cases where somebody has created a border michael@0: // (which might be large) and then is stretching it to fill some part michael@0: // of the page. michael@0: if (fabs(aSourceWidth - aImgWidth)/aImgWidth < 0.5 || fabs(aSourceHeight - aImgHeight)/aImgHeight < 0.5) michael@0: return GraphicsFilter::FILTER_NEAREST; michael@0: michael@0: // The image is growing a lot and in more than one direction. Resampling michael@0: // is slow and doesn't give us very much when growing a lot. michael@0: return aFilter; michael@0: } michael@0: michael@0: /* Some notes on other heuristics: michael@0: The Skia backend also uses nearest for backgrounds that are stretched by michael@0: a large amount. I'm not sure this is common enough for us to worry about michael@0: now. It also uses nearest for backgrounds/avoids high quality for images michael@0: that are very slightly scaled. I'm also not sure that very slightly michael@0: scaled backgrounds are common enough us to worry about. michael@0: michael@0: We don't currently have much support for doing high quality interpolation. michael@0: The only place this currently happens is on Quartz and we don't have as michael@0: much control over it as would be needed. Webkit avoids using high quality michael@0: resampling during load. It also avoids high quality if the transformation michael@0: is not just a scale and translation michael@0: michael@0: WebKit bug #40045 added code to avoid resampling different parts michael@0: of an image with different methods by using a resampling hint size. michael@0: It currently looks unused in WebKit but it's something to watch out for. michael@0: */ michael@0: michael@0: return aFilter; michael@0: } michael@0: #else michael@0: static GraphicsFilter ReduceResamplingFilter(GraphicsFilter aFilter, michael@0: int aImgWidth, int aImgHeight, michael@0: int aSourceWidth, int aSourceHeight) michael@0: { michael@0: // Just pass the filter through unchanged michael@0: return aFilter; michael@0: } michael@0: #endif michael@0: michael@0: /* static */ void michael@0: gfxUtils::DrawPixelSnapped(gfxContext* aContext, michael@0: gfxDrawable* aDrawable, michael@0: const gfxMatrix& aUserSpaceToImageSpace, michael@0: const gfxRect& aSubimage, michael@0: const gfxRect& aSourceRect, michael@0: const gfxRect& aImageRect, michael@0: const gfxRect& aFill, michael@0: const gfxImageFormat aFormat, michael@0: GraphicsFilter aFilter, michael@0: uint32_t aImageFlags) michael@0: { michael@0: PROFILER_LABEL("gfxUtils", "DrawPixelSnapped"); michael@0: bool doTile = !aImageRect.Contains(aSourceRect) && michael@0: !(aImageFlags & imgIContainer::FLAG_CLAMP); michael@0: michael@0: nsRefPtr currentTarget = aContext->CurrentSurface(); michael@0: gfxMatrix deviceSpaceToImageSpace = michael@0: DeviceToImageTransform(aContext, aUserSpaceToImageSpace); michael@0: michael@0: AutoCairoPixmanBugWorkaround workaround(aContext, deviceSpaceToImageSpace, michael@0: aFill, currentTarget); michael@0: if (!workaround.Succeeded()) michael@0: return; michael@0: michael@0: nsRefPtr drawable = aDrawable; michael@0: michael@0: aFilter = ReduceResamplingFilter(aFilter, aImageRect.Width(), aImageRect.Height(), aSourceRect.Width(), aSourceRect.Height()); michael@0: michael@0: gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace; michael@0: michael@0: // On Mobile, we don't ever want to do this; it has the potential for michael@0: // allocating very large temporary surfaces, especially since we'll michael@0: // do full-page snapshots often (see bug 749426). michael@0: #ifdef MOZ_GFX_OPTIMIZE_MOBILE michael@0: // If the pattern translation is large we can get into trouble with pixman's michael@0: // 16 bit coordinate limits. For now, we only do this on platforms where michael@0: // we know we have the pixman limits. 16384.0 is a somewhat arbitrary michael@0: // large number to make sure we avoid the expensive fmod when we can, but michael@0: // still maintain a safe margin from the actual limit michael@0: if (doTile && (userSpaceToImageSpace.y0 > 16384.0 || userSpaceToImageSpace.x0 > 16384.0)) { michael@0: userSpaceToImageSpace.x0 = fmod(userSpaceToImageSpace.x0, aImageRect.width); michael@0: userSpaceToImageSpace.y0 = fmod(userSpaceToImageSpace.y0, aImageRect.height); michael@0: } michael@0: #else michael@0: // OK now, the hard part left is to account for the subimage sampling michael@0: // restriction. If all the transforms involved are just integer michael@0: // translations, then we assume no resampling will occur so there's michael@0: // nothing to do. michael@0: // XXX if only we had source-clipping in cairo! michael@0: if (aContext->CurrentMatrix().HasNonIntegerTranslation() || michael@0: aUserSpaceToImageSpace.HasNonIntegerTranslation()) { michael@0: if (doTile || !aSubimage.Contains(aImageRect)) { michael@0: nsRefPtr restrictedDrawable = michael@0: CreateSamplingRestrictedDrawable(aDrawable, aContext, michael@0: aUserSpaceToImageSpace, aSourceRect, michael@0: aSubimage, aFormat); michael@0: if (restrictedDrawable) { michael@0: drawable.swap(restrictedDrawable); michael@0: } michael@0: } michael@0: // We no longer need to tile: Either we never needed to, or we already michael@0: // filled a surface with the tiled pattern; this surface can now be michael@0: // drawn without tiling. michael@0: doTile = false; michael@0: } michael@0: #endif michael@0: michael@0: drawable->Draw(aContext, aFill, doTile, aFilter, userSpaceToImageSpace); michael@0: } michael@0: michael@0: /* static */ int michael@0: gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat) michael@0: { michael@0: switch (aFormat) { michael@0: case gfxImageFormat::ARGB32: michael@0: return 32; michael@0: case gfxImageFormat::RGB24: michael@0: return 24; michael@0: case gfxImageFormat::RGB16_565: michael@0: return 16; michael@0: default: michael@0: break; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: static void michael@0: PathFromRegionInternal(gfxContext* aContext, const nsIntRegion& aRegion, michael@0: bool aSnap) michael@0: { michael@0: aContext->NewPath(); michael@0: nsIntRegionRectIterator iter(aRegion); michael@0: const nsIntRect* r; michael@0: while ((r = iter.Next()) != nullptr) { michael@0: aContext->Rectangle(gfxRect(r->x, r->y, r->width, r->height), aSnap); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: ClipToRegionInternal(gfxContext* aContext, const nsIntRegion& aRegion, michael@0: bool aSnap) michael@0: { michael@0: PathFromRegionInternal(aContext, aRegion, aSnap); michael@0: aContext->Clip(); michael@0: } michael@0: michael@0: static TemporaryRef michael@0: PathFromRegionInternal(DrawTarget* aTarget, const nsIntRegion& aRegion, michael@0: bool aSnap) michael@0: { michael@0: Matrix mat = aTarget->GetTransform(); michael@0: const gfxFloat epsilon = 0.000001; michael@0: #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon) michael@0: // We're essentially duplicating the logic in UserToDevicePixelSnapped here. michael@0: bool shouldNotSnap = !aSnap || (WITHIN_E(mat._11,1.0) && michael@0: WITHIN_E(mat._22,1.0) && michael@0: WITHIN_E(mat._12,0.0) && michael@0: WITHIN_E(mat._21,0.0)); michael@0: #undef WITHIN_E michael@0: michael@0: RefPtr pb = aTarget->CreatePathBuilder(); michael@0: nsIntRegionRectIterator iter(aRegion); michael@0: michael@0: const nsIntRect* r; michael@0: if (shouldNotSnap) { michael@0: while ((r = iter.Next()) != nullptr) { michael@0: pb->MoveTo(Point(r->x, r->y)); michael@0: pb->LineTo(Point(r->XMost(), r->y)); michael@0: pb->LineTo(Point(r->XMost(), r->YMost())); michael@0: pb->LineTo(Point(r->x, r->YMost())); michael@0: pb->Close(); michael@0: } michael@0: } else { michael@0: while ((r = iter.Next()) != nullptr) { michael@0: Rect rect(r->x, r->y, r->width, r->height); michael@0: michael@0: rect.Round(); michael@0: pb->MoveTo(rect.TopLeft()); michael@0: pb->LineTo(rect.TopRight()); michael@0: pb->LineTo(rect.BottomRight()); michael@0: pb->LineTo(rect.BottomLeft()); michael@0: pb->Close(); michael@0: } michael@0: } michael@0: RefPtr path = pb->Finish(); michael@0: return path; michael@0: } michael@0: michael@0: static void michael@0: ClipToRegionInternal(DrawTarget* aTarget, const nsIntRegion& aRegion, michael@0: bool aSnap) michael@0: { michael@0: RefPtr path = PathFromRegionInternal(aTarget, aRegion, aSnap); michael@0: aTarget->PushClip(path); michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion) michael@0: { michael@0: ClipToRegionInternal(aContext, aRegion, false); michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion) michael@0: { michael@0: ClipToRegionInternal(aTarget, aRegion, false); michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxUtils::ClipToRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion) michael@0: { michael@0: ClipToRegionInternal(aContext, aRegion, true); michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxUtils::ClipToRegionSnapped(DrawTarget* aTarget, const nsIntRegion& aRegion) michael@0: { michael@0: ClipToRegionInternal(aTarget, aRegion, true); michael@0: } michael@0: michael@0: /*static*/ gfxFloat michael@0: gfxUtils::ClampToScaleFactor(gfxFloat aVal) michael@0: { michael@0: // Arbitary scale factor limitation. We can increase this michael@0: // for better scaling performance at the cost of worse michael@0: // quality. michael@0: static const gfxFloat kScaleResolution = 2; michael@0: michael@0: // Negative scaling is just a flip and irrelevant to michael@0: // our resolution calculation. michael@0: if (aVal < 0.0) { michael@0: aVal = -aVal; michael@0: } michael@0: michael@0: bool inverse = false; michael@0: if (aVal < 1.0) { michael@0: inverse = true; michael@0: aVal = 1 / aVal; michael@0: } michael@0: michael@0: gfxFloat power = log(aVal)/log(kScaleResolution); michael@0: michael@0: // If power is within 1e-6 of an integer, round to nearest to michael@0: // prevent floating point errors, otherwise round up to the michael@0: // next integer value. michael@0: if (fabs(power - NS_round(power)) < 1e-6) { michael@0: power = NS_round(power); michael@0: } else if (inverse) { michael@0: power = floor(power); michael@0: } else { michael@0: power = ceil(power); michael@0: } michael@0: michael@0: gfxFloat scale = pow(kScaleResolution, power); michael@0: michael@0: if (inverse) { michael@0: scale = 1 / scale; michael@0: } michael@0: michael@0: return scale; michael@0: } michael@0: michael@0: michael@0: /*static*/ void michael@0: gfxUtils::PathFromRegion(gfxContext* aContext, const nsIntRegion& aRegion) michael@0: { michael@0: PathFromRegionInternal(aContext, aRegion, false); michael@0: } michael@0: michael@0: /*static*/ void michael@0: gfxUtils::PathFromRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion) michael@0: { michael@0: PathFromRegionInternal(aContext, aRegion, true); michael@0: } michael@0: michael@0: gfxMatrix michael@0: gfxUtils::TransformRectToRect(const gfxRect& aFrom, const gfxPoint& aToTopLeft, michael@0: const gfxPoint& aToTopRight, const gfxPoint& aToBottomRight) michael@0: { michael@0: gfxMatrix m; michael@0: if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { michael@0: // Not a rotation, so xy and yx are zero michael@0: m.xy = m.yx = 0.0; michael@0: m.xx = (aToBottomRight.x - aToTopLeft.x)/aFrom.width; michael@0: m.yy = (aToBottomRight.y - aToTopLeft.y)/aFrom.height; michael@0: m.x0 = aToTopLeft.x - m.xx*aFrom.x; michael@0: m.y0 = aToTopLeft.y - m.yy*aFrom.y; michael@0: } else { michael@0: NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, michael@0: "Destination rectangle not axis-aligned"); michael@0: m.xx = m.yy = 0.0; michael@0: m.xy = (aToBottomRight.x - aToTopLeft.x)/aFrom.height; michael@0: m.yx = (aToBottomRight.y - aToTopLeft.y)/aFrom.width; michael@0: m.x0 = aToTopLeft.x - m.xy*aFrom.y; michael@0: m.y0 = aToTopLeft.y - m.yx*aFrom.x; michael@0: } michael@0: return m; michael@0: } michael@0: michael@0: Matrix michael@0: gfxUtils::TransformRectToRect(const gfxRect& aFrom, const IntPoint& aToTopLeft, michael@0: const IntPoint& aToTopRight, const IntPoint& aToBottomRight) michael@0: { michael@0: Matrix m; michael@0: if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) { michael@0: // Not a rotation, so xy and yx are zero michael@0: m._12 = m._21 = 0.0; michael@0: m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.width; michael@0: m._22 = (aToBottomRight.y - aToTopLeft.y)/aFrom.height; michael@0: m._31 = aToTopLeft.x - m._11*aFrom.x; michael@0: m._32 = aToTopLeft.y - m._22*aFrom.y; michael@0: } else { michael@0: NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x, michael@0: "Destination rectangle not axis-aligned"); michael@0: m._11 = m._22 = 0.0; michael@0: m._21 = (aToBottomRight.x - aToTopLeft.x)/aFrom.height; michael@0: m._12 = (aToBottomRight.y - aToTopLeft.y)/aFrom.width; michael@0: m._31 = aToTopLeft.x - m._21*aFrom.y; michael@0: m._32 = aToTopLeft.y - m._12*aFrom.x; michael@0: } michael@0: return m; michael@0: } michael@0: michael@0: bool michael@0: gfxUtils::GfxRectToIntRect(const gfxRect& aIn, nsIntRect* aOut) michael@0: { michael@0: *aOut = nsIntRect(int32_t(aIn.X()), int32_t(aIn.Y()), michael@0: int32_t(aIn.Width()), int32_t(aIn.Height())); michael@0: return gfxRect(aOut->x, aOut->y, aOut->width, aOut->height).IsEqualEdges(aIn); michael@0: } michael@0: michael@0: void michael@0: gfxUtils::GetYCbCrToRGBDestFormatAndSize(const PlanarYCbCrData& aData, michael@0: gfxImageFormat& aSuggestedFormat, michael@0: gfxIntSize& aSuggestedSize) michael@0: { michael@0: YUVType yuvtype = michael@0: TypeFromSize(aData.mYSize.width, michael@0: aData.mYSize.height, michael@0: aData.mCbCrSize.width, michael@0: aData.mCbCrSize.height); michael@0: michael@0: // 'prescale' is true if the scaling is to be done as part of the michael@0: // YCbCr to RGB conversion rather than on the RGB data when rendered. michael@0: bool prescale = aSuggestedSize.width > 0 && aSuggestedSize.height > 0 && michael@0: ToIntSize(aSuggestedSize) != aData.mPicSize; michael@0: michael@0: if (aSuggestedFormat == gfxImageFormat::RGB16_565) { michael@0: #if defined(HAVE_YCBCR_TO_RGB565) michael@0: if (prescale && michael@0: !IsScaleYCbCrToRGB565Fast(aData.mPicX, michael@0: aData.mPicY, michael@0: aData.mPicSize.width, michael@0: aData.mPicSize.height, michael@0: aSuggestedSize.width, michael@0: aSuggestedSize.height, michael@0: yuvtype, michael@0: FILTER_BILINEAR) && michael@0: IsConvertYCbCrToRGB565Fast(aData.mPicX, michael@0: aData.mPicY, michael@0: aData.mPicSize.width, michael@0: aData.mPicSize.height, michael@0: yuvtype)) { michael@0: prescale = false; michael@0: } michael@0: #else michael@0: // yuv2rgb16 function not available michael@0: aSuggestedFormat = gfxImageFormat::RGB24; michael@0: #endif michael@0: } michael@0: else if (aSuggestedFormat != gfxImageFormat::RGB24) { michael@0: // No other formats are currently supported. michael@0: aSuggestedFormat = gfxImageFormat::RGB24; michael@0: } michael@0: if (aSuggestedFormat == gfxImageFormat::RGB24) { michael@0: /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data. michael@0: See bugs 639415 and 640073. */ michael@0: if (aData.mPicX != 0 || aData.mPicY != 0 || yuvtype == YV24) michael@0: prescale = false; michael@0: } michael@0: if (!prescale) { michael@0: ToIntSize(aSuggestedSize) = aData.mPicSize; michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxUtils::ConvertYCbCrToRGB(const PlanarYCbCrData& aData, michael@0: const gfxImageFormat& aDestFormat, michael@0: const gfxIntSize& aDestSize, michael@0: unsigned char* aDestBuffer, michael@0: int32_t aStride) michael@0: { michael@0: // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the michael@0: // luma plane is odd sized. michael@0: MOZ_ASSERT((aData.mCbCrSize.width == aData.mYSize.width || michael@0: aData.mCbCrSize.width == (aData.mYSize.width + 1) >> 1) && michael@0: (aData.mCbCrSize.height == aData.mYSize.height || michael@0: aData.mCbCrSize.height == (aData.mYSize.height + 1) >> 1)); michael@0: YUVType yuvtype = michael@0: TypeFromSize(aData.mYSize.width, michael@0: aData.mYSize.height, michael@0: aData.mCbCrSize.width, michael@0: aData.mCbCrSize.height); michael@0: michael@0: // Convert from YCbCr to RGB now, scaling the image if needed. michael@0: if (ToIntSize(aDestSize) != aData.mPicSize) { michael@0: #if defined(HAVE_YCBCR_TO_RGB565) michael@0: if (aDestFormat == gfxImageFormat::RGB16_565) { michael@0: ScaleYCbCrToRGB565(aData.mYChannel, michael@0: aData.mCbChannel, michael@0: aData.mCrChannel, michael@0: aDestBuffer, michael@0: aData.mPicX, michael@0: aData.mPicY, michael@0: aData.mPicSize.width, michael@0: aData.mPicSize.height, michael@0: aDestSize.width, michael@0: aDestSize.height, michael@0: aData.mYStride, michael@0: aData.mCbCrStride, michael@0: aStride, michael@0: yuvtype, michael@0: FILTER_BILINEAR); michael@0: } else michael@0: #endif michael@0: ScaleYCbCrToRGB32(aData.mYChannel, michael@0: aData.mCbChannel, michael@0: aData.mCrChannel, michael@0: aDestBuffer, michael@0: aData.mPicSize.width, michael@0: aData.mPicSize.height, michael@0: aDestSize.width, michael@0: aDestSize.height, michael@0: aData.mYStride, michael@0: aData.mCbCrStride, michael@0: aStride, michael@0: yuvtype, michael@0: ROTATE_0, michael@0: FILTER_BILINEAR); michael@0: } else { // no prescale michael@0: #if defined(HAVE_YCBCR_TO_RGB565) michael@0: if (aDestFormat == gfxImageFormat::RGB16_565) { michael@0: ConvertYCbCrToRGB565(aData.mYChannel, michael@0: aData.mCbChannel, michael@0: aData.mCrChannel, michael@0: aDestBuffer, michael@0: aData.mPicX, michael@0: aData.mPicY, michael@0: aData.mPicSize.width, michael@0: aData.mPicSize.height, michael@0: aData.mYStride, michael@0: aData.mCbCrStride, michael@0: aStride, michael@0: yuvtype); michael@0: } else // aDestFormat != gfxImageFormat::RGB16_565 michael@0: #endif michael@0: ConvertYCbCrToRGB32(aData.mYChannel, michael@0: aData.mCbChannel, michael@0: aData.mCrChannel, michael@0: aDestBuffer, michael@0: aData.mPicX, michael@0: aData.mPicY, michael@0: aData.mPicSize.width, michael@0: aData.mPicSize.height, michael@0: aData.mYStride, michael@0: aData.mCbCrStride, michael@0: aStride, michael@0: yuvtype); michael@0: } michael@0: } michael@0: michael@0: /* static */ TemporaryRef michael@0: gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface, michael@0: SurfaceFormat aFormat) michael@0: { michael@0: MOZ_ASSERT(aFormat != aSurface->GetFormat(), michael@0: "Unnecessary - and very expersive - surface format conversion"); michael@0: michael@0: Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height); michael@0: michael@0: if (aSurface->GetType() != SurfaceType::DATA) { michael@0: // If the surface is NOT of type DATA then its data is not mapped into main michael@0: // memory. Format conversion is probably faster on the GPU, and by doing it michael@0: // there we can avoid any expensive uploads/readbacks except for (possibly) michael@0: // a single readback due to the unavoidable GetDataSurface() call. Using michael@0: // CreateOffscreenContentDrawTarget ensures the conversion happens on the michael@0: // GPU. michael@0: RefPtr dt = gfxPlatform::GetPlatform()-> michael@0: CreateOffscreenContentDrawTarget(aSurface->GetSize(), aFormat); michael@0: // Using DrawSurface() here rather than CopySurface() because CopySurface michael@0: // is optimized for memcpy and therefore isn't good for format conversion. michael@0: // Using OP_OVER since in our case it's equivalent to OP_SOURCE and michael@0: // generally more optimized. michael@0: dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), michael@0: DrawOptions(1.0f, CompositionOp::OP_OVER)); michael@0: RefPtr surface = dt->Snapshot(); michael@0: return surface->GetDataSurface(); michael@0: } michael@0: michael@0: // If the surface IS of type DATA then it may or may not be in main memory michael@0: // depending on whether or not it has been mapped yet. We have no way of michael@0: // knowing, so we can't be sure if it's best to create a data wrapping michael@0: // DrawTarget for the conversion or an offscreen content DrawTarget. We could michael@0: // guess it's not mapped and create an offscreen content DrawTarget, but if michael@0: // it is then we'll end up uploading the surface data, and most likely the michael@0: // caller is going to be accessing the resulting surface data, resulting in a michael@0: // readback (both very expensive operations). Alternatively we could guess michael@0: // the data is mapped and create a data wrapping DrawTarget and, if the michael@0: // surface is not in main memory, then we will incure a readback. The latter michael@0: // of these two "wrong choices" is the least costly (a readback, vs an michael@0: // upload and a readback), and more than likely the DATA surface that we've michael@0: // been passed actually IS in main memory anyway. For these reasons it's most michael@0: // likely best to create a data wrapping DrawTarget here to do the format michael@0: // conversion. michael@0: RefPtr dataSurface = michael@0: Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat); michael@0: DataSourceSurface::MappedSurface map; michael@0: if (!dataSurface || michael@0: !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { michael@0: return nullptr; 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: aFormat); michael@0: if (!dt) { michael@0: dataSurface->Unmap(); michael@0: return nullptr; michael@0: } michael@0: // Using DrawSurface() here rather than CopySurface() because CopySurface michael@0: // is optimized for memcpy and therefore isn't good for format conversion. michael@0: // Using OP_OVER since in our case it's equivalent to OP_SOURCE and michael@0: // generally more optimized. michael@0: dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(), michael@0: DrawOptions(1.0f, CompositionOp::OP_OVER)); michael@0: dataSurface->Unmap(); michael@0: return dataSurface.forget(); michael@0: } michael@0: michael@0: #ifdef MOZ_DUMP_PAINTING michael@0: /* static */ void michael@0: gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile) michael@0: { michael@0: aDT->Flush(); michael@0: nsRefPtr surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT); michael@0: if (surf) { michael@0: surf->WriteAsPNG(aFile); michael@0: } else { michael@0: NS_WARNING("Failed to get Thebes surface!"); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: gfxUtils::DumpAsDataURL(DrawTarget* aDT) michael@0: { michael@0: aDT->Flush(); michael@0: nsRefPtr surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT); michael@0: if (surf) { michael@0: surf->DumpAsDataURL(); michael@0: } else { michael@0: NS_WARNING("Failed to get Thebes surface!"); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: gfxUtils::CopyAsDataURL(DrawTarget* aDT) michael@0: { michael@0: aDT->Flush(); michael@0: nsRefPtr surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT); michael@0: if (surf) { michael@0: surf->CopyAsDataURL(); michael@0: } else { michael@0: NS_WARNING("Failed to get Thebes surface!"); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: gfxUtils::WriteAsPNG(RefPtr aSourceSurface, const char* aFile) michael@0: { michael@0: RefPtr dataSurface = aSourceSurface->GetDataSurface(); michael@0: RefPtr dt michael@0: = gfxPlatform::GetPlatform() michael@0: ->CreateDrawTargetForData(dataSurface->GetData(), michael@0: dataSurface->GetSize(), michael@0: dataSurface->Stride(), michael@0: aSourceSurface->GetFormat()); michael@0: gfxUtils::WriteAsPNG(dt.get(), aFile); michael@0: } michael@0: michael@0: /* static */ void michael@0: gfxUtils::DumpAsDataURL(RefPtr aSourceSurface) michael@0: { michael@0: RefPtr dataSurface = aSourceSurface->GetDataSurface(); michael@0: RefPtr dt michael@0: = gfxPlatform::GetPlatform() michael@0: ->CreateDrawTargetForData(dataSurface->GetData(), michael@0: dataSurface->GetSize(), michael@0: dataSurface->Stride(), michael@0: aSourceSurface->GetFormat()); michael@0: gfxUtils::DumpAsDataURL(dt.get()); michael@0: } michael@0: michael@0: /* static */ void michael@0: gfxUtils::CopyAsDataURL(RefPtr aSourceSurface) michael@0: { michael@0: RefPtr dataSurface = aSourceSurface->GetDataSurface(); michael@0: RefPtr dt michael@0: = gfxPlatform::GetPlatform() michael@0: ->CreateDrawTargetForData(dataSurface->GetData(), michael@0: dataSurface->GetSize(), michael@0: dataSurface->Stride(), michael@0: aSourceSurface->GetFormat()); michael@0: michael@0: gfxUtils::CopyAsDataURL(dt.get()); michael@0: } michael@0: michael@0: bool gfxUtils::sDumpPaintList = getenv("MOZ_DUMP_PAINT_LIST") != 0; michael@0: bool gfxUtils::sDumpPainting = getenv("MOZ_DUMP_PAINT") != 0; michael@0: bool gfxUtils::sDumpPaintingToFile = getenv("MOZ_DUMP_PAINT_TO_FILE") != 0; michael@0: FILE *gfxUtils::sDumpPaintFile = nullptr; michael@0: #endif