ClippedImage.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  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/. */
  5. #include "ClippedImage.h"
  6. #include <algorithm>
  7. #include <new> // Workaround for bug in VS10; see bug 981264.
  8. #include <cmath>
  9. #include <utility>
  10. #include "gfxDrawable.h"
  11. #include "gfxPlatform.h"
  12. #include "gfxUtils.h"
  13. #include "mozilla/gfx/2D.h"
  14. #include "mozilla/Move.h"
  15. #include "mozilla/RefPtr.h"
  16. #include "mozilla/Pair.h"
  17. #include "mozilla/Tuple.h"
  18. #include "ImageRegion.h"
  19. #include "Orientation.h"
  20. #include "SVGImageContext.h"
  21. namespace mozilla {
  22. using namespace gfx;
  23. using layers::LayerManager;
  24. using layers::ImageContainer;
  25. using std::make_pair;
  26. using std::max;
  27. using std::modf;
  28. using std::pair;
  29. namespace image {
  30. class ClippedImageCachedSurface
  31. {
  32. public:
  33. ClippedImageCachedSurface(already_AddRefed<SourceSurface> aSurface,
  34. const nsIntSize& aSize,
  35. const Maybe<SVGImageContext>& aSVGContext,
  36. float aFrame,
  37. uint32_t aFlags,
  38. DrawResult aDrawResult)
  39. : mSurface(aSurface)
  40. , mSize(aSize)
  41. , mSVGContext(aSVGContext)
  42. , mFrame(aFrame)
  43. , mFlags(aFlags)
  44. , mDrawResult(aDrawResult)
  45. {
  46. MOZ_ASSERT(mSurface, "Must have a valid surface");
  47. }
  48. bool Matches(const nsIntSize& aSize,
  49. const Maybe<SVGImageContext>& aSVGContext,
  50. float aFrame,
  51. uint32_t aFlags) const
  52. {
  53. return mSize == aSize &&
  54. mSVGContext == aSVGContext &&
  55. mFrame == aFrame &&
  56. mFlags == aFlags;
  57. }
  58. already_AddRefed<SourceSurface> Surface() const
  59. {
  60. RefPtr<SourceSurface> surf(mSurface);
  61. return surf.forget();
  62. }
  63. DrawResult GetDrawResult() const
  64. {
  65. return mDrawResult;
  66. }
  67. bool NeedsRedraw() const
  68. {
  69. return mDrawResult != DrawResult::SUCCESS &&
  70. mDrawResult != DrawResult::BAD_IMAGE;
  71. }
  72. private:
  73. RefPtr<SourceSurface> mSurface;
  74. const nsIntSize mSize;
  75. Maybe<SVGImageContext> mSVGContext;
  76. const float mFrame;
  77. const uint32_t mFlags;
  78. const DrawResult mDrawResult;
  79. };
  80. class DrawSingleTileCallback : public gfxDrawingCallback
  81. {
  82. public:
  83. DrawSingleTileCallback(ClippedImage* aImage,
  84. const nsIntSize& aSize,
  85. const Maybe<SVGImageContext>& aSVGContext,
  86. uint32_t aWhichFrame,
  87. uint32_t aFlags)
  88. : mImage(aImage)
  89. , mSize(aSize)
  90. , mSVGContext(aSVGContext)
  91. , mWhichFrame(aWhichFrame)
  92. , mFlags(aFlags)
  93. , mDrawResult(DrawResult::NOT_READY)
  94. {
  95. MOZ_ASSERT(mImage, "Must have an image to clip");
  96. }
  97. virtual bool operator()(gfxContext* aContext,
  98. const gfxRect& aFillRect,
  99. const SamplingFilter aSamplingFilter,
  100. const gfxMatrix& aTransform)
  101. {
  102. MOZ_ASSERT(aTransform.IsIdentity(),
  103. "Caller is probably CreateSamplingRestrictedDrawable, "
  104. "which should not happen");
  105. // Draw the image. |gfxCallbackDrawable| always calls this function with
  106. // arguments that guarantee we never tile.
  107. mDrawResult =
  108. mImage->DrawSingleTile(aContext, mSize, ImageRegion::Create(aFillRect),
  109. mWhichFrame, aSamplingFilter, mSVGContext, mFlags);
  110. return true;
  111. }
  112. DrawResult GetDrawResult() { return mDrawResult; }
  113. private:
  114. RefPtr<ClippedImage> mImage;
  115. const nsIntSize mSize;
  116. const Maybe<SVGImageContext>& mSVGContext;
  117. const uint32_t mWhichFrame;
  118. const uint32_t mFlags;
  119. DrawResult mDrawResult;
  120. };
  121. ClippedImage::ClippedImage(Image* aImage,
  122. nsIntRect aClip,
  123. const Maybe<nsSize>& aSVGViewportSize)
  124. : ImageWrapper(aImage)
  125. , mClip(aClip)
  126. {
  127. MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image");
  128. MOZ_ASSERT_IF(aSVGViewportSize,
  129. aImage->GetType() == imgIContainer::TYPE_VECTOR);
  130. if (aSVGViewportSize) {
  131. mSVGViewportSize = Some(aSVGViewportSize->ToNearestPixels(
  132. nsPresContext::AppUnitsPerCSSPixel()));
  133. }
  134. }
  135. ClippedImage::~ClippedImage()
  136. { }
  137. bool
  138. ClippedImage::ShouldClip()
  139. {
  140. // We need to evaluate the clipping region against the image's width and
  141. // height once they're available to determine if it's valid and whether we
  142. // actually need to do any work. We may fail if the image's width and height
  143. // aren't available yet, in which case we'll try again later.
  144. if (mShouldClip.isNothing()) {
  145. int32_t width, height;
  146. RefPtr<ProgressTracker> progressTracker =
  147. InnerImage()->GetProgressTracker();
  148. if (InnerImage()->HasError()) {
  149. // If there's a problem with the inner image we'll let it handle
  150. // everything.
  151. mShouldClip.emplace(false);
  152. } else if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
  153. // Clamp the clipping region to the size of the SVG viewport.
  154. nsIntRect svgViewportRect(nsIntPoint(0,0), *mSVGViewportSize);
  155. mClip = mClip.Intersect(svgViewportRect);
  156. // If the clipping region is the same size as the SVG viewport size
  157. // we don't have to do anything.
  158. mShouldClip.emplace(!mClip.IsEqualInterior(svgViewportRect));
  159. } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 &&
  160. NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) {
  161. // Clamp the clipping region to the size of the underlying image.
  162. mClip = mClip.Intersect(nsIntRect(0, 0, width, height));
  163. // If the clipping region is the same size as the underlying image we
  164. // don't have to do anything.
  165. mShouldClip.emplace(!mClip.IsEqualInterior(nsIntRect(0, 0, width,
  166. height)));
  167. } else if (progressTracker &&
  168. !(progressTracker->GetProgress() & FLAG_LOAD_COMPLETE)) {
  169. // The image just hasn't finished loading yet. We don't yet know whether
  170. // clipping with be needed or not for now. Just return without memorizing
  171. // anything.
  172. return false;
  173. } else {
  174. // We have a fully loaded image without a clearly defined width and
  175. // height. This can happen with SVG images.
  176. mShouldClip.emplace(false);
  177. }
  178. }
  179. MOZ_ASSERT(mShouldClip.isSome(), "Should have computed a result");
  180. return *mShouldClip;
  181. }
  182. NS_IMPL_ISUPPORTS_INHERITED0(ClippedImage, ImageWrapper)
  183. NS_IMETHODIMP
  184. ClippedImage::GetWidth(int32_t* aWidth)
  185. {
  186. if (!ShouldClip()) {
  187. return InnerImage()->GetWidth(aWidth);
  188. }
  189. *aWidth = mClip.width;
  190. return NS_OK;
  191. }
  192. NS_IMETHODIMP
  193. ClippedImage::GetHeight(int32_t* aHeight)
  194. {
  195. if (!ShouldClip()) {
  196. return InnerImage()->GetHeight(aHeight);
  197. }
  198. *aHeight = mClip.height;
  199. return NS_OK;
  200. }
  201. NS_IMETHODIMP
  202. ClippedImage::GetIntrinsicSize(nsSize* aSize)
  203. {
  204. if (!ShouldClip()) {
  205. return InnerImage()->GetIntrinsicSize(aSize);
  206. }
  207. *aSize = nsSize(mClip.width, mClip.height);
  208. return NS_OK;
  209. }
  210. NS_IMETHODIMP
  211. ClippedImage::GetIntrinsicRatio(AspectRatio* aRatio)
  212. {
  213. if (!ShouldClip()) {
  214. return InnerImage()->GetIntrinsicRatio(aRatio);
  215. }
  216. *aRatio = AspectRatio::FromSize(mClip.width, mClip.height);
  217. return NS_OK;
  218. }
  219. NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
  220. ClippedImage::GetFrame(uint32_t aWhichFrame,
  221. uint32_t aFlags)
  222. {
  223. DrawResult result;
  224. RefPtr<SourceSurface> surface;
  225. Tie(result, surface) = GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags);
  226. return surface.forget();
  227. }
  228. NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
  229. ClippedImage::GetFrameAtSize(const IntSize& aSize,
  230. uint32_t aWhichFrame,
  231. uint32_t aFlags)
  232. {
  233. // XXX(seth): It'd be nice to support downscale-during-decode for this case,
  234. // but right now we just fall back to the intrinsic size.
  235. return GetFrame(aWhichFrame, aFlags);
  236. }
  237. Pair<DrawResult, RefPtr<SourceSurface>>
  238. ClippedImage::GetFrameInternal(const nsIntSize& aSize,
  239. const Maybe<SVGImageContext>& aSVGContext,
  240. uint32_t aWhichFrame,
  241. uint32_t aFlags)
  242. {
  243. if (!ShouldClip()) {
  244. RefPtr<SourceSurface> surface = InnerImage()->GetFrame(aWhichFrame, aFlags);
  245. return MakePair(surface ? DrawResult::SUCCESS : DrawResult::NOT_READY,
  246. Move(surface));
  247. }
  248. float frameToDraw = InnerImage()->GetFrameIndex(aWhichFrame);
  249. if (!mCachedSurface ||
  250. !mCachedSurface->Matches(aSize, aSVGContext, frameToDraw, aFlags) ||
  251. mCachedSurface->NeedsRedraw()) {
  252. // Create a surface to draw into.
  253. RefPtr<DrawTarget> target = gfxPlatform::GetPlatform()->
  254. CreateOffscreenContentDrawTarget(IntSize(aSize.width, aSize.height),
  255. SurfaceFormat::B8G8R8A8);
  256. if (!target || !target->IsValid()) {
  257. NS_ERROR("Could not create a DrawTarget");
  258. return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
  259. }
  260. RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target);
  261. MOZ_ASSERT(ctx); // already checked the draw target above
  262. // Create our callback.
  263. RefPtr<DrawSingleTileCallback> drawTileCallback =
  264. new DrawSingleTileCallback(this, aSize, aSVGContext, aWhichFrame, aFlags);
  265. RefPtr<gfxDrawable> drawable =
  266. new gfxCallbackDrawable(drawTileCallback, aSize);
  267. // Actually draw. The callback will end up invoking DrawSingleTile.
  268. gfxUtils::DrawPixelSnapped(ctx, drawable, aSize,
  269. ImageRegion::Create(aSize),
  270. SurfaceFormat::B8G8R8A8,
  271. SamplingFilter::LINEAR,
  272. imgIContainer::FLAG_CLAMP);
  273. // Cache the resulting surface.
  274. mCachedSurface =
  275. MakeUnique<ClippedImageCachedSurface>(target->Snapshot(), aSize, aSVGContext,
  276. frameToDraw, aFlags,
  277. drawTileCallback->GetDrawResult());
  278. }
  279. MOZ_ASSERT(mCachedSurface, "Should have a cached surface now");
  280. RefPtr<SourceSurface> surface = mCachedSurface->Surface();
  281. return MakePair(mCachedSurface->GetDrawResult(), Move(surface));
  282. }
  283. NS_IMETHODIMP_(bool)
  284. ClippedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
  285. {
  286. if (!ShouldClip()) {
  287. return InnerImage()->IsImageContainerAvailable(aManager, aFlags);
  288. }
  289. return false;
  290. }
  291. NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
  292. ClippedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
  293. {
  294. // XXX(seth): We currently don't have a way of clipping the result of
  295. // GetImageContainer. We work around this by always returning null, but if it
  296. // ever turns out that ClippedImage is widely used on codepaths that can
  297. // actually benefit from GetImageContainer, it would be a good idea to fix
  298. // that method for performance reasons.
  299. if (!ShouldClip()) {
  300. return InnerImage()->GetImageContainer(aManager, aFlags);
  301. }
  302. return nullptr;
  303. }
  304. static bool
  305. MustCreateSurface(gfxContext* aContext,
  306. const nsIntSize& aSize,
  307. const ImageRegion& aRegion,
  308. const uint32_t aFlags)
  309. {
  310. gfxRect imageRect(0, 0, aSize.width, aSize.height);
  311. bool willTile = !imageRect.Contains(aRegion.Rect()) &&
  312. !(aFlags & imgIContainer::FLAG_CLAMP);
  313. bool willResample = aContext->CurrentMatrix().HasNonIntegerTranslation() &&
  314. (willTile || !aRegion.RestrictionContains(imageRect));
  315. return willTile || willResample;
  316. }
  317. NS_IMETHODIMP_(DrawResult)
  318. ClippedImage::Draw(gfxContext* aContext,
  319. const nsIntSize& aSize,
  320. const ImageRegion& aRegion,
  321. uint32_t aWhichFrame,
  322. SamplingFilter aSamplingFilter,
  323. const Maybe<SVGImageContext>& aSVGContext,
  324. uint32_t aFlags)
  325. {
  326. if (!ShouldClip()) {
  327. return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame,
  328. aSamplingFilter, aSVGContext, aFlags);
  329. }
  330. // Check for tiling. If we need to tile then we need to create a
  331. // gfxCallbackDrawable to handle drawing for us.
  332. if (MustCreateSurface(aContext, aSize, aRegion, aFlags)) {
  333. // Create a temporary surface containing a single tile of this image.
  334. // GetFrame will call DrawSingleTile internally.
  335. DrawResult result;
  336. RefPtr<SourceSurface> surface;
  337. Tie(result, surface) =
  338. GetFrameInternal(aSize, aSVGContext, aWhichFrame, aFlags);
  339. if (!surface) {
  340. MOZ_ASSERT(result != DrawResult::SUCCESS);
  341. return result;
  342. }
  343. // Create a drawable from that surface.
  344. RefPtr<gfxSurfaceDrawable> drawable =
  345. new gfxSurfaceDrawable(surface, aSize);
  346. // Draw.
  347. gfxUtils::DrawPixelSnapped(aContext, drawable, aSize, aRegion,
  348. SurfaceFormat::B8G8R8A8, aSamplingFilter);
  349. return result;
  350. }
  351. return DrawSingleTile(aContext, aSize, aRegion, aWhichFrame,
  352. aSamplingFilter, aSVGContext, aFlags);
  353. }
  354. DrawResult
  355. ClippedImage::DrawSingleTile(gfxContext* aContext,
  356. const nsIntSize& aSize,
  357. const ImageRegion& aRegion,
  358. uint32_t aWhichFrame,
  359. SamplingFilter aSamplingFilter,
  360. const Maybe<SVGImageContext>& aSVGContext,
  361. uint32_t aFlags)
  362. {
  363. MOZ_ASSERT(!MustCreateSurface(aContext, aSize, aRegion, aFlags),
  364. "Shouldn't need to create a surface");
  365. gfxRect clip(mClip.x, mClip.y, mClip.width, mClip.height);
  366. nsIntSize size(aSize), innerSize(aSize);
  367. bool needScale = false;
  368. if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
  369. innerSize = *mSVGViewportSize;
  370. needScale = true;
  371. } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) &&
  372. NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) {
  373. needScale = true;
  374. } else {
  375. MOZ_ASSERT_UNREACHABLE(
  376. "If ShouldClip() led us to draw then we should never get here");
  377. }
  378. if (needScale) {
  379. double scaleX = aSize.width / clip.width;
  380. double scaleY = aSize.height / clip.height;
  381. // Map the clip and size to the scale requested by the caller.
  382. clip.Scale(scaleX, scaleY);
  383. size = innerSize;
  384. size.Scale(scaleX, scaleY);
  385. }
  386. // We restrict our drawing to only the clipping region, and translate so that
  387. // the clipping region is placed at the position the caller expects.
  388. ImageRegion region(aRegion);
  389. region.MoveBy(clip.x, clip.y);
  390. region = region.Intersect(clip);
  391. gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
  392. aContext->Multiply(gfxMatrix::Translation(-clip.x, -clip.y));
  393. auto unclipViewport = [&](const SVGImageContext& aOldContext) {
  394. // Map the viewport to the inner image. Note that we don't take the aSize
  395. // parameter of imgIContainer::Draw into account, just the clipping region.
  396. // The size in pixels at which the output will ultimately be drawn is
  397. // irrelevant here since the purpose of the SVG viewport size is to
  398. // determine what *region* of the SVG document will be drawn.
  399. CSSIntSize vSize(aOldContext.GetViewportSize());
  400. vSize.width = ceil(vSize.width * double(innerSize.width) / mClip.width);
  401. vSize.height =
  402. ceil(vSize.height * double(innerSize.height) / mClip.height);
  403. return SVGImageContext(vSize,
  404. aOldContext.GetPreserveAspectRatio());
  405. };
  406. return InnerImage()->Draw(aContext, size, region,
  407. aWhichFrame, aSamplingFilter,
  408. aSVGContext.map(unclipViewport),
  409. aFlags);
  410. }
  411. NS_IMETHODIMP
  412. ClippedImage::RequestDiscard()
  413. {
  414. // We're very aggressive about discarding.
  415. mCachedSurface = nullptr;
  416. return InnerImage()->RequestDiscard();
  417. }
  418. NS_IMETHODIMP_(Orientation)
  419. ClippedImage::GetOrientation()
  420. {
  421. // XXX(seth): This should not actually be here; this is just to work around a
  422. // what appears to be a bug in MSVC's linker.
  423. return InnerImage()->GetOrientation();
  424. }
  425. nsIntSize
  426. ClippedImage::OptimalImageSizeForDest(const gfxSize& aDest,
  427. uint32_t aWhichFrame,
  428. SamplingFilter aSamplingFilter,
  429. uint32_t aFlags)
  430. {
  431. if (!ShouldClip()) {
  432. return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
  433. aSamplingFilter, aFlags);
  434. }
  435. int32_t imgWidth, imgHeight;
  436. bool needScale = false;
  437. bool forceUniformScaling = false;
  438. if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
  439. imgWidth = mSVGViewportSize->width;
  440. imgHeight = mSVGViewportSize->height;
  441. needScale = true;
  442. forceUniformScaling = (aFlags & imgIContainer::FLAG_FORCE_UNIFORM_SCALING);
  443. } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) &&
  444. NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) {
  445. needScale = true;
  446. }
  447. if (needScale) {
  448. // To avoid ugly sampling artifacts, ClippedImage needs the image size to
  449. // be chosen such that the clipping region lies on pixel boundaries.
  450. // First, we select a scale that's good for ClippedImage. An integer
  451. // multiple of the size of the clipping region is always fine.
  452. IntSize scale = IntSize::Ceil(aDest.width / mClip.width,
  453. aDest.height / mClip.height);
  454. if (forceUniformScaling) {
  455. scale.width = scale.height = max(scale.height, scale.width);
  456. }
  457. // Determine the size we'd prefer to render the inner image at, and ask the
  458. // inner image what size we should actually use.
  459. gfxSize desiredSize(imgWidth * scale.width, imgHeight * scale.height);
  460. nsIntSize innerDesiredSize =
  461. InnerImage()->OptimalImageSizeForDest(desiredSize, aWhichFrame,
  462. aSamplingFilter, aFlags);
  463. // To get our final result, we take the inner image's desired size and
  464. // determine how large the clipped region would be at that scale. (Again, we
  465. // ensure an integer multiple of the size of the clipping region.)
  466. IntSize finalScale = IntSize::Ceil(double(innerDesiredSize.width) / imgWidth,
  467. double(innerDesiredSize.height) / imgHeight);
  468. return mClip.Size() * finalScale;
  469. }
  470. MOZ_ASSERT(false,
  471. "If ShouldClip() led us to draw then we should never get here");
  472. return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
  473. aSamplingFilter, aFlags);
  474. }
  475. NS_IMETHODIMP_(nsIntRect)
  476. ClippedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
  477. {
  478. if (!ShouldClip()) {
  479. return InnerImage()->GetImageSpaceInvalidationRect(aRect);
  480. }
  481. nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
  482. rect = rect.Intersect(mClip);
  483. rect.MoveBy(-mClip.x, -mClip.y);
  484. return rect;
  485. }
  486. } // namespace image
  487. } // namespace mozilla