michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gfxDrawable.h" michael@0: #include "gfxASurface.h" michael@0: #include "gfxContext.h" michael@0: #include "gfxImageSurface.h" michael@0: #include "gfxPlatform.h" michael@0: #include "gfxColor.h" michael@0: #ifdef MOZ_X11 michael@0: #include "cairo.h" michael@0: #include "gfxXlibSurface.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: michael@0: gfxSurfaceDrawable::gfxSurfaceDrawable(gfxASurface* aSurface, michael@0: const gfxIntSize aSize, michael@0: const gfxMatrix aTransform) michael@0: : gfxDrawable(aSize) michael@0: , mSurface(aSurface) michael@0: , mTransform(aTransform) michael@0: { michael@0: } michael@0: michael@0: gfxSurfaceDrawable::gfxSurfaceDrawable(DrawTarget* aDrawTarget, michael@0: const gfxIntSize aSize, michael@0: const gfxMatrix aTransform) michael@0: : gfxDrawable(aSize) michael@0: , mDrawTarget(aDrawTarget) michael@0: , mTransform(aTransform) michael@0: { michael@0: } michael@0: michael@0: gfxSurfaceDrawable::gfxSurfaceDrawable(SourceSurface* aSurface, michael@0: const gfxIntSize aSize, michael@0: const gfxMatrix aTransform) michael@0: : gfxDrawable(aSize) michael@0: , mSourceSurface(aSurface) michael@0: , mTransform(aTransform) michael@0: { michael@0: } michael@0: michael@0: static gfxMatrix michael@0: DeviceToImageTransform(gfxContext* aContext, michael@0: const gfxMatrix& aUserSpaceToImageSpace) michael@0: { michael@0: gfxFloat deviceX, deviceY; michael@0: nsRefPtr currentTarget = michael@0: aContext->CurrentSurface(&deviceX, &deviceY); michael@0: gfxMatrix currentMatrix = aContext->CurrentMatrix(); michael@0: gfxMatrix deviceToUser = gfxMatrix(currentMatrix).Invert(); michael@0: deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY)); michael@0: return gfxMatrix(deviceToUser).Multiply(aUserSpaceToImageSpace); michael@0: } michael@0: michael@0: static void michael@0: PreparePatternForUntiledDrawing(gfxPattern* aPattern, michael@0: const gfxMatrix& aDeviceToImage, michael@0: gfxASurface *currentTarget, michael@0: const GraphicsFilter aDefaultFilter) michael@0: { michael@0: if (!currentTarget) { michael@0: // This happens if we're dealing with an Azure target. michael@0: aPattern->SetExtend(gfxPattern::EXTEND_PAD); michael@0: aPattern->SetFilter(aDefaultFilter); michael@0: return; michael@0: } michael@0: michael@0: // In theory we can handle this using cairo's EXTEND_PAD, michael@0: // but implementation limitations mean we have to consult michael@0: // the surface type. michael@0: switch (currentTarget->GetType()) { michael@0: michael@0: #ifdef MOZ_X11 michael@0: case gfxSurfaceType::Xlib: michael@0: { michael@0: // See bugs 324698, 422179, and 468496. This is a workaround for michael@0: // XRender's RepeatPad not being implemented correctly on old X michael@0: // servers. michael@0: // michael@0: // In this situation, cairo avoids XRender and instead reads back michael@0: // to perform EXTEND_PAD with pixman. This is too slow so we michael@0: // avoid EXTEND_PAD and set the filter to CAIRO_FILTER_FAST --- michael@0: // otherwise, pixman's sampling will sample transparency for the michael@0: // outside edges and we'll get blurry edges. michael@0: // michael@0: // But don't do this for simple downscales because it's horrible. michael@0: // Downscaling means that device-space coordinates are michael@0: // scaled *up* to find the image pixel coordinates. michael@0: // michael@0: // Cairo, and hence Gecko, can use RepeatPad on Xorg 1.7. We michael@0: // enable EXTEND_PAD provided that we're running on a recent michael@0: // enough X server. michael@0: if (static_cast(currentTarget)->IsPadSlow()) { michael@0: bool isDownscale = michael@0: aDeviceToImage.xx >= 1.0 && aDeviceToImage.yy >= 1.0 && michael@0: aDeviceToImage.xy == 0.0 && aDeviceToImage.yx == 0.0; michael@0: michael@0: GraphicsFilter filter = michael@0: isDownscale ? aDefaultFilter : (const GraphicsFilter)GraphicsFilter::FILTER_FAST; michael@0: aPattern->SetFilter(filter); michael@0: michael@0: // Use the default EXTEND_NONE michael@0: break; michael@0: } michael@0: // else fall through to EXTEND_PAD and the default filter. michael@0: } michael@0: #endif michael@0: michael@0: default: michael@0: // turn on EXTEND_PAD. michael@0: // This is what we really want for all surface types, if the michael@0: // implementation was universally good. michael@0: aPattern->SetExtend(gfxPattern::EXTEND_PAD); michael@0: aPattern->SetFilter(aDefaultFilter); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxSurfaceDrawable::Draw(gfxContext* aContext, michael@0: const gfxRect& aFillRect, michael@0: bool aRepeat, michael@0: const GraphicsFilter& aFilter, michael@0: const gfxMatrix& aTransform) michael@0: { michael@0: nsRefPtr pattern; michael@0: if (mDrawTarget) { michael@0: if (aContext->IsCairo()) { michael@0: nsRefPtr source = michael@0: gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mDrawTarget); michael@0: pattern = new gfxPattern(source); michael@0: } else { michael@0: RefPtr source = mDrawTarget->Snapshot(); michael@0: pattern = new gfxPattern(source, Matrix()); michael@0: } michael@0: } else if (mSourceSurface) { michael@0: pattern = new gfxPattern(mSourceSurface, Matrix()); michael@0: } else { michael@0: pattern = new gfxPattern(mSurface); michael@0: } michael@0: if (aRepeat) { michael@0: pattern->SetExtend(gfxPattern::EXTEND_REPEAT); michael@0: pattern->SetFilter(aFilter); michael@0: } else { michael@0: GraphicsFilter filter = aFilter; michael@0: if (aContext->CurrentMatrix().HasOnlyIntegerTranslation() && michael@0: aTransform.HasOnlyIntegerTranslation()) michael@0: { michael@0: // If we only have integer translation, no special filtering needs to michael@0: // happen and we explicitly use FILTER_FAST. This is fast for some michael@0: // backends. michael@0: filter = GraphicsFilter::FILTER_FAST; michael@0: } michael@0: nsRefPtr currentTarget = aContext->CurrentSurface(); michael@0: gfxMatrix deviceSpaceToImageSpace = michael@0: DeviceToImageTransform(aContext, aTransform); michael@0: PreparePatternForUntiledDrawing(pattern, deviceSpaceToImageSpace, michael@0: currentTarget, filter); michael@0: } michael@0: pattern->SetMatrix(gfxMatrix(aTransform).Multiply(mTransform)); michael@0: aContext->NewPath(); michael@0: aContext->SetPattern(pattern); michael@0: aContext->Rectangle(aFillRect); michael@0: aContext->Fill(); michael@0: // clear the pattern so that the snapshot is released before the michael@0: // drawable is destroyed michael@0: aContext->SetDeviceColor(gfxRGBA(0.0, 0.0, 0.0, 0.0)); michael@0: return true; michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxSurfaceDrawable::GetAsImageSurface() michael@0: { michael@0: if (mDrawTarget || mSourceSurface) { michael@0: // TODO: Find a way to implement this. The caller really wants a 'sub-image' of michael@0: // the original, without having to do a copy. GetDataSurface() might just copy, michael@0: // which isn't useful. michael@0: return nullptr; michael@0: michael@0: } michael@0: return mSurface->GetAsImageSurface(); michael@0: } michael@0: michael@0: gfxCallbackDrawable::gfxCallbackDrawable(gfxDrawingCallback* aCallback, michael@0: const gfxIntSize aSize) michael@0: : gfxDrawable(aSize) michael@0: , mCallback(aCallback) michael@0: { michael@0: } michael@0: michael@0: already_AddRefed michael@0: gfxCallbackDrawable::MakeSurfaceDrawable(const GraphicsFilter aFilter) michael@0: { michael@0: SurfaceFormat format = michael@0: gfxPlatform::GetPlatform()->Optimal2DFormatForContent(gfxContentType::COLOR_ALPHA); michael@0: RefPtr dt = michael@0: gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(mSize.ToIntSize(), michael@0: format); michael@0: if (!dt) michael@0: return nullptr; michael@0: michael@0: nsRefPtr ctx = new gfxContext(dt); michael@0: Draw(ctx, gfxRect(0, 0, mSize.width, mSize.height), false, aFilter); michael@0: michael@0: RefPtr surface = dt->Snapshot(); michael@0: nsRefPtr drawable = new gfxSurfaceDrawable(surface, mSize); michael@0: return drawable.forget(); michael@0: } michael@0: michael@0: bool michael@0: gfxCallbackDrawable::Draw(gfxContext* aContext, michael@0: const gfxRect& aFillRect, michael@0: bool aRepeat, michael@0: const GraphicsFilter& aFilter, michael@0: const gfxMatrix& aTransform) michael@0: { michael@0: if (aRepeat && !mSurfaceDrawable) { michael@0: mSurfaceDrawable = MakeSurfaceDrawable(aFilter); michael@0: } michael@0: michael@0: if (mSurfaceDrawable) michael@0: return mSurfaceDrawable->Draw(aContext, aFillRect, aRepeat, aFilter, michael@0: aTransform); michael@0: michael@0: if (mCallback) michael@0: return (*mCallback)(aContext, aFillRect, aFilter, aTransform); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: gfxPatternDrawable::gfxPatternDrawable(gfxPattern* aPattern, michael@0: const gfxIntSize aSize) michael@0: : gfxDrawable(aSize) michael@0: , mPattern(aPattern) michael@0: { michael@0: } michael@0: michael@0: gfxPatternDrawable::~gfxPatternDrawable() michael@0: { michael@0: } michael@0: michael@0: class DrawingCallbackFromDrawable : public gfxDrawingCallback { michael@0: public: michael@0: DrawingCallbackFromDrawable(gfxDrawable* aDrawable) michael@0: : mDrawable(aDrawable) { michael@0: NS_ASSERTION(aDrawable, "aDrawable is null!"); michael@0: } michael@0: michael@0: virtual ~DrawingCallbackFromDrawable() {} michael@0: michael@0: virtual bool operator()(gfxContext* aContext, michael@0: const gfxRect& aFillRect, michael@0: const GraphicsFilter& aFilter, michael@0: const gfxMatrix& aTransform = gfxMatrix()) michael@0: { michael@0: return mDrawable->Draw(aContext, aFillRect, false, aFilter, michael@0: aTransform); michael@0: } michael@0: private: michael@0: nsRefPtr mDrawable; michael@0: }; michael@0: michael@0: already_AddRefed michael@0: gfxPatternDrawable::MakeCallbackDrawable() michael@0: { michael@0: nsRefPtr callback = michael@0: new DrawingCallbackFromDrawable(this); michael@0: nsRefPtr callbackDrawable = michael@0: new gfxCallbackDrawable(callback, mSize); michael@0: return callbackDrawable.forget(); michael@0: } michael@0: michael@0: bool michael@0: gfxPatternDrawable::Draw(gfxContext* aContext, michael@0: const gfxRect& aFillRect, michael@0: bool aRepeat, michael@0: const GraphicsFilter& aFilter, michael@0: const gfxMatrix& aTransform) michael@0: { michael@0: if (!mPattern) michael@0: return false; michael@0: michael@0: if (aRepeat) { michael@0: // We can't use mPattern directly: We want our repeated tiles to have michael@0: // the size mSize, which might not be the case in mPattern. michael@0: // So we need to draw mPattern into a surface of size mSize, create michael@0: // a pattern from the surface and draw that pattern. michael@0: // gfxCallbackDrawable and gfxSurfaceDrawable already know how to do michael@0: // those things, so we use them here. Drawing mPattern into the surface michael@0: // will happen through this Draw() method with aRepeat = false. michael@0: nsRefPtr callbackDrawable = MakeCallbackDrawable(); michael@0: return callbackDrawable->Draw(aContext, aFillRect, true, aFilter, michael@0: aTransform); michael@0: } michael@0: michael@0: aContext->NewPath(); michael@0: gfxMatrix oldMatrix = mPattern->GetMatrix(); michael@0: mPattern->SetMatrix(gfxMatrix(aTransform).Multiply(oldMatrix)); michael@0: aContext->SetPattern(mPattern); michael@0: aContext->Rectangle(aFillRect); michael@0: aContext->Fill(); michael@0: mPattern->SetMatrix(oldMatrix); michael@0: return true; michael@0: }