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