app_server: Implemented nested clipping paths

* The alpha mask is no longer stored with 75% more memory than needed,
   resolving a TODO.
 * AlphaMasks are now BReferenceables.
 * AlphaMasks are transferred to a pushed DrawState.
 * When an AlphaMask is set on a DrawState, it sets the previous state's
   mask to the AlphaMask. That one now takes care of updating not only itself,
   but the previous mask as well (which works recursively).
 * In AlphaMask::Generate(), a combination happens with the previous state's
   mask, which again works recursively in case the previous mask also needs
   to be updated. This step is combined with extracting the alpha channel from
   the UtilityBitmap used to play the ServerPicture.
 * Fixed some out of bounds access to memory in the "outside" case in
   agg_clipped_alpha_mask.h. This happened when the requested region was
   both before and after where the mask has data.
This commit is contained in:
Stephan Aßmus 2014-02-03 23:39:42 +01:00
parent 7bc9f84556
commit 77214b5abc
5 changed files with 119 additions and 41 deletions

View File

@ -21,6 +21,8 @@
AlphaMask::AlphaMask(ServerPicture* picture, bool inverse, BPoint origin,
const DrawState& drawState)
:
fPreviousMask(NULL),
fPicture(picture),
fInverse(inverse),
fOrigin(origin),
@ -44,8 +46,8 @@ AlphaMask::AlphaMask(ServerPicture* picture, bool inverse, BPoint origin,
AlphaMask::~AlphaMask()
{
fPicture->ReleaseReference();
if (fCachedBitmap)
fCachedBitmap->ReleaseReference();
delete[] fCachedBitmap;
SetPrevious(NULL);
}
@ -54,6 +56,25 @@ AlphaMask::Update(BRect bounds, BPoint offset)
{
fViewBounds = bounds;
fViewOffset = offset;
if (fPreviousMask != NULL)
fPreviousMask->Update(bounds, offset);
}
void
AlphaMask::SetPrevious(AlphaMask* mask)
{
// Since multiple DrawStates can point to the same AlphaMask,
// don't accept ourself as the "previous" mask on the state stack.
if (mask == this || mask == fPreviousMask)
return;
if (mask != NULL)
mask->AcquireReference();
if (fPreviousMask != NULL)
fPreviousMask->ReleaseReference();
fPreviousMask = mask;
}
@ -70,23 +91,77 @@ AlphaMask::Generate()
return &fScanline;
}
if (fCachedBitmap != NULL)
fCachedBitmap->ReleaseReference();
uint32 width = fViewBounds.IntegerWidth() + 1;
uint32 height = fViewBounds.IntegerHeight() + 1;
if (fViewBounds != fCachedBounds || fCachedBitmap == NULL) {
delete[] fCachedBitmap;
fCachedBitmap = new(std::nothrow) uint8[width * height];
}
// If rendering the picture fails, we will draw without any clipping.
ServerBitmap* bitmap = _RenderPicture();
if (bitmap == NULL) {
fCachedBitmap = NULL;
if (bitmap == NULL || fCachedBitmap == NULL) {
fBuffer.attach(NULL, 0, 0, 0);
return NULL;
}
fCachedBitmap = bitmap;
uint8* bits = bitmap->Bits();
uint32 bytesPerRow = bitmap->BytesPerRow();
uint8* row = bits;
uint8* pixel = fCachedBitmap;
// Let any previous masks also regenerate themselves. Updating the cached
// mask bitmap is only necessary after the view size changed or the
// scrolling offset, which definitely affects any masks of lower states
// as well, so it works recursively until the bottom mask is regenerated.
bool transferBitmap = true;
if (fPreviousMask != NULL) {
fPreviousMask->Generate();
if (fPreviousMask->fCachedBitmap != NULL) {
uint8* previousBits = fPreviousMask->fCachedBitmap;
for (uint32 y = 0; y < height; y++) {
for (uint32 x = 0; x < width; x++) {
if (previousBits[0] != 0) {
if (fInverse)
pixel[0] = 255 - row[3];
else
pixel[0] = row[3];
pixel[0] = pixel[0] * previousBits[0] / 255;
} else
pixel[0] = 0;
previousBits++;
pixel++;
row += 4;
}
bits += bytesPerRow;
row = bits;
}
transferBitmap = false;
}
}
if (transferBitmap) {
for (uint32 y = 0; y < height; y++) {
for (uint32 x = 0; x < width; x++) {
if (fInverse)
pixel[0] = 255 - row[3];
else
pixel[0] = row[3];
pixel++;
row += 4;
}
bits += bytesPerRow;
row = bits;
}
}
bitmap->ReleaseReference();
fCachedBounds = fViewBounds;
fCachedOffset = fViewOffset;
fBuffer.attach(fCachedBitmap->Bits(), fCachedBitmap->Width(),
fCachedBitmap->Height(), fCachedBitmap->BytesPerRow());
fBuffer.attach(fCachedBitmap, width, height, width);
fCachedMask.attach(fBuffer, fViewOffset.x + fOrigin.x,
fViewOffset.y + fOrigin.y, fInverse ? 255 : 0);
@ -98,8 +173,6 @@ AlphaMask::Generate()
ServerBitmap*
AlphaMask::_RenderPicture() const
{
// TODO: Only the alpha channel is relevant, but there is no B_ALPHA8
// color space, so we use 300% more memory than needed.
UtilityBitmap* bitmap = new(std::nothrow) UtilityBitmap(fViewBounds,
B_RGBA32, 0);
if (bitmap == NULL)
@ -132,16 +205,6 @@ AlphaMask::_RenderPicture() const
context.PopState();
delete engine;
if (!fInverse)
return bitmap;
// Compute the inverse of our bitmap. There probably is a better way.
uint32 size = bitmap->BitsLength();
uint8* bits = (uint8*)bitmap->Bits();
for (uint32 i = 3; i < size; i += 4)
bits[i] = 255 - bits[i];
return bitmap;
}

View File

@ -6,6 +6,7 @@
#ifndef ALPHA_MASK_H
#define ALPHA_MASK_H
#include <Referenceable.h>
#include "agg_clipped_alpha_mask.h"
#include "ServerPicture.h"
@ -18,7 +19,7 @@ class ServerBitmap;
class ServerPicture;
class AlphaMask {
class AlphaMask : public BReferenceable {
public:
AlphaMask(ServerPicture* mask, bool inverse,
BPoint origin, const DrawState& drawState);
@ -26,6 +27,8 @@ public:
void Update(BRect bounds, BPoint offset);
void SetPrevious(AlphaMask* mask);
scanline_unpacked_masked_type* Generate();
private:
@ -33,6 +36,8 @@ private:
private:
AlphaMask* fPreviousMask;
ServerPicture* fPicture;
const bool fInverse;
BPoint fOrigin;
@ -41,7 +46,7 @@ private:
BRect fViewBounds;
BPoint fViewOffset;
ServerBitmap* fCachedBitmap;
uint8* fCachedBitmap;
BRect fCachedBounds;
BPoint fCachedOffset;

View File

@ -101,7 +101,8 @@ DrawState::~DrawState()
{
delete fClippingRegion;
delete fPreviousState;
delete fAlphaMask;
if (fAlphaMask != NULL)
fAlphaMask->ReleaseReference();
}
@ -115,6 +116,7 @@ DrawState::PushState()
next->fOrigin = BPoint(0.0, 0.0);
next->fScale = 1.0;
next->fPreviousState = this;
next->SetAlphaMask(fAlphaMask);
}
return next;
@ -394,9 +396,17 @@ DrawState::SetAlphaMask(AlphaMask* mask)
{
// NOTE: In BeOS, it wasn't possible to clip to a BPicture and keep
// regular custom clipping to a BRegion at the same time.
if (fAlphaMask == mask)
return;
delete fAlphaMask;
if (mask != NULL)
mask->AcquireReference();
if (fAlphaMask != NULL)
fAlphaMask->ReleaseReference();
fAlphaMask = mask;
if (fAlphaMask != NULL && fPreviousState != NULL)
fAlphaMask->SetPrevious(fPreviousState->fAlphaMask);
}

View File

@ -1883,8 +1883,11 @@ fDesktop->LockSingleWindow();
if (picture == NULL)
break;
fCurrentView->SetAlphaMask(new(std::nothrow) AlphaMask(
picture, inverse, where, *fCurrentView->CurrentState()));
AlphaMask* mask = new(std::nothrow) AlphaMask(
picture, inverse, where, *fCurrentView->CurrentState());
fCurrentView->SetAlphaMask(mask);
if (mask != NULL)
mask->ReleaseReference();
_UpdateDrawState(fCurrentView);
picture->ReleaseReference();

View File

@ -72,27 +72,27 @@ namespace agg
x = 0;
}
int rest = 0;
if(x + count > xmax)
{
int rest = x + count - xmax - 1;
rest = x + count - xmax - 1;
count -= rest;
if(count <= 0)
{
memset(dst, m_outside, num_pix * sizeof(cover_type));
return;
}
memset(covers + count, m_outside, rest * sizeof(cover_type));
}
const int8u* mask = m_rbuf->row_ptr(y) + x * Step + Offset;
do
while(count != 0)
{
*covers = (cover_type)((cover_full + (*covers) * (*mask))
>> cover_shift);
++covers;
mask += Step;
--count;
}
if(rest > 0)
{
memset(covers, m_outside, rest * sizeof(cover_type));
}
while(--count);
}
private:
@ -102,11 +102,8 @@ namespace agg
rendering_buffer* m_rbuf;
int8u m_outside;
// TODO this assumes an RGBA bitmap and only uses the alpha channel.
// We should keep the masking bitmap as an 8-bit bitmap with only the
// alpha channel, to save memory. (this would be Step=1, Offset=0)
static const int Step = 4;
static const int Offset = 3;
static const int Step = 1;
static const int Offset = 0;
};
}