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