michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "nsMathUtils.h" michael@0: michael@0: #include "gfxWindowsNativeDrawing.h" michael@0: #include "gfxWindowsSurface.h" michael@0: #include "gfxAlphaRecovery.h" michael@0: #include "gfxPattern.h" michael@0: #include "mozilla/gfx/2D.h" michael@0: michael@0: enum { michael@0: RENDER_STATE_INIT, michael@0: michael@0: RENDER_STATE_NATIVE_DRAWING, michael@0: RENDER_STATE_NATIVE_DRAWING_DONE, michael@0: michael@0: RENDER_STATE_ALPHA_RECOVERY_BLACK, michael@0: RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE, michael@0: RENDER_STATE_ALPHA_RECOVERY_WHITE, michael@0: RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE, michael@0: michael@0: RENDER_STATE_DONE michael@0: }; michael@0: michael@0: gfxWindowsNativeDrawing::gfxWindowsNativeDrawing(gfxContext* ctx, michael@0: const gfxRect& nativeRect, michael@0: uint32_t nativeDrawFlags) michael@0: : mContext(ctx), mNativeRect(nativeRect), mNativeDrawFlags(nativeDrawFlags), mRenderState(RENDER_STATE_INIT) michael@0: { michael@0: } michael@0: michael@0: HDC michael@0: gfxWindowsNativeDrawing::BeginNativeDrawing() michael@0: { michael@0: if (mRenderState == RENDER_STATE_INIT) { michael@0: nsRefPtr surf; michael@0: michael@0: if (mContext->GetCairo()) { michael@0: surf = mContext->CurrentSurface(&mDeviceOffset.x, &mDeviceOffset.y); michael@0: } michael@0: michael@0: if (surf && surf->CairoStatus()) michael@0: return nullptr; michael@0: michael@0: gfxMatrix m = mContext->CurrentMatrix(); michael@0: if (!m.HasNonTranslation()) michael@0: mTransformType = TRANSLATION_ONLY; michael@0: else if (m.HasNonAxisAlignedTransform()) michael@0: mTransformType = COMPLEX; michael@0: else michael@0: mTransformType = AXIS_ALIGNED_SCALE; michael@0: michael@0: // if this is a native win32 surface, we don't have to michael@0: // redirect rendering to our own HDC; in some cases, michael@0: // we may be able to use the HDC from the surface directly. michael@0: if (surf && michael@0: ((surf->GetType() == gfxSurfaceType::Win32 || michael@0: surf->GetType() == gfxSurfaceType::Win32Printing) && michael@0: (surf->GetContentType() == gfxContentType::COLOR || michael@0: (surf->GetContentType() == gfxContentType::COLOR_ALPHA && michael@0: (mNativeDrawFlags & CAN_DRAW_TO_COLOR_ALPHA))))) michael@0: { michael@0: // grab the DC. This can fail if there is a complex clipping path, michael@0: // in which case we'll have to fall back. michael@0: mWinSurface = static_cast(static_cast(surf.get())); michael@0: mDC = mWinSurface->GetDCWithClip(mContext); michael@0: michael@0: if (mDC) { michael@0: if (mTransformType == TRANSLATION_ONLY) { michael@0: mRenderState = RENDER_STATE_NATIVE_DRAWING; michael@0: michael@0: mTranslation = m.GetTranslation(); michael@0: } else if (((mTransformType == AXIS_ALIGNED_SCALE) michael@0: && (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) || michael@0: (mNativeDrawFlags & CAN_COMPLEX_TRANSFORM)) michael@0: { michael@0: mWorldTransform.eM11 = (FLOAT) m.xx; michael@0: mWorldTransform.eM12 = (FLOAT) m.yx; michael@0: mWorldTransform.eM21 = (FLOAT) m.xy; michael@0: mWorldTransform.eM22 = (FLOAT) m.yy; michael@0: mWorldTransform.eDx = (FLOAT) m.x0; michael@0: mWorldTransform.eDy = (FLOAT) m.y0; michael@0: michael@0: mRenderState = RENDER_STATE_NATIVE_DRAWING; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If we couldn't do native drawing, then we have to do two-buffer drawing michael@0: // and do alpha recovery michael@0: if (mRenderState == RENDER_STATE_INIT) { michael@0: mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK; michael@0: michael@0: // We round out our native rect here, that way the snapping will michael@0: // happen correctly. michael@0: mNativeRect.RoundOut(); michael@0: michael@0: // we only do the scale bit if we can do an axis aligned michael@0: // scale; otherwise we scale (if necessary) after michael@0: // rendering with cairo. Note that if we're doing alpha recovery, michael@0: // we cannot do a full complex transform with win32 (I mean, we could, but michael@0: // it would require more code that's not here.) michael@0: if (mTransformType == TRANSLATION_ONLY || !(mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) { michael@0: mScale = gfxSize(1.0, 1.0); michael@0: michael@0: // Add 1 to the surface size; it's guaranteed to not be incorrect, michael@0: // and it fixes bug 382458 michael@0: // There's probably a better fix, but I haven't figured out michael@0: // the root cause of the problem. michael@0: mTempSurfaceSize = michael@0: gfxIntSize((int32_t) ceil(mNativeRect.Width() + 1), michael@0: (int32_t) ceil(mNativeRect.Height() + 1)); michael@0: } else { michael@0: // figure out the scale factors michael@0: mScale = m.ScaleFactors(true); michael@0: michael@0: mWorldTransform.eM11 = (FLOAT) mScale.width; michael@0: mWorldTransform.eM12 = 0.0f; michael@0: mWorldTransform.eM21 = 0.0f; michael@0: mWorldTransform.eM22 = (FLOAT) mScale.height; michael@0: mWorldTransform.eDx = 0.0f; michael@0: mWorldTransform.eDy = 0.0f; michael@0: michael@0: // See comment above about "+1" michael@0: mTempSurfaceSize = michael@0: gfxIntSize((int32_t) ceil(mNativeRect.Width() * mScale.width + 1), michael@0: (int32_t) ceil(mNativeRect.Height() * mScale.height + 1)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { michael@0: // we can just do native drawing directly to the context's surface michael@0: michael@0: // do we need to use SetWorldTransform? michael@0: if (mTransformType != TRANSLATION_ONLY) { michael@0: SetGraphicsMode(mDC, GM_ADVANCED); michael@0: GetWorldTransform(mDC, &mOldWorldTransform); michael@0: SetWorldTransform(mDC, &mWorldTransform); michael@0: } michael@0: GetViewportOrgEx(mDC, &mOrigViewportOrigin); michael@0: SetViewportOrgEx(mDC, michael@0: mOrigViewportOrigin.x + (int)mDeviceOffset.x, michael@0: mOrigViewportOrigin.y + (int)mDeviceOffset.y, michael@0: nullptr); michael@0: michael@0: return mDC; michael@0: } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK || michael@0: mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) michael@0: { michael@0: // we're going to use mWinSurface to create our temporary surface here michael@0: michael@0: // get us a RGB24 DIB; DIB is important, because michael@0: // we can later call GetImageSurface on it. michael@0: mWinSurface = new gfxWindowsSurface(mTempSurfaceSize); michael@0: mDC = mWinSurface->GetDC(); michael@0: michael@0: RECT r = { 0, 0, mTempSurfaceSize.width, mTempSurfaceSize.height }; michael@0: if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK) michael@0: FillRect(mDC, &r, (HBRUSH)GetStockObject(BLACK_BRUSH)); michael@0: else michael@0: FillRect(mDC, &r, (HBRUSH)GetStockObject(WHITE_BRUSH)); michael@0: michael@0: if ((mTransformType != TRANSLATION_ONLY) && michael@0: (mNativeDrawFlags & CAN_AXIS_ALIGNED_SCALE)) michael@0: { michael@0: SetGraphicsMode(mDC, GM_ADVANCED); michael@0: SetWorldTransform(mDC, &mWorldTransform); michael@0: } michael@0: michael@0: return mDC; michael@0: } else { michael@0: NS_ERROR("Bogus render state!"); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxWindowsNativeDrawing::IsDoublePass() michael@0: { michael@0: if (!mContext->IsCairo() && michael@0: (mContext->GetDrawTarget()->GetType() != mozilla::gfx::BackendType::CAIRO || michael@0: mContext->GetDrawTarget()->IsDualDrawTarget())) { michael@0: return true; michael@0: } michael@0: michael@0: nsRefPtr surf = mContext->CurrentSurface(&mDeviceOffset.x, &mDeviceOffset.y); michael@0: if (!surf || surf->CairoStatus()) michael@0: return false; michael@0: if (surf->GetType() != gfxSurfaceType::Win32 && michael@0: surf->GetType() != gfxSurfaceType::Win32Printing) { michael@0: return true; michael@0: } michael@0: if ((surf->GetContentType() != gfxContentType::COLOR || michael@0: (surf->GetContentType() == gfxContentType::COLOR_ALPHA && michael@0: !(mNativeDrawFlags & CAN_DRAW_TO_COLOR_ALPHA)))) michael@0: return true; michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: gfxWindowsNativeDrawing::ShouldRenderAgain() michael@0: { michael@0: switch (mRenderState) { michael@0: case RENDER_STATE_NATIVE_DRAWING_DONE: michael@0: return false; michael@0: michael@0: case RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE: michael@0: mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE; michael@0: return true; michael@0: michael@0: case RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE: michael@0: return false; michael@0: michael@0: default: michael@0: NS_ERROR("Invalid RenderState in gfxWindowsNativeDrawing::ShouldRenderAgain"); michael@0: break; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: gfxWindowsNativeDrawing::EndNativeDrawing() michael@0: { michael@0: if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { michael@0: // we drew directly to the HDC in the context; undo our changes michael@0: SetViewportOrgEx(mDC, mOrigViewportOrigin.x, mOrigViewportOrigin.y, nullptr); michael@0: michael@0: if (mTransformType != TRANSLATION_ONLY) michael@0: SetWorldTransform(mDC, &mOldWorldTransform); michael@0: michael@0: mWinSurface->MarkDirty(); michael@0: michael@0: mRenderState = RENDER_STATE_NATIVE_DRAWING_DONE; michael@0: } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_BLACK) { michael@0: mBlackSurface = mWinSurface; michael@0: mWinSurface = nullptr; michael@0: michael@0: mRenderState = RENDER_STATE_ALPHA_RECOVERY_BLACK_DONE; michael@0: } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE) { michael@0: mWhiteSurface = mWinSurface; michael@0: mWinSurface = nullptr; michael@0: michael@0: mRenderState = RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE; michael@0: } else { michael@0: NS_ERROR("Invalid RenderState in gfxWindowsNativeDrawing::EndNativeDrawing"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxWindowsNativeDrawing::PaintToContext() michael@0: { michael@0: if (mRenderState == RENDER_STATE_NATIVE_DRAWING_DONE) { michael@0: // nothing to do, it already went to the context michael@0: mRenderState = RENDER_STATE_DONE; michael@0: } else if (mRenderState == RENDER_STATE_ALPHA_RECOVERY_WHITE_DONE) { michael@0: nsRefPtr black = mBlackSurface->GetAsImageSurface(); michael@0: nsRefPtr white = mWhiteSurface->GetAsImageSurface(); michael@0: if (!gfxAlphaRecovery::RecoverAlpha(black, white)) { michael@0: NS_ERROR("Alpha recovery failure"); michael@0: return; michael@0: } michael@0: nsRefPtr alphaSurface = michael@0: new gfxImageSurface(black->Data(), black->GetSize(), michael@0: black->Stride(), michael@0: gfxImageFormat::ARGB32); michael@0: michael@0: mContext->Save(); michael@0: mContext->Translate(mNativeRect.TopLeft()); michael@0: mContext->NewPath(); michael@0: mContext->Rectangle(gfxRect(gfxPoint(0.0, 0.0), mNativeRect.Size())); michael@0: michael@0: nsRefPtr pat = new gfxPattern(alphaSurface); michael@0: michael@0: gfxMatrix m; michael@0: m.Scale(mScale.width, mScale.height); michael@0: pat->SetMatrix(m); michael@0: michael@0: if (mNativeDrawFlags & DO_NEAREST_NEIGHBOR_FILTERING) michael@0: pat->SetFilter(GraphicsFilter::FILTER_FAST); michael@0: michael@0: pat->SetExtend(gfxPattern::EXTEND_PAD); michael@0: mContext->SetPattern(pat); michael@0: mContext->Fill(); michael@0: mContext->Restore(); michael@0: michael@0: mRenderState = RENDER_STATE_DONE; michael@0: } else { michael@0: NS_ERROR("Invalid RenderState in gfxWindowsNativeDrawing::PaintToContext"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: gfxWindowsNativeDrawing::TransformToNativeRect(const gfxRect& r, michael@0: RECT& rout) michael@0: { michael@0: /* If we're doing native drawing, then we're still in the coordinate space michael@0: * of the context; otherwise, we're in our own little world, michael@0: * relative to the passed-in nativeRect. michael@0: */ michael@0: michael@0: gfxRect roundedRect(r); michael@0: michael@0: if (mRenderState == RENDER_STATE_NATIVE_DRAWING) { michael@0: if (mTransformType == TRANSLATION_ONLY) { michael@0: roundedRect.MoveBy(mTranslation); michael@0: } michael@0: } else { michael@0: roundedRect.MoveBy(-mNativeRect.TopLeft()); michael@0: } michael@0: michael@0: roundedRect.Round(); michael@0: michael@0: rout.left = LONG(roundedRect.X()); michael@0: rout.right = LONG(roundedRect.XMost()); michael@0: rout.top = LONG(roundedRect.Y()); michael@0: rout.bottom = LONG(roundedRect.YMost()); michael@0: }