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.

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

mercurial