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 <windows.h>
7
8 #include "nsMathUtils.h"
9
10 #include "gfxWindowsNativeDrawing.h"
11 #include "gfxWindowsSurface.h"
12 #include "gfxAlphaRecovery.h"
13 #include "gfxPattern.h"
14 #include "mozilla/gfx/2D.h"
15 #include "mozilla/gfx/Helpers.h"
16 #include "gfx2DGlue.h"
17
18 #include "cairo.h"
19 #include "cairo-win32.h"
20
21 using namespace mozilla;
22 using namespace mozilla::gfx;
23
24 enum {
25 RENDER_STATE_INIT,
26
27 RENDER_STATE_NATIVE_DRAWING,
28 RENDER_STATE_NATIVE_DRAWING_DONE,
29
30 RENDER_STATE_ALPHA_RECOVERY_BLACK,
31 RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE,
32 RENDER_STATE_ALPHA_RECOVERY_WHITE,
33 RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE,
34
35 RENDER_STATE_DONE
36 };
37
gfxWindowsNativeDrawing(gfxContext * ctx,const gfxRect & nativeRect,uint32_t nativeDrawFlags)38 gfxWindowsNativeDrawing::gfxWindowsNativeDrawing(gfxContext* ctx,
39 const gfxRect& nativeRect,
40 uint32_t nativeDrawFlags)
41 : mContext(ctx), mNativeRect(nativeRect), mNativeDrawFlags(nativeDrawFlags), mRenderState(RENDER_STATE_INIT)
42 {
43 }
44
45 HDC
BeginNativeDrawing()46 gfxWindowsNativeDrawing::BeginNativeDrawing()
47 {
48 if (mRenderState == RENDER_STATE_INIT) {
49 RefPtr<gfxASurface> surf;
50 DrawTarget* drawTarget = mContext->GetDrawTarget();
51 cairo_t* cairo = nullptr;
52 if (drawTarget->GetBackendType() == BackendType::CAIRO) {
53 cairo = static_cast<cairo_t*>
54 (drawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
55 if (cairo) {
56 cairo_surface_t* s = cairo_get_group_target(cairo);
57 if (s) {
58 mDeviceOffset = mContext->GetDeviceOffset();
59 double sdx, sdy;
60 cairo_surface_get_device_offset(s, &sdx, &sdy);
61 mDeviceOffset.x -= sdx;
62 mDeviceOffset.y -= sdy;
63 surf = gfxASurface::Wrap(s);
64 }
65 }
66 }
67
68 if (surf && surf->CairoStatus() != 0)
69 return nullptr;
70
71 gfxMatrix m = mContext->CurrentMatrix();
72 if (!m.HasNonTranslation())
73 mTransformType = TRANSLATION_ONLY;
74 else if (m.HasNonAxisAlignedTransform())
75 mTransformType = COMPLEX;
76 else
77 mTransformType = AXIS_ALIGNED_SCALE;
78
79 // if this is a native win32 surface, we don't have to
80 // redirect rendering to our own HDC; in some cases,
81 // we may be able to use the HDC from the surface directly.
82 if (surf &&
83 ((surf->GetType() == gfxSurfaceType::Win32 ||
84 surf->GetType() == gfxSurfaceType::Win32Printing) &&
85 (surf->GetContentType() == gfxContentType::COLOR ||
86 (surf->GetContentType() == gfxContentType::COLOR_ALPHA &&
87 (mNativeDrawFlags & CAN_DRAW_TO_COLOR_ALPHA)))))
88 {
89 // grab the DC. This can fail if there is a complex clipping path,
90 // in which case we'll have to fall back.
91 mWinSurface = static_cast<gfxWindowsSurface*>(static_cast<gfxASurface*>(surf.get()));
92 mDC = cairo_win32_get_dc_with_clip(cairo);
93
94 if (mDC) {
95 if (mTransformType == TRANSLATION_ONLY) {
96 mRenderState = RENDER_STATE_NATIVE_DRAWING;
97
98 mTranslation = m.GetTranslation();
99 } else if (((mTransformType == AXIS_ALIGNED_SCALE)
100 && (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) ||
101 (mNativeDrawFlags & CAN_COMPLEX_TRANSFORM))
102 {
103 mWorldTransform.eM11 = (FLOAT) m._11;
104 mWorldTransform.eM12 = (FLOAT) m._12;
105 mWorldTransform.eM21 = (FLOAT) m._21;
106 mWorldTransform.eM22 = (FLOAT) m._22;
107 mWorldTransform.eDx = (FLOAT) m._31;
108 mWorldTransform.eDy = (FLOAT) m._32;
109
110 mRenderState = RENDER_STATE_NATIVE_DRAWING;
111 }
112 }
113 }
114
115 // If we couldn't do native drawing, then we have to do two-buffer drawing
116 // and do alpha recovery
117 if (mRenderState == RENDER_STATE_INIT) {
118 mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK;
119
120 // We round out our native rect here, that way the snapping will
121 // happen correctly.
122 mNativeRect.RoundOut();
123
124 // we only do the scale bit if we can do an axis aligned
125 // scale; otherwise we scale (if necessary) after
126 // rendering with cairo. Note that if we're doing alpha recovery,
127 // we cannot do a full complex transform with win32 (I mean, we could, but
128 // it would require more code that's not here.)
129 if (mTransformType == TRANSLATION_ONLY || !(mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) {
130 mScale = gfxSize(1.0, 1.0);
131
132 // Add 1 to the surface size; it's guaranteed to not be incorrect,
133 // and it fixes bug 382458
134 // There's probably a better fix, but I haven't figured out
135 // the root cause of the problem.
136 mTempSurfaceSize =
137 IntSize((int32_t) ceil(mNativeRect.Width() + 1),
138 (int32_t) ceil(mNativeRect.Height() + 1));
139 } else {
140 // figure out the scale factors
141 mScale = m.ScaleFactors(true);
142
143 mWorldTransform.eM11 = (FLOAT) mScale.width;
144 mWorldTransform.eM12 = 0.0f;
145 mWorldTransform.eM21 = 0.0f;
146 mWorldTransform.eM22 = (FLOAT) mScale.height;
147 mWorldTransform.eDx = 0.0f;
148 mWorldTransform.eDy = 0.0f;
149
150 // See comment above about "+1"
151 mTempSurfaceSize =
152 IntSize((int32_t) ceil(mNativeRect.Width() * mScale.width + 1),
153 (int32_t) ceil(mNativeRect.Height() * mScale.height + 1));
154 }
155 }
156 }
157
158 if (mRenderState == RENDER_STATE_NATIVE_DRAWING) {
159 // we can just do native drawing directly to the context's surface
160
161 // do we need to use SetWorldTransform?
162 if (mTransformType != TRANSLATION_ONLY) {
163 SetGraphicsMode(mDC, GM_ADVANCED);
164 GetWorldTransform(mDC, &mOldWorldTransform);
165 SetWorldTransform(mDC, &mWorldTransform);
166 }
167 GetViewportOrgEx(mDC, &mOrigViewportOrigin);
168 SetViewportOrgEx(mDC,
169 mOrigViewportOrigin.x - (int)mDeviceOffset.x,
170 mOrigViewportOrigin.y - (int)mDeviceOffset.y,
171 nullptr);
172
173 return mDC;
174 } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK ||
175 mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE)
176 {
177 // we're going to use mWinSurface to create our temporary surface here
178
179 // get us a RGB24 DIB; DIB is important, because
180 // we can later call GetImageSurface on it.
181 mWinSurface = new gfxWindowsSurface(mTempSurfaceSize);
182 mDC = mWinSurface->GetDC();
183
184 RECT r = { 0, 0, mTempSurfaceSize.width, mTempSurfaceSize.height };
185 if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK)
186 FillRect(mDC, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));
187 else
188 FillRect(mDC, &r, (HBRUSH)GetStockObject(WHITE_BRUSH));
189
190 if ((mTransformType != TRANSLATION_ONLY) &&
191 (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE))
192 {
193 SetGraphicsMode(mDC, GM_ADVANCED);
194 SetWorldTransform(mDC, &mWorldTransform);
195 }
196
197 return mDC;
198 } else {
199 NS_ERROR("Bogus render state!");
200 return nullptr;
201 }
202 }
203
204 bool
ShouldRenderAgain()205 gfxWindowsNativeDrawing::ShouldRenderAgain()
206 {
207 switch (mRenderState) {
208 case RENDER_STATE_NATIVE_DRAWING_DONE:
209 return false;
210
211 case RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE:
212 mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE;
213 return true;
214
215 case RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE:
216 return false;
217
218 default:
219 NS_ERROR("Invalid RenderState in gfxWindowsNativeDrawing::ShouldRenderAgain");
220 break;
221 }
222
223 return false;
224 }
225
226 void
EndNativeDrawing()227 gfxWindowsNativeDrawing::EndNativeDrawing()
228 {
229 if (mRenderState == RENDER_STATE_NATIVE_DRAWING) {
230 // we drew directly to the HDC in the context; undo our changes
231 SetViewportOrgEx(mDC, mOrigViewportOrigin.x, mOrigViewportOrigin.y, nullptr);
232
233 if (mTransformType != TRANSLATION_ONLY)
234 SetWorldTransform(mDC, &mOldWorldTransform);
235
236 mWinSurface->MarkDirty();
237
238 mRenderState = RENDER_STATE_NATIVE_DRAWING_DONE;
239 } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK) {
240 mBlackSurface = mWinSurface;
241 mWinSurface = nullptr;
242
243 mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE;
244 } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) {
245 mWhiteSurface = mWinSurface;
246 mWinSurface = nullptr;
247
248 mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE;
249 } else {
250 NS_ERROR("Invalid RenderState in gfxWindowsNativeDrawing::EndNativeDrawing");
251 }
252 }
253
254 void
PaintToContext()255 gfxWindowsNativeDrawing::PaintToContext()
256 {
257 if (mRenderState == RENDER_STATE_NATIVE_DRAWING_DONE) {
258 // nothing to do, it already went to the context
259 mRenderState = RENDER_STATE_DONE;
260 } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE) {
261 RefPtr<gfxImageSurface> black = mBlackSurface->GetAsImageSurface();
262 RefPtr<gfxImageSurface> white = mWhiteSurface->GetAsImageSurface();
263 if (!gfxAlphaRecovery::RecoverAlpha(black, white)) {
264 NS_ERROR("Alpha recovery failure");
265 return;
266 }
267 RefPtr<DataSourceSurface> source =
268 Factory::CreateWrappingDataSourceSurface(black->Data(),
269 black->Stride(),
270 black->GetSize(),
271 SurfaceFormat::B8G8R8A8);
272 {
273 DrawTarget* dt = mContext->GetDrawTarget();
274 AutoRestoreTransform autoRestoreTransform(dt);
275
276 Matrix newTransform = dt->GetTransform();
277 newTransform.PreTranslate(ToPoint(mNativeRect.TopLeft()));
278 dt->SetTransform(newTransform);
279
280 Rect rect(Point(0.0, 0.0), ToSize(mNativeRect.Size()));
281 Matrix m = Matrix::Scaling(1.0 / mScale.width, 1.0 / mScale.height);
282 SamplingFilter filter = (mNativeDrawFlags & DO_NEAREST_NEIGHBOR_FILTERING)
283 ? SamplingFilter::LINEAR
284 : SamplingFilter::GOOD;
285 SurfacePattern pat(source, ExtendMode::CLAMP, m, filter);
286 dt->FillRect(rect, pat);
287 }
288
289 mRenderState = RENDER_STATE_DONE;
290 } else {
291 NS_ERROR("Invalid RenderState in gfxWindowsNativeDrawing::PaintToContext");
292 }
293 }
294
295 void
TransformToNativeRect(const gfxRect & r,RECT & rout)296 gfxWindowsNativeDrawing::TransformToNativeRect(const gfxRect& r,
297 RECT& rout)
298 {
299 /* If we're doing native drawing, then we're still in the coordinate space
300 * of the context; otherwise, we're in our own little world,
301 * relative to the passed-in nativeRect.
302 */
303
304 gfxRect roundedRect(r);
305
306 if (mRenderState == RENDER_STATE_NATIVE_DRAWING) {
307 if (mTransformType == TRANSLATION_ONLY) {
308 roundedRect.MoveBy(mTranslation);
309 }
310 } else {
311 roundedRect.MoveBy(-mNativeRect.TopLeft());
312 }
313
314 roundedRect.Round();
315
316 rout.left = LONG(roundedRect.X());
317 rout.right = LONG(roundedRect.XMost());
318 rout.top = LONG(roundedRect.Y());
319 rout.bottom = LONG(roundedRect.YMost());
320 }
321