* Copyright 2014-2015, Haiku, Inc.
* Distributed under the terms of the MIT License.
*
* Authors:
* Adrien Destugues <pulkomandy@pulkomandy.tk>
* Stephan Aßmus <superstippi@gmx.de>
* Julian Harnath <julian.harnath@rwth-aachen.de>
*/
#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 <AutoLocker.h>
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())) {
_Generate();
}
_AttachMaskToBuffer();
return oldOrigin;
}
size_t
AlphaMask::BitmapSize() const
{
return fBits->BitsLength();
}
ServerBitmap*
AlphaMask::_CreateTemporaryBitmap(BRect bounds) const
{
BReference<UtilityBitmap> 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<ServerBitmap> 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;
}
}
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()
{
}
template<class VectorMaskType>
VectorAlphaMask<VectorMaskType>::VectorAlphaMask(AlphaMask* previousMask,
BPoint where, bool inverse)
:
AlphaMask(previousMask, inverse),
fWhere(where)
{
}
template<class VectorMaskType>
VectorAlphaMask<VectorMaskType>::VectorAlphaMask(AlphaMask* previousMask,
VectorAlphaMask* other)
:
AlphaMask(previousMask, other),
fWhere(other->fWhere)
{
}
template<class VectorMaskType>
ServerBitmap*
VectorAlphaMask<VectorMaskType>::_RenderSource(const IntRect& canvasBounds)
{
fBounds = static_cast<VectorMaskType*>(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<ServerBitmap> bitmap(_CreateTemporaryBitmap(fBounds), true);
if (bitmap == NULL)
return NULL;
BitmapHWInterface interface(bitmap);
ObjectDeleter<DrawingEngine> engine(interface.CreateDrawingEngine());
if (!engine.IsSet())
return NULL;
engine->SetRendererOffset(fBounds.left, fBounds.top);
OffscreenCanvas canvas(engine.Get(),
static_cast<VectorMaskType*>(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<VectorMaskType*>(this)->DrawVectors(&canvas);
engine->UnlockParallelAccess();
}
canvas.PopState();
return bitmap.Detach();
}
template<class VectorMaskType>
IntPoint
VectorAlphaMask<VectorMaskType>::_Offset()
{
return fWhere;
}
PictureAlphaMask::PictureAlphaMask(AlphaMask* previousMask,
ServerPicture* picture, const DrawState& drawState, BPoint where,
bool inverse)
:
VectorAlphaMask<PictureAlphaMask>(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;
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()
{
}
DrawState* ShapeAlphaMask::fDrawState = NULL;
ShapeAlphaMask::ShapeAlphaMask(AlphaMask* previousMask,
const shape_data& shape, BPoint where, bool inverse)
:
VectorAlphaMask<ShapeAlphaMask>(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<ShapeAlphaMask>(previousMask, other),
fShape(other->fShape),
fShapeBounds(other->fShapeBounds)
{
}
ShapeAlphaMask::~ShapeAlphaMask()
{
}
ShapeAlphaMask*
ShapeAlphaMask::Create(AlphaMask* previousMask, const shape_data& shape,
BPoint where, bool inverse)
{
BReference<ShapeAlphaMask> mask(AlphaMaskCache::Default()->Get(shape,
previousMask, inverse), true);
if (mask == NULL) {
mask.SetTo(new(std::nothrow) ShapeAlphaMask(previousMask, shape,
BPoint(0, 0), inverse), true);
} else {
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);
}