mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 00:25:27 +00:00
310 lines
11 KiB
C++
310 lines
11 KiB
C++
/* -*- 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 "gfxImageSurface.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<gfxASurface> 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<gfxXlibSurface*>(currentTarget)->IsPadSlow()) {
|
|
bool isDownscale =
|
|
aDeviceToImage._11 >= 1.0 && aDeviceToImage._22 >= 1.0 &&
|
|
aDeviceToImage._21 == 0.0 && aDeviceToImage._12 == 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<gfxPattern> pattern;
|
|
if (mDrawTarget) {
|
|
if (aContext->IsCairo()) {
|
|
nsRefPtr<gfxASurface> source =
|
|
gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mDrawTarget);
|
|
pattern = new gfxPattern(source);
|
|
} else {
|
|
RefPtr<SourceSurface> 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<gfxASurface> 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<gfxImageSurface>
|
|
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<gfxSurfaceDrawable>
|
|
gfxCallbackDrawable::MakeSurfaceDrawable(const GraphicsFilter aFilter)
|
|
{
|
|
SurfaceFormat format =
|
|
gfxPlatform::GetPlatform()->Optimal2DFormatForContent(gfxContentType::COLOR_ALPHA);
|
|
RefPtr<DrawTarget> dt =
|
|
gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(mSize.ToIntSize(),
|
|
format);
|
|
if (!dt)
|
|
return nullptr;
|
|
|
|
nsRefPtr<gfxContext> ctx = new gfxContext(dt);
|
|
Draw(ctx, gfxRect(0, 0, mSize.width, mSize.height), false, aFilter);
|
|
|
|
RefPtr<SourceSurface> surface = dt->Snapshot();
|
|
nsRefPtr<gfxSurfaceDrawable> 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<gfxDrawable> mDrawable;
|
|
};
|
|
|
|
already_AddRefed<gfxCallbackDrawable>
|
|
gfxPatternDrawable::MakeCallbackDrawable()
|
|
{
|
|
nsRefPtr<gfxDrawingCallback> callback =
|
|
new DrawingCallbackFromDrawable(this);
|
|
nsRefPtr<gfxCallbackDrawable> 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<gfxCallbackDrawable> 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;
|
|
}
|