root/src/servers/app/drawing/AlphaMask.cpp
/*
 * 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>


// #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<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;
        }
}


// #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<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;

        // Render the picture to the bitmap
        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;
}



// #pragma mark - PictureAlphaMask


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;

        // 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<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()
{
}


/* static */ ShapeAlphaMask*
ShapeAlphaMask::Create(AlphaMask* previousMask, const shape_data& shape,
        BPoint where, bool inverse)
{
        // Look if we have a suitable cached mask
        BReference<ShapeAlphaMask> 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);
}