gfx/thebes/gfxUtils.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "gfxUtils.h"
     7 #include "gfxContext.h"
     8 #include "gfxPlatform.h"
     9 #include "gfxDrawable.h"
    10 #include "mozilla/gfx/2D.h"
    11 #include "mozilla/RefPtr.h"
    12 #include "nsRegion.h"
    13 #include "yuv_convert.h"
    14 #include "ycbcr_to_rgb565.h"
    15 #include "GeckoProfiler.h"
    16 #include "ImageContainer.h"
    17 #include "gfx2DGlue.h"
    19 #ifdef XP_WIN
    20 #include "gfxWindowsPlatform.h"
    21 #endif
    23 using namespace mozilla;
    24 using namespace mozilla::layers;
    25 using namespace mozilla::gfx;
    27 #include "DeprecatedPremultiplyTables.h"
    29 static const uint8_t PremultiplyValue(uint8_t a, uint8_t v) {
    30     return gfxUtils::sPremultiplyTable[a*256+v];
    31 }
    33 static const uint8_t UnpremultiplyValue(uint8_t a, uint8_t v) {
    34     return gfxUtils::sUnpremultiplyTable[a*256+v];
    35 }
    37 void
    38 gfxUtils::PremultiplyImageSurface(gfxImageSurface *aSourceSurface,
    39                                   gfxImageSurface *aDestSurface)
    40 {
    41     if (!aDestSurface)
    42         aDestSurface = aSourceSurface;
    44     MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() &&
    45                aSourceSurface->Width()  == aDestSurface->Width() &&
    46                aSourceSurface->Height() == aDestSurface->Height() &&
    47                aSourceSurface->Stride() == aDestSurface->Stride(),
    48                "Source and destination surfaces don't have identical characteristics");
    50     MOZ_ASSERT(aSourceSurface->Stride() == aSourceSurface->Width() * 4,
    51                "Source surface stride isn't tightly packed");
    53     // Only premultiply ARGB32
    54     if (aSourceSurface->Format() != gfxImageFormat::ARGB32) {
    55         if (aDestSurface != aSourceSurface) {
    56             memcpy(aDestSurface->Data(), aSourceSurface->Data(),
    57                    aSourceSurface->Stride() * aSourceSurface->Height());
    58         }
    59         return;
    60     }
    62     uint8_t *src = aSourceSurface->Data();
    63     uint8_t *dst = aDestSurface->Data();
    65     uint32_t dim = aSourceSurface->Width() * aSourceSurface->Height();
    66     for (uint32_t i = 0; i < dim; ++i) {
    67 #ifdef IS_LITTLE_ENDIAN
    68         uint8_t b = *src++;
    69         uint8_t g = *src++;
    70         uint8_t r = *src++;
    71         uint8_t a = *src++;
    73         *dst++ = PremultiplyValue(a, b);
    74         *dst++ = PremultiplyValue(a, g);
    75         *dst++ = PremultiplyValue(a, r);
    76         *dst++ = a;
    77 #else
    78         uint8_t a = *src++;
    79         uint8_t r = *src++;
    80         uint8_t g = *src++;
    81         uint8_t b = *src++;
    83         *dst++ = a;
    84         *dst++ = PremultiplyValue(a, r);
    85         *dst++ = PremultiplyValue(a, g);
    86         *dst++ = PremultiplyValue(a, b);
    87 #endif
    88     }
    89 }
    91 void
    92 gfxUtils::UnpremultiplyImageSurface(gfxImageSurface *aSourceSurface,
    93                                     gfxImageSurface *aDestSurface)
    94 {
    95     if (!aDestSurface)
    96         aDestSurface = aSourceSurface;
    98     MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() &&
    99                aSourceSurface->Width()  == aDestSurface->Width() &&
   100                aSourceSurface->Height() == aDestSurface->Height(),
   101                "Source and destination surfaces don't have identical characteristics");
   103     // Only premultiply ARGB32
   104     if (aSourceSurface->Format() != gfxImageFormat::ARGB32) {
   105         if (aDestSurface != aSourceSurface) {
   106             aDestSurface->CopyFrom(aSourceSurface);
   107         }
   108         return;
   109     }
   111     uint8_t *src = aSourceSurface->Data();
   112     uint8_t *dst = aDestSurface->Data();
   114     for (int32_t i = 0; i < aSourceSurface->Height(); ++i) {
   115         uint8_t *srcRow = src + (i * aSourceSurface->Stride());
   116         uint8_t *dstRow = dst + (i * aDestSurface->Stride());
   118         for (int32_t j = 0; j < aSourceSurface->Width(); ++j) {
   119 #ifdef IS_LITTLE_ENDIAN
   120           uint8_t b = *srcRow++;
   121           uint8_t g = *srcRow++;
   122           uint8_t r = *srcRow++;
   123           uint8_t a = *srcRow++;
   125           *dstRow++ = UnpremultiplyValue(a, b);
   126           *dstRow++ = UnpremultiplyValue(a, g);
   127           *dstRow++ = UnpremultiplyValue(a, r);
   128           *dstRow++ = a;
   129 #else
   130           uint8_t a = *srcRow++;
   131           uint8_t r = *srcRow++;
   132           uint8_t g = *srcRow++;
   133           uint8_t b = *srcRow++;
   135           *dstRow++ = a;
   136           *dstRow++ = UnpremultiplyValue(a, r);
   137           *dstRow++ = UnpremultiplyValue(a, g);
   138           *dstRow++ = UnpremultiplyValue(a, b);
   139 #endif
   140         }
   141     }
   142 }
   144 TemporaryRef<DataSourceSurface>
   145 gfxUtils::UnpremultiplyDataSurface(DataSourceSurface* aSurface)
   146 {
   147     // Only premultiply ARGB32
   148     if (aSurface->GetFormat() != SurfaceFormat::B8G8R8A8) {
   149         return aSurface;
   150     }
   152     DataSourceSurface::MappedSurface map;
   153     if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) {
   154         return nullptr;
   155     }
   157     RefPtr<DataSourceSurface> dest = Factory::CreateDataSourceSurfaceWithStride(aSurface->GetSize(),
   158                                                                                 aSurface->GetFormat(),
   159                                                                                 map.mStride);
   161     DataSourceSurface::MappedSurface destMap;
   162     if (!dest->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
   163         aSurface->Unmap();
   164         return nullptr;
   165     }
   167     uint8_t *src = map.mData;
   168     uint8_t *dst = destMap.mData;
   170     for (int32_t i = 0; i < aSurface->GetSize().height; ++i) {
   171         uint8_t *srcRow = src + (i * map.mStride);
   172         uint8_t *dstRow = dst + (i * destMap.mStride);
   174         for (int32_t j = 0; j < aSurface->GetSize().width; ++j) {
   175 #ifdef IS_LITTLE_ENDIAN
   176           uint8_t b = *srcRow++;
   177           uint8_t g = *srcRow++;
   178           uint8_t r = *srcRow++;
   179           uint8_t a = *srcRow++;
   181           *dstRow++ = UnpremultiplyValue(a, b);
   182           *dstRow++ = UnpremultiplyValue(a, g);
   183           *dstRow++ = UnpremultiplyValue(a, r);
   184           *dstRow++ = a;
   185 #else
   186           uint8_t a = *srcRow++;
   187           uint8_t r = *srcRow++;
   188           uint8_t g = *srcRow++;
   189           uint8_t b = *srcRow++;
   191           *dstRow++ = a;
   192           *dstRow++ = UnpremultiplyValue(a, r);
   193           *dstRow++ = UnpremultiplyValue(a, g);
   194           *dstRow++ = UnpremultiplyValue(a, b);
   195 #endif
   196         }
   197     }
   199     aSurface->Unmap();
   200     dest->Unmap();
   201     return dest;
   202 }
   204 void
   205 gfxUtils::ConvertBGRAtoRGBA(gfxImageSurface *aSourceSurface,
   206                             gfxImageSurface *aDestSurface) {
   207     if (!aDestSurface)
   208         aDestSurface = aSourceSurface;
   210     MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() &&
   211                aSourceSurface->Width()  == aDestSurface->Width() &&
   212                aSourceSurface->Height() == aDestSurface->Height() &&
   213                aSourceSurface->Stride() == aDestSurface->Stride(),
   214                "Source and destination surfaces don't have identical characteristics");
   216     MOZ_ASSERT(aSourceSurface->Stride() == aSourceSurface->Width() * 4,
   217                "Source surface stride isn't tightly packed");
   219     MOZ_ASSERT(aSourceSurface->Format() == gfxImageFormat::ARGB32 || aSourceSurface->Format() == gfxImageFormat::RGB24,
   220                "Surfaces must be ARGB32 or RGB24");
   222     uint8_t *src = aSourceSurface->Data();
   223     uint8_t *dst = aDestSurface->Data();
   225     uint32_t dim = aSourceSurface->Width() * aSourceSurface->Height();
   226     uint8_t *srcEnd = src + 4*dim;
   228     if (src == dst) {
   229         uint8_t buffer[4];
   230         for (; src != srcEnd; src += 4) {
   231             buffer[0] = src[2];
   232             buffer[1] = src[1];
   233             buffer[2] = src[0];
   235             src[0] = buffer[0];
   236             src[1] = buffer[1];
   237             src[2] = buffer[2];
   238         }
   239     } else {
   240         for (; src != srcEnd; src += 4, dst += 4) {
   241             dst[0] = src[2];
   242             dst[1] = src[1];
   243             dst[2] = src[0];
   244             dst[3] = src[3];
   245         }
   246     }
   247 }
   249 void
   250 gfxUtils::ConvertBGRAtoRGBA(uint8_t* aData, uint32_t aLength)
   251 {
   252     uint8_t *src = aData;
   253     uint8_t *srcEnd = src + aLength;
   255     uint8_t buffer[4];
   256     for (; src != srcEnd; src += 4) {
   257         buffer[0] = src[2];
   258         buffer[1] = src[1];
   259         buffer[2] = src[0];
   261         src[0] = buffer[0];
   262         src[1] = buffer[1];
   263         src[2] = buffer[2];
   264     }
   265 }
   267 static bool
   268 IsSafeImageTransformComponent(gfxFloat aValue)
   269 {
   270   return aValue >= -32768 && aValue <= 32767;
   271 }
   273 #ifndef MOZ_GFX_OPTIMIZE_MOBILE
   274 /**
   275  * This returns the fastest operator to use for solid surfaces which have no
   276  * alpha channel or their alpha channel is uniformly opaque.
   277  * This differs per render mode.
   278  */
   279 static gfxContext::GraphicsOperator
   280 OptimalFillOperator()
   281 {
   282 #ifdef XP_WIN
   283     if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() ==
   284         gfxWindowsPlatform::RENDER_DIRECT2D) {
   285         // D2D -really- hates operator source.
   286         return gfxContext::OPERATOR_OVER;
   287     } else {
   288 #endif
   289         return gfxContext::OPERATOR_SOURCE;
   290 #ifdef XP_WIN
   291     }
   292 #endif
   293 }
   295 // EXTEND_PAD won't help us here; we have to create a temporary surface to hold
   296 // the subimage of pixels we're allowed to sample.
   297 static already_AddRefed<gfxDrawable>
   298 CreateSamplingRestrictedDrawable(gfxDrawable* aDrawable,
   299                                  gfxContext* aContext,
   300                                  const gfxMatrix& aUserSpaceToImageSpace,
   301                                  const gfxRect& aSourceRect,
   302                                  const gfxRect& aSubimage,
   303                                  const gfxImageFormat aFormat)
   304 {
   305     PROFILER_LABEL("gfxUtils", "CreateSamplingRestricedDrawable");
   306     gfxRect userSpaceClipExtents = aContext->GetClipExtents();
   307     // This isn't optimal --- if aContext has a rotation then GetClipExtents
   308     // will have to do a bounding-box computation, and TransformBounds might
   309     // too, so we could get a better result if we computed image space clip
   310     // extents in one go --- but it doesn't really matter and this is easier
   311     // to understand.
   312     gfxRect imageSpaceClipExtents =
   313         aUserSpaceToImageSpace.TransformBounds(userSpaceClipExtents);
   314     // Inflate by one pixel because bilinear filtering will sample at most
   315     // one pixel beyond the computed image pixel coordinate.
   316     imageSpaceClipExtents.Inflate(1.0);
   318     gfxRect needed = imageSpaceClipExtents.Intersect(aSourceRect);
   319     needed = needed.Intersect(aSubimage);
   320     needed.RoundOut();
   322     // if 'needed' is empty, nothing will be drawn since aFill
   323     // must be entirely outside the clip region, so it doesn't
   324     // matter what we do here, but we should avoid trying to
   325     // create a zero-size surface.
   326     if (needed.IsEmpty())
   327         return nullptr;
   329     nsRefPtr<gfxDrawable> drawable;
   330     gfxIntSize size(int32_t(needed.Width()), int32_t(needed.Height()));
   332     nsRefPtr<gfxImageSurface> image = aDrawable->GetAsImageSurface();
   333     if (image && gfxRect(0, 0, image->GetSize().width, image->GetSize().height).Contains(needed)) {
   334       nsRefPtr<gfxASurface> temp = image->GetSubimage(needed);
   335       drawable = new gfxSurfaceDrawable(temp, size, gfxMatrix().Translate(-needed.TopLeft()));
   336     } else {
   337       mozilla::RefPtr<mozilla::gfx::DrawTarget> target =
   338         gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(ToIntSize(size),
   339                                                                      ImageFormatToSurfaceFormat(aFormat));
   340       if (!target) {
   341         return nullptr;
   342       }
   344       nsRefPtr<gfxContext> tmpCtx = new gfxContext(target);
   345       tmpCtx->SetOperator(OptimalFillOperator());
   346       aDrawable->Draw(tmpCtx, needed - needed.TopLeft(), true,
   347                       GraphicsFilter::FILTER_FAST, gfxMatrix().Translate(needed.TopLeft()));
   348       drawable = new gfxSurfaceDrawable(target, size, gfxMatrix().Translate(-needed.TopLeft()));
   349     }
   351     return drawable.forget();
   352 }
   353 #endif // !MOZ_GFX_OPTIMIZE_MOBILE
   355 // working around cairo/pixman bug (bug 364968)
   356 // Our device-space-to-image-space transform may not be acceptable to pixman.
   357 struct MOZ_STACK_CLASS AutoCairoPixmanBugWorkaround
   358 {
   359     AutoCairoPixmanBugWorkaround(gfxContext*      aContext,
   360                                  const gfxMatrix& aDeviceSpaceToImageSpace,
   361                                  const gfxRect&   aFill,
   362                                  const gfxASurface* aSurface)
   363      : mContext(aContext), mSucceeded(true), mPushedGroup(false)
   364     {
   365         // Quartz's limits for matrix are much larger than pixman
   366         if (!aSurface || aSurface->GetType() == gfxSurfaceType::Quartz)
   367             return;
   369         if (!IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.xx) ||
   370             !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.xy) ||
   371             !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.yx) ||
   372             !IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.yy)) {
   373             NS_WARNING("Scaling up too much, bailing out");
   374             mSucceeded = false;
   375             return;
   376         }
   378         if (IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.x0) &&
   379             IsSafeImageTransformComponent(aDeviceSpaceToImageSpace.y0))
   380             return;
   382         // We'll push a group, which will hopefully reduce our transform's
   383         // translation so it's in bounds.
   384         gfxMatrix currentMatrix = mContext->CurrentMatrix();
   385         mContext->Save();
   387         // Clip the rounded-out-to-device-pixels bounds of the
   388         // transformed fill area. This is the area for the group we
   389         // want to push.
   390         mContext->IdentityMatrix();
   391         gfxRect bounds = currentMatrix.TransformBounds(aFill);
   392         bounds.RoundOut();
   393         mContext->Clip(bounds);
   394         mContext->SetMatrix(currentMatrix);
   395         mContext->PushGroup(gfxContentType::COLOR_ALPHA);
   396         mContext->SetOperator(gfxContext::OPERATOR_OVER);
   398         mPushedGroup = true;
   399     }
   401     ~AutoCairoPixmanBugWorkaround()
   402     {
   403         if (mPushedGroup) {
   404             mContext->PopGroupToSource();
   405             mContext->Paint();
   406             mContext->Restore();
   407         }
   408     }
   410     bool PushedGroup() { return mPushedGroup; }
   411     bool Succeeded() { return mSucceeded; }
   413 private:
   414     gfxContext* mContext;
   415     bool mSucceeded;
   416     bool mPushedGroup;
   417 };
   419 static gfxMatrix
   420 DeviceToImageTransform(gfxContext* aContext,
   421                        const gfxMatrix& aUserSpaceToImageSpace)
   422 {
   423     gfxFloat deviceX, deviceY;
   424     nsRefPtr<gfxASurface> currentTarget =
   425         aContext->CurrentSurface(&deviceX, &deviceY);
   426     gfxMatrix currentMatrix = aContext->CurrentMatrix();
   427     gfxMatrix deviceToUser = gfxMatrix(currentMatrix).Invert();
   428     deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY));
   429     return gfxMatrix(deviceToUser).Multiply(aUserSpaceToImageSpace);
   430 }
   432 /* These heuristics are based on Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode() */
   433 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
   434 static GraphicsFilter ReduceResamplingFilter(GraphicsFilter aFilter,
   435                                              int aImgWidth, int aImgHeight,
   436                                              float aSourceWidth, float aSourceHeight)
   437 {
   438     // Images smaller than this in either direction are considered "small" and
   439     // are not resampled ever (see below).
   440     const int kSmallImageSizeThreshold = 8;
   442     // The amount an image can be stretched in a single direction before we
   443     // say that it is being stretched so much that it must be a line or
   444     // background that doesn't need resampling.
   445     const float kLargeStretch = 3.0f;
   447     if (aImgWidth <= kSmallImageSizeThreshold
   448         || aImgHeight <= kSmallImageSizeThreshold) {
   449         // Never resample small images. These are often used for borders and
   450         // rules (think 1x1 images used to make lines).
   451         return GraphicsFilter::FILTER_NEAREST;
   452     }
   454     if (aImgHeight * kLargeStretch <= aSourceHeight || aImgWidth * kLargeStretch <= aSourceWidth) {
   455         // Large image tiling detected.
   457         // Don't resample if it is being tiled a lot in only one direction.
   458         // This is trying to catch cases where somebody has created a border
   459         // (which might be large) and then is stretching it to fill some part
   460         // of the page.
   461         if (fabs(aSourceWidth - aImgWidth)/aImgWidth < 0.5 || fabs(aSourceHeight - aImgHeight)/aImgHeight < 0.5)
   462             return GraphicsFilter::FILTER_NEAREST;
   464         // The image is growing a lot and in more than one direction. Resampling
   465         // is slow and doesn't give us very much when growing a lot.
   466         return aFilter;
   467     }
   469     /* Some notes on other heuristics:
   470        The Skia backend also uses nearest for backgrounds that are stretched by
   471        a large amount. I'm not sure this is common enough for us to worry about
   472        now. It also uses nearest for backgrounds/avoids high quality for images
   473        that are very slightly scaled.  I'm also not sure that very slightly
   474        scaled backgrounds are common enough us to worry about.
   476        We don't currently have much support for doing high quality interpolation.
   477        The only place this currently happens is on Quartz and we don't have as
   478        much control over it as would be needed. Webkit avoids using high quality
   479        resampling during load. It also avoids high quality if the transformation
   480        is not just a scale and translation
   482        WebKit bug #40045 added code to avoid resampling different parts
   483        of an image with different methods by using a resampling hint size.
   484        It currently looks unused in WebKit but it's something to watch out for.
   485     */
   487     return aFilter;
   488 }
   489 #else
   490 static GraphicsFilter ReduceResamplingFilter(GraphicsFilter aFilter,
   491                                              int aImgWidth, int aImgHeight,
   492                                              int aSourceWidth, int aSourceHeight)
   493 {
   494     // Just pass the filter through unchanged
   495     return aFilter;
   496 }
   497 #endif
   499 /* static */ void
   500 gfxUtils::DrawPixelSnapped(gfxContext*      aContext,
   501                            gfxDrawable*     aDrawable,
   502                            const gfxMatrix& aUserSpaceToImageSpace,
   503                            const gfxRect&   aSubimage,
   504                            const gfxRect&   aSourceRect,
   505                            const gfxRect&   aImageRect,
   506                            const gfxRect&   aFill,
   507                            const gfxImageFormat aFormat,
   508                            GraphicsFilter aFilter,
   509                            uint32_t         aImageFlags)
   510 {
   511     PROFILER_LABEL("gfxUtils", "DrawPixelSnapped");
   512     bool doTile = !aImageRect.Contains(aSourceRect) &&
   513                   !(aImageFlags & imgIContainer::FLAG_CLAMP);
   515     nsRefPtr<gfxASurface> currentTarget = aContext->CurrentSurface();
   516     gfxMatrix deviceSpaceToImageSpace =
   517         DeviceToImageTransform(aContext, aUserSpaceToImageSpace);
   519     AutoCairoPixmanBugWorkaround workaround(aContext, deviceSpaceToImageSpace,
   520                                             aFill, currentTarget);
   521     if (!workaround.Succeeded())
   522         return;
   524     nsRefPtr<gfxDrawable> drawable = aDrawable;
   526     aFilter = ReduceResamplingFilter(aFilter, aImageRect.Width(), aImageRect.Height(), aSourceRect.Width(), aSourceRect.Height());
   528     gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace;
   530     // On Mobile, we don't ever want to do this; it has the potential for
   531     // allocating very large temporary surfaces, especially since we'll
   532     // do full-page snapshots often (see bug 749426).
   533 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
   534     // If the pattern translation is large we can get into trouble with pixman's
   535     // 16 bit coordinate limits. For now, we only do this on platforms where
   536     // we know we have the pixman limits. 16384.0 is a somewhat arbitrary
   537     // large number to make sure we avoid the expensive fmod when we can, but
   538     // still maintain a safe margin from the actual limit
   539     if (doTile && (userSpaceToImageSpace.y0 > 16384.0 || userSpaceToImageSpace.x0 > 16384.0)) {
   540         userSpaceToImageSpace.x0 = fmod(userSpaceToImageSpace.x0, aImageRect.width);
   541         userSpaceToImageSpace.y0 = fmod(userSpaceToImageSpace.y0, aImageRect.height);
   542     }
   543 #else
   544     // OK now, the hard part left is to account for the subimage sampling
   545     // restriction. If all the transforms involved are just integer
   546     // translations, then we assume no resampling will occur so there's
   547     // nothing to do.
   548     // XXX if only we had source-clipping in cairo!
   549     if (aContext->CurrentMatrix().HasNonIntegerTranslation() ||
   550         aUserSpaceToImageSpace.HasNonIntegerTranslation()) {
   551         if (doTile || !aSubimage.Contains(aImageRect)) {
   552             nsRefPtr<gfxDrawable> restrictedDrawable =
   553               CreateSamplingRestrictedDrawable(aDrawable, aContext,
   554                                                aUserSpaceToImageSpace, aSourceRect,
   555                                                aSubimage, aFormat);
   556             if (restrictedDrawable) {
   557                 drawable.swap(restrictedDrawable);
   558             }
   559         }
   560         // We no longer need to tile: Either we never needed to, or we already
   561         // filled a surface with the tiled pattern; this surface can now be
   562         // drawn without tiling.
   563         doTile = false;
   564     }
   565 #endif
   567     drawable->Draw(aContext, aFill, doTile, aFilter, userSpaceToImageSpace);
   568 }
   570 /* static */ int
   571 gfxUtils::ImageFormatToDepth(gfxImageFormat aFormat)
   572 {
   573     switch (aFormat) {
   574         case gfxImageFormat::ARGB32:
   575             return 32;
   576         case gfxImageFormat::RGB24:
   577             return 24;
   578         case gfxImageFormat::RGB16_565:
   579             return 16;
   580         default:
   581             break;
   582     }
   583     return 0;
   584 }
   586 static void
   587 PathFromRegionInternal(gfxContext* aContext, const nsIntRegion& aRegion,
   588                        bool aSnap)
   589 {
   590   aContext->NewPath();
   591   nsIntRegionRectIterator iter(aRegion);
   592   const nsIntRect* r;
   593   while ((r = iter.Next()) != nullptr) {
   594     aContext->Rectangle(gfxRect(r->x, r->y, r->width, r->height), aSnap);
   595   }
   596 }
   598 static void
   599 ClipToRegionInternal(gfxContext* aContext, const nsIntRegion& aRegion,
   600                      bool aSnap)
   601 {
   602   PathFromRegionInternal(aContext, aRegion, aSnap);
   603   aContext->Clip();
   604 }
   606 static TemporaryRef<Path>
   607 PathFromRegionInternal(DrawTarget* aTarget, const nsIntRegion& aRegion,
   608                        bool aSnap)
   609 {
   610   Matrix mat = aTarget->GetTransform();
   611   const gfxFloat epsilon = 0.000001;
   612 #define WITHIN_E(a,b) (fabs((a)-(b)) < epsilon)
   613   // We're essentially duplicating the logic in UserToDevicePixelSnapped here.
   614   bool shouldNotSnap = !aSnap || (WITHIN_E(mat._11,1.0) &&
   615                                   WITHIN_E(mat._22,1.0) &&
   616                                   WITHIN_E(mat._12,0.0) &&
   617                                   WITHIN_E(mat._21,0.0));
   618 #undef WITHIN_E
   620   RefPtr<PathBuilder> pb = aTarget->CreatePathBuilder();
   621   nsIntRegionRectIterator iter(aRegion);
   623   const nsIntRect* r;
   624   if (shouldNotSnap) {
   625     while ((r = iter.Next()) != nullptr) {
   626       pb->MoveTo(Point(r->x, r->y));
   627       pb->LineTo(Point(r->XMost(), r->y));
   628       pb->LineTo(Point(r->XMost(), r->YMost()));
   629       pb->LineTo(Point(r->x, r->YMost()));
   630       pb->Close();
   631     }
   632   } else {
   633     while ((r = iter.Next()) != nullptr) {
   634       Rect rect(r->x, r->y, r->width, r->height);
   636       rect.Round();
   637       pb->MoveTo(rect.TopLeft());
   638       pb->LineTo(rect.TopRight());
   639       pb->LineTo(rect.BottomRight());
   640       pb->LineTo(rect.BottomLeft());
   641       pb->Close();
   642     }
   643   }
   644   RefPtr<Path> path = pb->Finish();
   645   return path;
   646 }
   648 static void
   649 ClipToRegionInternal(DrawTarget* aTarget, const nsIntRegion& aRegion,
   650                      bool aSnap)
   651 {
   652   RefPtr<Path> path = PathFromRegionInternal(aTarget, aRegion, aSnap);
   653   aTarget->PushClip(path);
   654 }
   656 /*static*/ void
   657 gfxUtils::ClipToRegion(gfxContext* aContext, const nsIntRegion& aRegion)
   658 {
   659   ClipToRegionInternal(aContext, aRegion, false);
   660 }
   662 /*static*/ void
   663 gfxUtils::ClipToRegion(DrawTarget* aTarget, const nsIntRegion& aRegion)
   664 {
   665   ClipToRegionInternal(aTarget, aRegion, false);
   666 }
   668 /*static*/ void
   669 gfxUtils::ClipToRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion)
   670 {
   671   ClipToRegionInternal(aContext, aRegion, true);
   672 }
   674 /*static*/ void
   675 gfxUtils::ClipToRegionSnapped(DrawTarget* aTarget, const nsIntRegion& aRegion)
   676 {
   677   ClipToRegionInternal(aTarget, aRegion, true);
   678 }
   680 /*static*/ gfxFloat
   681 gfxUtils::ClampToScaleFactor(gfxFloat aVal)
   682 {
   683   // Arbitary scale factor limitation. We can increase this
   684   // for better scaling performance at the cost of worse
   685   // quality.
   686   static const gfxFloat kScaleResolution = 2;
   688   // Negative scaling is just a flip and irrelevant to
   689   // our resolution calculation.
   690   if (aVal < 0.0) {
   691     aVal = -aVal;
   692   }
   694   bool inverse = false;
   695   if (aVal < 1.0) {
   696     inverse = true;
   697     aVal = 1 / aVal;
   698   }
   700   gfxFloat power = log(aVal)/log(kScaleResolution);
   702   // If power is within 1e-6 of an integer, round to nearest to
   703   // prevent floating point errors, otherwise round up to the
   704   // next integer value.
   705   if (fabs(power - NS_round(power)) < 1e-6) {
   706     power = NS_round(power);
   707   } else if (inverse) {
   708     power = floor(power);
   709   } else {
   710     power = ceil(power);
   711   }
   713   gfxFloat scale = pow(kScaleResolution, power);
   715   if (inverse) {
   716     scale = 1 / scale;
   717   }
   719   return scale;
   720 }
   723 /*static*/ void
   724 gfxUtils::PathFromRegion(gfxContext* aContext, const nsIntRegion& aRegion)
   725 {
   726   PathFromRegionInternal(aContext, aRegion, false);
   727 }
   729 /*static*/ void
   730 gfxUtils::PathFromRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion)
   731 {
   732   PathFromRegionInternal(aContext, aRegion, true);
   733 }
   735 gfxMatrix
   736 gfxUtils::TransformRectToRect(const gfxRect& aFrom, const gfxPoint& aToTopLeft,
   737                               const gfxPoint& aToTopRight, const gfxPoint& aToBottomRight)
   738 {
   739   gfxMatrix m;
   740   if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
   741     // Not a rotation, so xy and yx are zero
   742     m.xy = m.yx = 0.0;
   743     m.xx = (aToBottomRight.x - aToTopLeft.x)/aFrom.width;
   744     m.yy = (aToBottomRight.y - aToTopLeft.y)/aFrom.height;
   745     m.x0 = aToTopLeft.x - m.xx*aFrom.x;
   746     m.y0 = aToTopLeft.y - m.yy*aFrom.y;
   747   } else {
   748     NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
   749                  "Destination rectangle not axis-aligned");
   750     m.xx = m.yy = 0.0;
   751     m.xy = (aToBottomRight.x - aToTopLeft.x)/aFrom.height;
   752     m.yx = (aToBottomRight.y - aToTopLeft.y)/aFrom.width;
   753     m.x0 = aToTopLeft.x - m.xy*aFrom.y;
   754     m.y0 = aToTopLeft.y - m.yx*aFrom.x;
   755   }
   756   return m;
   757 }
   759 Matrix
   760 gfxUtils::TransformRectToRect(const gfxRect& aFrom, const IntPoint& aToTopLeft,
   761                               const IntPoint& aToTopRight, const IntPoint& aToBottomRight)
   762 {
   763   Matrix m;
   764   if (aToTopRight.y == aToTopLeft.y && aToTopRight.x == aToBottomRight.x) {
   765     // Not a rotation, so xy and yx are zero
   766     m._12 = m._21 = 0.0;
   767     m._11 = (aToBottomRight.x - aToTopLeft.x)/aFrom.width;
   768     m._22 = (aToBottomRight.y - aToTopLeft.y)/aFrom.height;
   769     m._31 = aToTopLeft.x - m._11*aFrom.x;
   770     m._32 = aToTopLeft.y - m._22*aFrom.y;
   771   } else {
   772     NS_ASSERTION(aToTopRight.y == aToBottomRight.y && aToTopRight.x == aToTopLeft.x,
   773                  "Destination rectangle not axis-aligned");
   774     m._11 = m._22 = 0.0;
   775     m._21 = (aToBottomRight.x - aToTopLeft.x)/aFrom.height;
   776     m._12 = (aToBottomRight.y - aToTopLeft.y)/aFrom.width;
   777     m._31 = aToTopLeft.x - m._21*aFrom.y;
   778     m._32 = aToTopLeft.y - m._12*aFrom.x;
   779   }
   780   return m;
   781 }
   783 bool
   784 gfxUtils::GfxRectToIntRect(const gfxRect& aIn, nsIntRect* aOut)
   785 {
   786   *aOut = nsIntRect(int32_t(aIn.X()), int32_t(aIn.Y()),
   787   int32_t(aIn.Width()), int32_t(aIn.Height()));
   788   return gfxRect(aOut->x, aOut->y, aOut->width, aOut->height).IsEqualEdges(aIn);
   789 }
   791 void
   792 gfxUtils::GetYCbCrToRGBDestFormatAndSize(const PlanarYCbCrData& aData,
   793                                          gfxImageFormat& aSuggestedFormat,
   794                                          gfxIntSize& aSuggestedSize)
   795 {
   796   YUVType yuvtype =
   797     TypeFromSize(aData.mYSize.width,
   798                       aData.mYSize.height,
   799                       aData.mCbCrSize.width,
   800                       aData.mCbCrSize.height);
   802   // 'prescale' is true if the scaling is to be done as part of the
   803   // YCbCr to RGB conversion rather than on the RGB data when rendered.
   804   bool prescale = aSuggestedSize.width > 0 && aSuggestedSize.height > 0 &&
   805                     ToIntSize(aSuggestedSize) != aData.mPicSize;
   807   if (aSuggestedFormat == gfxImageFormat::RGB16_565) {
   808 #if defined(HAVE_YCBCR_TO_RGB565)
   809     if (prescale &&
   810         !IsScaleYCbCrToRGB565Fast(aData.mPicX,
   811                                        aData.mPicY,
   812                                        aData.mPicSize.width,
   813                                        aData.mPicSize.height,
   814                                        aSuggestedSize.width,
   815                                        aSuggestedSize.height,
   816                                        yuvtype,
   817                                        FILTER_BILINEAR) &&
   818         IsConvertYCbCrToRGB565Fast(aData.mPicX,
   819                                         aData.mPicY,
   820                                         aData.mPicSize.width,
   821                                         aData.mPicSize.height,
   822                                         yuvtype)) {
   823       prescale = false;
   824     }
   825 #else
   826     // yuv2rgb16 function not available
   827     aSuggestedFormat = gfxImageFormat::RGB24;
   828 #endif
   829   }
   830   else if (aSuggestedFormat != gfxImageFormat::RGB24) {
   831     // No other formats are currently supported.
   832     aSuggestedFormat = gfxImageFormat::RGB24;
   833   }
   834   if (aSuggestedFormat == gfxImageFormat::RGB24) {
   835     /* ScaleYCbCrToRGB32 does not support a picture offset, nor 4:4:4 data.
   836        See bugs 639415 and 640073. */
   837     if (aData.mPicX != 0 || aData.mPicY != 0 || yuvtype == YV24)
   838       prescale = false;
   839   }
   840   if (!prescale) {
   841     ToIntSize(aSuggestedSize) = aData.mPicSize;
   842   }
   843 }
   845 void
   846 gfxUtils::ConvertYCbCrToRGB(const PlanarYCbCrData& aData,
   847                             const gfxImageFormat& aDestFormat,
   848                             const gfxIntSize& aDestSize,
   849                             unsigned char* aDestBuffer,
   850                             int32_t aStride)
   851 {
   852   // ConvertYCbCrToRGB et al. assume the chroma planes are rounded up if the
   853   // luma plane is odd sized.
   854   MOZ_ASSERT((aData.mCbCrSize.width == aData.mYSize.width ||
   855               aData.mCbCrSize.width == (aData.mYSize.width + 1) >> 1) &&
   856              (aData.mCbCrSize.height == aData.mYSize.height ||
   857               aData.mCbCrSize.height == (aData.mYSize.height + 1) >> 1));
   858   YUVType yuvtype =
   859     TypeFromSize(aData.mYSize.width,
   860                       aData.mYSize.height,
   861                       aData.mCbCrSize.width,
   862                       aData.mCbCrSize.height);
   864   // Convert from YCbCr to RGB now, scaling the image if needed.
   865   if (ToIntSize(aDestSize) != aData.mPicSize) {
   866 #if defined(HAVE_YCBCR_TO_RGB565)
   867     if (aDestFormat == gfxImageFormat::RGB16_565) {
   868       ScaleYCbCrToRGB565(aData.mYChannel,
   869                               aData.mCbChannel,
   870                               aData.mCrChannel,
   871                               aDestBuffer,
   872                               aData.mPicX,
   873                               aData.mPicY,
   874                               aData.mPicSize.width,
   875                               aData.mPicSize.height,
   876                               aDestSize.width,
   877                               aDestSize.height,
   878                               aData.mYStride,
   879                               aData.mCbCrStride,
   880                               aStride,
   881                               yuvtype,
   882                               FILTER_BILINEAR);
   883     } else
   884 #endif
   885       ScaleYCbCrToRGB32(aData.mYChannel,
   886                              aData.mCbChannel,
   887                              aData.mCrChannel,
   888                              aDestBuffer,
   889                              aData.mPicSize.width,
   890                              aData.mPicSize.height,
   891                              aDestSize.width,
   892                              aDestSize.height,
   893                              aData.mYStride,
   894                              aData.mCbCrStride,
   895                              aStride,
   896                              yuvtype,
   897                              ROTATE_0,
   898                              FILTER_BILINEAR);
   899   } else { // no prescale
   900 #if defined(HAVE_YCBCR_TO_RGB565)
   901     if (aDestFormat == gfxImageFormat::RGB16_565) {
   902       ConvertYCbCrToRGB565(aData.mYChannel,
   903                                 aData.mCbChannel,
   904                                 aData.mCrChannel,
   905                                 aDestBuffer,
   906                                 aData.mPicX,
   907                                 aData.mPicY,
   908                                 aData.mPicSize.width,
   909                                 aData.mPicSize.height,
   910                                 aData.mYStride,
   911                                 aData.mCbCrStride,
   912                                 aStride,
   913                                 yuvtype);
   914     } else // aDestFormat != gfxImageFormat::RGB16_565
   915 #endif
   916       ConvertYCbCrToRGB32(aData.mYChannel,
   917                                aData.mCbChannel,
   918                                aData.mCrChannel,
   919                                aDestBuffer,
   920                                aData.mPicX,
   921                                aData.mPicY,
   922                                aData.mPicSize.width,
   923                                aData.mPicSize.height,
   924                                aData.mYStride,
   925                                aData.mCbCrStride,
   926                                aStride,
   927                                yuvtype);
   928   }
   929 }
   931 /* static */ TemporaryRef<DataSourceSurface>
   932 gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface,
   933                                                    SurfaceFormat aFormat)
   934 {
   935   MOZ_ASSERT(aFormat != aSurface->GetFormat(),
   936              "Unnecessary - and very expersive - surface format conversion");
   938   Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height);
   940   if (aSurface->GetType() != SurfaceType::DATA) {
   941     // If the surface is NOT of type DATA then its data is not mapped into main
   942     // memory. Format conversion is probably faster on the GPU, and by doing it
   943     // there we can avoid any expensive uploads/readbacks except for (possibly)
   944     // a single readback due to the unavoidable GetDataSurface() call. Using
   945     // CreateOffscreenContentDrawTarget ensures the conversion happens on the
   946     // GPU.
   947     RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
   948       CreateOffscreenContentDrawTarget(aSurface->GetSize(), aFormat);
   949     // Using DrawSurface() here rather than CopySurface() because CopySurface
   950     // is optimized for memcpy and therefore isn't good for format conversion.
   951     // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
   952     // generally more optimized.
   953     dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
   954                     DrawOptions(1.0f, CompositionOp::OP_OVER));
   955     RefPtr<SourceSurface> surface = dt->Snapshot();
   956     return surface->GetDataSurface();
   957   }
   959   // If the surface IS of type DATA then it may or may not be in main memory
   960   // depending on whether or not it has been mapped yet. We have no way of
   961   // knowing, so we can't be sure if it's best to create a data wrapping
   962   // DrawTarget for the conversion or an offscreen content DrawTarget. We could
   963   // guess it's not mapped and create an offscreen content DrawTarget, but if
   964   // it is then we'll end up uploading the surface data, and most likely the
   965   // caller is going to be accessing the resulting surface data, resulting in a
   966   // readback (both very expensive operations). Alternatively we could guess
   967   // the data is mapped and create a data wrapping DrawTarget and, if the
   968   // surface is not in main memory, then we will incure a readback. The latter
   969   // of these two "wrong choices" is the least costly (a readback, vs an
   970   // upload and a readback), and more than likely the DATA surface that we've
   971   // been passed actually IS in main memory anyway. For these reasons it's most
   972   // likely best to create a data wrapping DrawTarget here to do the format
   973   // conversion.
   974   RefPtr<DataSourceSurface> dataSurface =
   975     Factory::CreateDataSourceSurface(aSurface->GetSize(), aFormat);
   976   DataSourceSurface::MappedSurface map;
   977   if (!dataSurface ||
   978       !dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
   979     return nullptr;
   980   }
   981   RefPtr<DrawTarget> dt =
   982     Factory::CreateDrawTargetForData(BackendType::CAIRO,
   983                                      map.mData,
   984                                      dataSurface->GetSize(),
   985                                      map.mStride,
   986                                      aFormat);
   987   if (!dt) {
   988     dataSurface->Unmap();
   989     return nullptr;
   990   }
   991   // Using DrawSurface() here rather than CopySurface() because CopySurface
   992   // is optimized for memcpy and therefore isn't good for format conversion.
   993   // Using OP_OVER since in our case it's equivalent to OP_SOURCE and
   994   // generally more optimized.
   995   dt->DrawSurface(aSurface, bounds, bounds, DrawSurfaceOptions(),
   996                   DrawOptions(1.0f, CompositionOp::OP_OVER));
   997   dataSurface->Unmap();
   998   return dataSurface.forget();
   999 }
  1001 #ifdef MOZ_DUMP_PAINTING
  1002 /* static */ void
  1003 gfxUtils::WriteAsPNG(DrawTarget* aDT, const char* aFile)
  1005   aDT->Flush();
  1006   nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT);
  1007   if (surf) {
  1008     surf->WriteAsPNG(aFile);
  1009   } else {
  1010     NS_WARNING("Failed to get Thebes surface!");
  1014 /* static */ void
  1015 gfxUtils::DumpAsDataURL(DrawTarget* aDT)
  1017   aDT->Flush();
  1018   nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT);
  1019   if (surf) {
  1020     surf->DumpAsDataURL();
  1021   } else {
  1022     NS_WARNING("Failed to get Thebes surface!");
  1026 /* static */ void
  1027 gfxUtils::CopyAsDataURL(DrawTarget* aDT)
  1029   aDT->Flush();
  1030   nsRefPtr<gfxASurface> surf = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(aDT);
  1031   if (surf) {
  1032     surf->CopyAsDataURL();
  1033   } else {
  1034     NS_WARNING("Failed to get Thebes surface!");
  1038 /* static */ void
  1039 gfxUtils::WriteAsPNG(RefPtr<gfx::SourceSurface> aSourceSurface, const char* aFile)
  1041   RefPtr<gfx::DataSourceSurface> dataSurface = aSourceSurface->GetDataSurface();
  1042   RefPtr<gfx::DrawTarget> dt
  1043             = gfxPlatform::GetPlatform()
  1044                 ->CreateDrawTargetForData(dataSurface->GetData(),
  1045                                           dataSurface->GetSize(),
  1046                                           dataSurface->Stride(),
  1047                                           aSourceSurface->GetFormat());
  1048   gfxUtils::WriteAsPNG(dt.get(), aFile);
  1051 /* static */ void
  1052 gfxUtils::DumpAsDataURL(RefPtr<gfx::SourceSurface> aSourceSurface)
  1054   RefPtr<gfx::DataSourceSurface> dataSurface = aSourceSurface->GetDataSurface();
  1055   RefPtr<gfx::DrawTarget> dt
  1056             = gfxPlatform::GetPlatform()
  1057                 ->CreateDrawTargetForData(dataSurface->GetData(),
  1058                                           dataSurface->GetSize(),
  1059                                           dataSurface->Stride(),
  1060                                           aSourceSurface->GetFormat());
  1061   gfxUtils::DumpAsDataURL(dt.get());
  1064 /* static */ void
  1065 gfxUtils::CopyAsDataURL(RefPtr<gfx::SourceSurface> aSourceSurface)
  1067   RefPtr<gfx::DataSourceSurface> dataSurface = aSourceSurface->GetDataSurface();
  1068   RefPtr<gfx::DrawTarget> dt
  1069             = gfxPlatform::GetPlatform()
  1070                 ->CreateDrawTargetForData(dataSurface->GetData(),
  1071                                           dataSurface->GetSize(),
  1072                                           dataSurface->Stride(),
  1073                                           aSourceSurface->GetFormat());
  1075   gfxUtils::CopyAsDataURL(dt.get());
  1078 bool gfxUtils::sDumpPaintList = getenv("MOZ_DUMP_PAINT_LIST") != 0;
  1079 bool gfxUtils::sDumpPainting = getenv("MOZ_DUMP_PAINT") != 0;
  1080 bool gfxUtils::sDumpPaintingToFile = getenv("MOZ_DUMP_PAINT_TO_FILE") != 0;
  1081 FILE *gfxUtils::sDumpPaintFile = nullptr;
  1082 #endif

mercurial