|
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
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 |
|
6 #include "gfxDrawable.h" |
|
7 #include "gfxASurface.h" |
|
8 #include "gfxContext.h" |
|
9 #include "gfxImageSurface.h" |
|
10 #include "gfxPlatform.h" |
|
11 #include "gfxColor.h" |
|
12 #ifdef MOZ_X11 |
|
13 #include "cairo.h" |
|
14 #include "gfxXlibSurface.h" |
|
15 #endif |
|
16 |
|
17 using namespace mozilla; |
|
18 using namespace mozilla::gfx; |
|
19 |
|
20 gfxSurfaceDrawable::gfxSurfaceDrawable(gfxASurface* aSurface, |
|
21 const gfxIntSize aSize, |
|
22 const gfxMatrix aTransform) |
|
23 : gfxDrawable(aSize) |
|
24 , mSurface(aSurface) |
|
25 , mTransform(aTransform) |
|
26 { |
|
27 } |
|
28 |
|
29 gfxSurfaceDrawable::gfxSurfaceDrawable(DrawTarget* aDrawTarget, |
|
30 const gfxIntSize aSize, |
|
31 const gfxMatrix aTransform) |
|
32 : gfxDrawable(aSize) |
|
33 , mDrawTarget(aDrawTarget) |
|
34 , mTransform(aTransform) |
|
35 { |
|
36 } |
|
37 |
|
38 gfxSurfaceDrawable::gfxSurfaceDrawable(SourceSurface* aSurface, |
|
39 const gfxIntSize aSize, |
|
40 const gfxMatrix aTransform) |
|
41 : gfxDrawable(aSize) |
|
42 , mSourceSurface(aSurface) |
|
43 , mTransform(aTransform) |
|
44 { |
|
45 } |
|
46 |
|
47 static gfxMatrix |
|
48 DeviceToImageTransform(gfxContext* aContext, |
|
49 const gfxMatrix& aUserSpaceToImageSpace) |
|
50 { |
|
51 gfxFloat deviceX, deviceY; |
|
52 nsRefPtr<gfxASurface> currentTarget = |
|
53 aContext->CurrentSurface(&deviceX, &deviceY); |
|
54 gfxMatrix currentMatrix = aContext->CurrentMatrix(); |
|
55 gfxMatrix deviceToUser = gfxMatrix(currentMatrix).Invert(); |
|
56 deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY)); |
|
57 return gfxMatrix(deviceToUser).Multiply(aUserSpaceToImageSpace); |
|
58 } |
|
59 |
|
60 static void |
|
61 PreparePatternForUntiledDrawing(gfxPattern* aPattern, |
|
62 const gfxMatrix& aDeviceToImage, |
|
63 gfxASurface *currentTarget, |
|
64 const GraphicsFilter aDefaultFilter) |
|
65 { |
|
66 if (!currentTarget) { |
|
67 // This happens if we're dealing with an Azure target. |
|
68 aPattern->SetExtend(gfxPattern::EXTEND_PAD); |
|
69 aPattern->SetFilter(aDefaultFilter); |
|
70 return; |
|
71 } |
|
72 |
|
73 // In theory we can handle this using cairo's EXTEND_PAD, |
|
74 // but implementation limitations mean we have to consult |
|
75 // the surface type. |
|
76 switch (currentTarget->GetType()) { |
|
77 |
|
78 #ifdef MOZ_X11 |
|
79 case gfxSurfaceType::Xlib: |
|
80 { |
|
81 // See bugs 324698, 422179, and 468496. This is a workaround for |
|
82 // XRender's RepeatPad not being implemented correctly on old X |
|
83 // servers. |
|
84 // |
|
85 // In this situation, cairo avoids XRender and instead reads back |
|
86 // to perform EXTEND_PAD with pixman. This is too slow so we |
|
87 // avoid EXTEND_PAD and set the filter to CAIRO_FILTER_FAST --- |
|
88 // otherwise, pixman's sampling will sample transparency for the |
|
89 // outside edges and we'll get blurry edges. |
|
90 // |
|
91 // But don't do this for simple downscales because it's horrible. |
|
92 // Downscaling means that device-space coordinates are |
|
93 // scaled *up* to find the image pixel coordinates. |
|
94 // |
|
95 // Cairo, and hence Gecko, can use RepeatPad on Xorg 1.7. We |
|
96 // enable EXTEND_PAD provided that we're running on a recent |
|
97 // enough X server. |
|
98 if (static_cast<gfxXlibSurface*>(currentTarget)->IsPadSlow()) { |
|
99 bool isDownscale = |
|
100 aDeviceToImage.xx >= 1.0 && aDeviceToImage.yy >= 1.0 && |
|
101 aDeviceToImage.xy == 0.0 && aDeviceToImage.yx == 0.0; |
|
102 |
|
103 GraphicsFilter filter = |
|
104 isDownscale ? aDefaultFilter : (const GraphicsFilter)GraphicsFilter::FILTER_FAST; |
|
105 aPattern->SetFilter(filter); |
|
106 |
|
107 // Use the default EXTEND_NONE |
|
108 break; |
|
109 } |
|
110 // else fall through to EXTEND_PAD and the default filter. |
|
111 } |
|
112 #endif |
|
113 |
|
114 default: |
|
115 // turn on EXTEND_PAD. |
|
116 // This is what we really want for all surface types, if the |
|
117 // implementation was universally good. |
|
118 aPattern->SetExtend(gfxPattern::EXTEND_PAD); |
|
119 aPattern->SetFilter(aDefaultFilter); |
|
120 break; |
|
121 } |
|
122 } |
|
123 |
|
124 bool |
|
125 gfxSurfaceDrawable::Draw(gfxContext* aContext, |
|
126 const gfxRect& aFillRect, |
|
127 bool aRepeat, |
|
128 const GraphicsFilter& aFilter, |
|
129 const gfxMatrix& aTransform) |
|
130 { |
|
131 nsRefPtr<gfxPattern> pattern; |
|
132 if (mDrawTarget) { |
|
133 if (aContext->IsCairo()) { |
|
134 nsRefPtr<gfxASurface> source = |
|
135 gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mDrawTarget); |
|
136 pattern = new gfxPattern(source); |
|
137 } else { |
|
138 RefPtr<SourceSurface> source = mDrawTarget->Snapshot(); |
|
139 pattern = new gfxPattern(source, Matrix()); |
|
140 } |
|
141 } else if (mSourceSurface) { |
|
142 pattern = new gfxPattern(mSourceSurface, Matrix()); |
|
143 } else { |
|
144 pattern = new gfxPattern(mSurface); |
|
145 } |
|
146 if (aRepeat) { |
|
147 pattern->SetExtend(gfxPattern::EXTEND_REPEAT); |
|
148 pattern->SetFilter(aFilter); |
|
149 } else { |
|
150 GraphicsFilter filter = aFilter; |
|
151 if (aContext->CurrentMatrix().HasOnlyIntegerTranslation() && |
|
152 aTransform.HasOnlyIntegerTranslation()) |
|
153 { |
|
154 // If we only have integer translation, no special filtering needs to |
|
155 // happen and we explicitly use FILTER_FAST. This is fast for some |
|
156 // backends. |
|
157 filter = GraphicsFilter::FILTER_FAST; |
|
158 } |
|
159 nsRefPtr<gfxASurface> currentTarget = aContext->CurrentSurface(); |
|
160 gfxMatrix deviceSpaceToImageSpace = |
|
161 DeviceToImageTransform(aContext, aTransform); |
|
162 PreparePatternForUntiledDrawing(pattern, deviceSpaceToImageSpace, |
|
163 currentTarget, filter); |
|
164 } |
|
165 pattern->SetMatrix(gfxMatrix(aTransform).Multiply(mTransform)); |
|
166 aContext->NewPath(); |
|
167 aContext->SetPattern(pattern); |
|
168 aContext->Rectangle(aFillRect); |
|
169 aContext->Fill(); |
|
170 // clear the pattern so that the snapshot is released before the |
|
171 // drawable is destroyed |
|
172 aContext->SetDeviceColor(gfxRGBA(0.0, 0.0, 0.0, 0.0)); |
|
173 return true; |
|
174 } |
|
175 |
|
176 already_AddRefed<gfxImageSurface> |
|
177 gfxSurfaceDrawable::GetAsImageSurface() |
|
178 { |
|
179 if (mDrawTarget || mSourceSurface) { |
|
180 // TODO: Find a way to implement this. The caller really wants a 'sub-image' of |
|
181 // the original, without having to do a copy. GetDataSurface() might just copy, |
|
182 // which isn't useful. |
|
183 return nullptr; |
|
184 |
|
185 } |
|
186 return mSurface->GetAsImageSurface(); |
|
187 } |
|
188 |
|
189 gfxCallbackDrawable::gfxCallbackDrawable(gfxDrawingCallback* aCallback, |
|
190 const gfxIntSize aSize) |
|
191 : gfxDrawable(aSize) |
|
192 , mCallback(aCallback) |
|
193 { |
|
194 } |
|
195 |
|
196 already_AddRefed<gfxSurfaceDrawable> |
|
197 gfxCallbackDrawable::MakeSurfaceDrawable(const GraphicsFilter aFilter) |
|
198 { |
|
199 SurfaceFormat format = |
|
200 gfxPlatform::GetPlatform()->Optimal2DFormatForContent(gfxContentType::COLOR_ALPHA); |
|
201 RefPtr<DrawTarget> dt = |
|
202 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(mSize.ToIntSize(), |
|
203 format); |
|
204 if (!dt) |
|
205 return nullptr; |
|
206 |
|
207 nsRefPtr<gfxContext> ctx = new gfxContext(dt); |
|
208 Draw(ctx, gfxRect(0, 0, mSize.width, mSize.height), false, aFilter); |
|
209 |
|
210 RefPtr<SourceSurface> surface = dt->Snapshot(); |
|
211 nsRefPtr<gfxSurfaceDrawable> drawable = new gfxSurfaceDrawable(surface, mSize); |
|
212 return drawable.forget(); |
|
213 } |
|
214 |
|
215 bool |
|
216 gfxCallbackDrawable::Draw(gfxContext* aContext, |
|
217 const gfxRect& aFillRect, |
|
218 bool aRepeat, |
|
219 const GraphicsFilter& aFilter, |
|
220 const gfxMatrix& aTransform) |
|
221 { |
|
222 if (aRepeat && !mSurfaceDrawable) { |
|
223 mSurfaceDrawable = MakeSurfaceDrawable(aFilter); |
|
224 } |
|
225 |
|
226 if (mSurfaceDrawable) |
|
227 return mSurfaceDrawable->Draw(aContext, aFillRect, aRepeat, aFilter, |
|
228 aTransform); |
|
229 |
|
230 if (mCallback) |
|
231 return (*mCallback)(aContext, aFillRect, aFilter, aTransform); |
|
232 |
|
233 return false; |
|
234 } |
|
235 |
|
236 gfxPatternDrawable::gfxPatternDrawable(gfxPattern* aPattern, |
|
237 const gfxIntSize aSize) |
|
238 : gfxDrawable(aSize) |
|
239 , mPattern(aPattern) |
|
240 { |
|
241 } |
|
242 |
|
243 gfxPatternDrawable::~gfxPatternDrawable() |
|
244 { |
|
245 } |
|
246 |
|
247 class DrawingCallbackFromDrawable : public gfxDrawingCallback { |
|
248 public: |
|
249 DrawingCallbackFromDrawable(gfxDrawable* aDrawable) |
|
250 : mDrawable(aDrawable) { |
|
251 NS_ASSERTION(aDrawable, "aDrawable is null!"); |
|
252 } |
|
253 |
|
254 virtual ~DrawingCallbackFromDrawable() {} |
|
255 |
|
256 virtual bool operator()(gfxContext* aContext, |
|
257 const gfxRect& aFillRect, |
|
258 const GraphicsFilter& aFilter, |
|
259 const gfxMatrix& aTransform = gfxMatrix()) |
|
260 { |
|
261 return mDrawable->Draw(aContext, aFillRect, false, aFilter, |
|
262 aTransform); |
|
263 } |
|
264 private: |
|
265 nsRefPtr<gfxDrawable> mDrawable; |
|
266 }; |
|
267 |
|
268 already_AddRefed<gfxCallbackDrawable> |
|
269 gfxPatternDrawable::MakeCallbackDrawable() |
|
270 { |
|
271 nsRefPtr<gfxDrawingCallback> callback = |
|
272 new DrawingCallbackFromDrawable(this); |
|
273 nsRefPtr<gfxCallbackDrawable> callbackDrawable = |
|
274 new gfxCallbackDrawable(callback, mSize); |
|
275 return callbackDrawable.forget(); |
|
276 } |
|
277 |
|
278 bool |
|
279 gfxPatternDrawable::Draw(gfxContext* aContext, |
|
280 const gfxRect& aFillRect, |
|
281 bool aRepeat, |
|
282 const GraphicsFilter& aFilter, |
|
283 const gfxMatrix& aTransform) |
|
284 { |
|
285 if (!mPattern) |
|
286 return false; |
|
287 |
|
288 if (aRepeat) { |
|
289 // We can't use mPattern directly: We want our repeated tiles to have |
|
290 // the size mSize, which might not be the case in mPattern. |
|
291 // So we need to draw mPattern into a surface of size mSize, create |
|
292 // a pattern from the surface and draw that pattern. |
|
293 // gfxCallbackDrawable and gfxSurfaceDrawable already know how to do |
|
294 // those things, so we use them here. Drawing mPattern into the surface |
|
295 // will happen through this Draw() method with aRepeat = false. |
|
296 nsRefPtr<gfxCallbackDrawable> callbackDrawable = MakeCallbackDrawable(); |
|
297 return callbackDrawable->Draw(aContext, aFillRect, true, aFilter, |
|
298 aTransform); |
|
299 } |
|
300 |
|
301 aContext->NewPath(); |
|
302 gfxMatrix oldMatrix = mPattern->GetMatrix(); |
|
303 mPattern->SetMatrix(gfxMatrix(aTransform).Multiply(oldMatrix)); |
|
304 aContext->SetPattern(mPattern); |
|
305 aContext->Rectangle(aFillRect); |
|
306 aContext->Fill(); |
|
307 mPattern->SetMatrix(oldMatrix); |
|
308 return true; |
|
309 } |