/* * Copyright 2014-2015, Haiku, Inc. * Distributed under the terms of the MIT License. * * Authors: * Adrien Destugues * Stephan Aßmus * Julian Harnath */ #include "AlphaMask.h" #include "AlphaMaskCache.h" #include "BitmapHWInterface.h" #include "BitmapManager.h" #include "Canvas.h" #include "DrawingEngine.h" #include "PictureBoundingBoxPlayer.h" #include "ServerBitmap.h" #include "ServerPicture.h" #include "Shape.h" #include "ShapePrivate.h" #include // #pragma mark - AlphaMask AlphaMask::AlphaMask(AlphaMask* previousMask, bool inverse) : fPreviousMask(previousMask), fBounds(), fClippedToCanvas(true), fCanvasOrigin(), fCanvasBounds(), fInverse(inverse), fBackgroundOpacity(0), fNextMaskCount(0), fInCache(false), fIndirectCacheReferences(0), fBits(NULL), fBuffer(), fMask(), fScanline(fMask) { recursive_lock_init(&fLock, "AlphaMask"); if (previousMask != NULL) atomic_add(&previousMask->fNextMaskCount, 1); _SetOutsideOpacity(); } AlphaMask::AlphaMask(AlphaMask* previousMask, AlphaMask* other) : fPreviousMask(previousMask), fBounds(other->fBounds), fClippedToCanvas(other->fClippedToCanvas), fCanvasOrigin(other->fCanvasOrigin), fCanvasBounds(other->fCanvasBounds), fInverse(other->fInverse), fBackgroundOpacity(other->fBackgroundOpacity), fNextMaskCount(0), fInCache(false), fIndirectCacheReferences(0), fBits(other->fBits), fBuffer(other->fBuffer), fMask(other->fMask), fScanline(fMask) { recursive_lock_init(&fLock, "AlphaMask"); fMask.attach(fBuffer); if (previousMask != NULL) atomic_add(&previousMask->fNextMaskCount, 1); _SetOutsideOpacity(); } AlphaMask::AlphaMask(uint8 backgroundOpacity) : fPreviousMask(), fBounds(), fClippedToCanvas(true), fCanvasOrigin(), fCanvasBounds(), fInverse(false), fBackgroundOpacity(backgroundOpacity), fNextMaskCount(0), fInCache(false), fIndirectCacheReferences(0), fBits(NULL), fBuffer(), fMask(), fScanline(fMask) { recursive_lock_init(&fLock, "AlphaMask"); _SetOutsideOpacity(); } AlphaMask::~AlphaMask() { if (fPreviousMask.IsSet()) atomic_add(&fPreviousMask->fNextMaskCount, -1); recursive_lock_destroy(&fLock); } IntPoint AlphaMask::SetCanvasGeometry(IntPoint origin, IntRect bounds) { RecursiveLocker locker(fLock); if (origin == fCanvasOrigin && bounds.Width() == fCanvasBounds.Width() && bounds.Height() == fCanvasBounds.Height()) return fCanvasOrigin; IntPoint oldOrigin = fCanvasOrigin; fCanvasOrigin = origin; IntRect oldBounds = fCanvasBounds; fCanvasBounds = IntRect(0, 0, bounds.Width(), bounds.Height()); if (fPreviousMask != NULL) fPreviousMask->SetCanvasGeometry(origin, bounds); if (fClippedToCanvas && (fCanvasBounds.Width() > oldBounds.Width() || fCanvasBounds.Height() > oldBounds.Height())) { // The canvas is now larger than before and we previously // drew the alpha mask clipped to the (old) bounds of the // canvas. So we now have to redraw the alpha mask with the // new size. _Generate(); } _AttachMaskToBuffer(); return oldOrigin; } size_t AlphaMask::BitmapSize() const { return fBits->BitsLength(); } ServerBitmap* AlphaMask::_CreateTemporaryBitmap(BRect bounds) const { BReference bitmap(new(std::nothrow) UtilityBitmap(bounds, B_RGBA32, 0), true); if (bitmap == NULL) return NULL; if (!bitmap->IsValid()) return NULL; memset(bitmap->Bits(), fBackgroundOpacity, bitmap->BitsLength()); return bitmap.Detach(); } void AlphaMask::_Generate() { RecursiveLocker locker(fLock); RecursiveLocker previousLocker; if (fPreviousMask != NULL) previousLocker.SetTo(fPreviousMask->fLock, false); ServerBitmap* const bitmap = _RenderSource(fCanvasBounds); BReference bitmapRef(bitmap, true); if (bitmap == NULL) { _SetNoClipping(); return; } fBits.SetTo(new(std::nothrow) UtilityBitmap(fBounds, B_GRAY8, 0), true); if (fBits == NULL) return; const int32 width = fBits->Width(); const int32 height = fBits->Height(); uint8* source = bitmap->Bits(); uint8* destination = fBits->Bits(); uint32 numPixels = width * height; if (fPreviousMask != NULL) { uint8 previousOutsideOpacity = fPreviousMask->OutsideOpacity(); if (fPreviousMask->fBounds.Intersects(fBounds)) { IntRect previousBounds(fBounds.OffsetByCopy( -fPreviousMask->fBounds.left, -fPreviousMask->fBounds.top)); if (previousBounds.right > fPreviousMask->fBounds.Width()) previousBounds.right = fPreviousMask->fBounds.Width(); if (previousBounds.bottom > fPreviousMask->fBounds.Height()) previousBounds.bottom = fPreviousMask->fBounds.Height(); int32 y = previousBounds.top; for (; y < 0; y++) { for (int32 x = 0; x < width; x++) { *destination = (fInverse ? 255 - source[3] : source[3]) * previousOutsideOpacity / 255; destination++; source += 4; } } for (; y <= previousBounds.bottom; y++) { int32 x = previousBounds.left; for (; x < 0; x++) { *destination = (fInverse ? 255 - source[3] : source[3]) * previousOutsideOpacity / 255; destination++; source += 4; } uint8* previousRow = fPreviousMask->fBuffer.row_ptr(y); for (; x <= previousBounds.right; x++) { uint8 sourceAlpha = fInverse ? 255 - source[3] : source[3]; *destination = sourceAlpha * previousRow[x] / 255; destination++; source += 4; } for (; x < previousBounds.left + width; x++) { *destination = (fInverse ? 255 - source[3] : source[3]) * previousOutsideOpacity / 255; destination++; source += 4; } } for (; y < previousBounds.top + height; y++) { for (int32 x = 0; x < width; x++) { *destination = (fInverse ? 255 - source[3] : source[3]) * previousOutsideOpacity / 255; destination++; source += 4; } } } else { while (numPixels--) { *destination = (fInverse ? 255 - source[3] : source[3]) * previousOutsideOpacity / 255; destination++; source += 4; } } } else { while (numPixels--) { *destination = fInverse ? 255 - source[3] : source[3]; destination++; source += 4; } } fBuffer.attach(fBits->Bits(), width, height, width); _AttachMaskToBuffer(); _AddToCache(); } void AlphaMask::_SetNoClipping() { fBuffer.attach(NULL, 0, 0, 0); _AttachMaskToBuffer(); } const IntRect& AlphaMask::_PreviousMaskBounds() const { return fPreviousMask->fBounds; } void AlphaMask::_AttachMaskToBuffer() { const IntPoint maskOffset = _Offset(); const int32 offsetX = fBounds.left + maskOffset.x + fCanvasOrigin.x; const int32 offsetY = fBounds.top + maskOffset.y + fCanvasOrigin.y; fMask.attach(fBuffer, offsetX, offsetY, fOutsideOpacity); } void AlphaMask::_SetOutsideOpacity() { fOutsideOpacity = fInverse ? 255 - fBackgroundOpacity : fBackgroundOpacity; if (fPreviousMask != NULL) { fOutsideOpacity = fOutsideOpacity * fPreviousMask->OutsideOpacity() / 255; } } // #pragma mark - UniformAlphaMask UniformAlphaMask::UniformAlphaMask(uint8 opacity) : AlphaMask(opacity) { fBounds.Set(0, 0, 0, 0); _SetNoClipping(); } ServerBitmap* UniformAlphaMask::_RenderSource(const IntRect&) { return NULL; } IntPoint UniformAlphaMask::_Offset() { return IntPoint(0, 0); } void UniformAlphaMask::_AddToCache() { } // #pragma mark - VectorAlphaMask template VectorAlphaMask::VectorAlphaMask(AlphaMask* previousMask, BPoint where, bool inverse) : AlphaMask(previousMask, inverse), fWhere(where) { } template VectorAlphaMask::VectorAlphaMask(AlphaMask* previousMask, VectorAlphaMask* other) : AlphaMask(previousMask, other), fWhere(other->fWhere) { } template ServerBitmap* VectorAlphaMask::_RenderSource(const IntRect& canvasBounds) { fBounds = static_cast(this)->DetermineBoundingBox(); if (fBounds.Width() > canvasBounds.Width() || fBounds.Height() > canvasBounds.Height()) { fBounds = fBounds & canvasBounds; fClippedToCanvas = true; } else fClippedToCanvas = false; if (fPreviousMask != NULL) { if (IsInverted()) { if (fPreviousMask->OutsideOpacity() != 0) { IntRect previousBounds = _PreviousMaskBounds(); if (previousBounds.IsValid()) fBounds = fBounds | previousBounds; } else fBounds = _PreviousMaskBounds(); fClippedToCanvas = fClippedToCanvas || fPreviousMask->IsClipped(); } else if (fPreviousMask->OutsideOpacity() == 0) fBounds = fBounds & _PreviousMaskBounds(); } if (!fBounds.IsValid()) return NULL; BReference bitmap(_CreateTemporaryBitmap(fBounds), true); if (bitmap == NULL) return NULL; // Render the picture to the bitmap BitmapHWInterface interface(bitmap); ObjectDeleter engine(interface.CreateDrawingEngine()); if (!engine.IsSet()) return NULL; engine->SetRendererOffset(fBounds.left, fBounds.top); OffscreenCanvas canvas(engine.Get(), static_cast(this)->GetDrawState(), fBounds); DrawState* const drawState = canvas.CurrentState(); drawState->SetDrawingMode(B_OP_ALPHA); drawState->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); drawState->SetDrawingModeLocked(true); canvas.PushState(); canvas.ResyncDrawState(); if (engine->LockParallelAccess()) { BRegion clipping; clipping.Set((clipping_rect)fBounds); engine->ConstrainClippingRegion(&clipping); static_cast(this)->DrawVectors(&canvas); engine->UnlockParallelAccess(); } canvas.PopState(); return bitmap.Detach(); } template IntPoint VectorAlphaMask::_Offset() { return fWhere; } // #pragma mark - PictureAlphaMask PictureAlphaMask::PictureAlphaMask(AlphaMask* previousMask, ServerPicture* picture, const DrawState& drawState, BPoint where, bool inverse) : VectorAlphaMask(previousMask, where, inverse), fPicture(picture), fDrawState(new(std::nothrow) DrawState(drawState)) { } PictureAlphaMask::~PictureAlphaMask() { } void PictureAlphaMask::DrawVectors(Canvas* canvas) { fPicture->Play(canvas); } BRect PictureAlphaMask::DetermineBoundingBox() const { BRect boundingBox; PictureBoundingBoxPlayer::Play(fPicture, fDrawState.Get(), &boundingBox); if (!boundingBox.IsValid()) return boundingBox; // Round up and add an additional 2 pixels on the bottom/right to // compensate for the various types of rounding used in Painter. boundingBox.left = floorf(boundingBox.left); boundingBox.right = ceilf(boundingBox.right) + 2; boundingBox.top = floorf(boundingBox.top); boundingBox.bottom = ceilf(boundingBox.bottom) + 2; return boundingBox; } const DrawState& PictureAlphaMask::GetDrawState() const { return *fDrawState.Get(); } void PictureAlphaMask::_AddToCache() { // currently not implemented } // #pragma mark - ShapeAlphaMask DrawState* ShapeAlphaMask::fDrawState = NULL; ShapeAlphaMask::ShapeAlphaMask(AlphaMask* previousMask, const shape_data& shape, BPoint where, bool inverse) : VectorAlphaMask(previousMask, where, inverse), fShape(new(std::nothrow) shape_data(shape), true) { if (fDrawState == NULL) fDrawState = new(std::nothrow) DrawState(); fShapeBounds = fShape->DetermineBoundingBox(); } ShapeAlphaMask::ShapeAlphaMask(AlphaMask* previousMask, ShapeAlphaMask* other) : VectorAlphaMask(previousMask, other), fShape(other->fShape), fShapeBounds(other->fShapeBounds) { } ShapeAlphaMask::~ShapeAlphaMask() { } /* static */ ShapeAlphaMask* ShapeAlphaMask::Create(AlphaMask* previousMask, const shape_data& shape, BPoint where, bool inverse) { // Look if we have a suitable cached mask BReference mask(AlphaMaskCache::Default()->Get(shape, previousMask, inverse), true); if (mask == NULL) { // No cached mask, create new one mask.SetTo(new(std::nothrow) ShapeAlphaMask(previousMask, shape, BPoint(0, 0), inverse), true); } else { // Create new mask which reuses the parameters and the mask bitmap // of the cache entry // TODO: don't make a new mask if the cache entry has no drawstate // using it anymore, because then we ca just immediately reuse it RecursiveLocker locker(mask->fLock); mask.SetTo(new(std::nothrow) ShapeAlphaMask(previousMask, mask), true); } return mask.Detach(); } void ShapeAlphaMask::DrawVectors(Canvas* canvas) { canvas->GetDrawingEngine()->DrawShape(fBounds, fShape->opCount, fShape->opList, fShape->ptCount, fShape->ptList, true, BPoint(0, 0), 1.0); } BRect ShapeAlphaMask::DetermineBoundingBox() const { return fShapeBounds; } const DrawState& ShapeAlphaMask::GetDrawState() const { return *fDrawState; } void ShapeAlphaMask::_AddToCache() { AlphaMaskCache::Default()->Put(this); }