xref: /haiku/src/servers/app/drawing/AlphaMask.cpp (revision 372b901dfeada686207d00bbcce456f748bbda12)
1 /*
2  * Copyright 2014-2015, Haiku, Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Adrien Destugues <pulkomandy@pulkomandy.tk>
7  *		Stephan Aßmus <superstippi@gmx.de>
8  *		Julian Harnath <julian.harnath@rwth-aachen.de>
9  */
10 
11 
12 #include "AlphaMask.h"
13 
14 #include "AlphaMaskCache.h"
15 #include "BitmapHWInterface.h"
16 #include "BitmapManager.h"
17 #include "Canvas.h"
18 #include "DrawingEngine.h"
19 #include "PictureBoundingBoxPlayer.h"
20 #include "ServerBitmap.h"
21 #include "ServerPicture.h"
22 #include "Shape.h"
23 #include "ShapePrivate.h"
24 
25 #include <AutoLocker.h>
26 
27 
28 // #pragma mark - AlphaMask
29 
30 
31 AlphaMask::AlphaMask(AlphaMask* previousMask, bool inverse)
32 	:
33 	fPreviousMask(previousMask),
34 	fBounds(),
35 	fClippedToCanvas(true),
36 	fCanvasOrigin(),
37 	fCanvasBounds(),
38 	fInverse(inverse),
39 	fBackgroundOpacity(0),
40 	fNextMaskCount(0),
41 	fInCache(false),
42 	fIndirectCacheReferences(0),
43 	fBits(NULL),
44 	fBuffer(),
45 	fMask(),
46 	fScanline(fMask)
47 {
48 	recursive_lock_init(&fLock, "AlphaMask");
49 
50 	if (previousMask != NULL)
51 		atomic_add(&previousMask->fNextMaskCount, 1);
52 }
53 
54 
55 AlphaMask::AlphaMask(AlphaMask* previousMask, AlphaMask* other)
56 	:
57 	fPreviousMask(previousMask),
58 	fBounds(other->fBounds),
59 	fClippedToCanvas(other->fClippedToCanvas),
60 	fCanvasOrigin(other->fCanvasOrigin),
61 	fCanvasBounds(other->fCanvasBounds),
62 	fInverse(other->fInverse),
63 	fBackgroundOpacity(other->fBackgroundOpacity),
64 	fNextMaskCount(0),
65 	fInCache(false),
66 	fIndirectCacheReferences(0),
67 	fBits(other->fBits),
68 	fBuffer(other->fBuffer),
69 	fMask(other->fMask),
70 	fScanline(fMask)
71 {
72 	recursive_lock_init(&fLock, "AlphaMask");
73 
74 	fMask.attach(fBuffer);
75 
76 	if (previousMask != NULL)
77 		atomic_add(&previousMask->fNextMaskCount, 1);
78 }
79 
80 
81 AlphaMask::AlphaMask(uint8 backgroundOpacity)
82 	:
83 	fPreviousMask(),
84 	fBounds(),
85 	fClippedToCanvas(true),
86 	fCanvasOrigin(),
87 	fCanvasBounds(),
88 	fInverse(false),
89 	fBackgroundOpacity(backgroundOpacity),
90 	fNextMaskCount(0),
91 	fInCache(false),
92 	fIndirectCacheReferences(0),
93 	fBits(NULL),
94 	fBuffer(),
95 	fMask(),
96 	fScanline(fMask)
97 {
98 	recursive_lock_init(&fLock, "AlphaMask");
99 }
100 
101 
102 AlphaMask::~AlphaMask()
103 {
104 	if (fPreviousMask.IsSet())
105 		atomic_add(&fPreviousMask->fNextMaskCount, -1);
106 
107 	recursive_lock_destroy(&fLock);
108 }
109 
110 
111 IntPoint
112 AlphaMask::SetCanvasGeometry(IntPoint origin, IntRect bounds)
113 {
114 	RecursiveLocker locker(fLock);
115 
116 	if (origin == fCanvasOrigin && bounds.Width() == fCanvasBounds.Width()
117 		&& bounds.Height() == fCanvasBounds.Height())
118 		return fCanvasOrigin;
119 
120 	IntPoint oldOrigin = fCanvasOrigin;
121 	fCanvasOrigin = origin;
122 	IntRect oldBounds = fCanvasBounds;
123 	fCanvasBounds = IntRect(0, 0, bounds.Width(), bounds.Height());
124 
125 	if (fPreviousMask != NULL)
126 		fPreviousMask->SetCanvasGeometry(origin, bounds);
127 
128 	if (fClippedToCanvas && (fCanvasBounds.Width() > oldBounds.Width()
129 		|| fCanvasBounds.Height() > oldBounds.Height())) {
130 		// The canvas is now larger than before and we previously
131 		// drew the alpha mask clipped to the (old) bounds of the
132 		// canvas. So we now have to redraw the alpha mask with the
133 		// new size.
134 		_Generate();
135 	}
136 
137 	_AttachMaskToBuffer();
138 
139 	return oldOrigin;
140 }
141 
142 
143 size_t
144 AlphaMask::BitmapSize() const
145 {
146 	return fBits->BitsLength();
147 }
148 
149 
150 ServerBitmap*
151 AlphaMask::_CreateTemporaryBitmap(BRect bounds) const
152 {
153 	BReference<UtilityBitmap> bitmap(new(std::nothrow) UtilityBitmap(bounds,
154 		B_RGBA32, 0), true);
155 	if (bitmap == NULL)
156 		return NULL;
157 
158 	if (!bitmap->IsValid())
159 		return NULL;
160 
161 	memset(bitmap->Bits(), fBackgroundOpacity, bitmap->BitsLength());
162 
163 	return bitmap.Detach();
164 }
165 
166 
167 void
168 AlphaMask::_Generate()
169 {
170 	RecursiveLocker locker(fLock);
171 	RecursiveLocker previousLocker;
172 	if (fPreviousMask != NULL)
173 		previousLocker.SetTo(fPreviousMask->fLock, false);
174 
175 	ServerBitmap* const bitmap = _RenderSource(fCanvasBounds);
176 	BReference<ServerBitmap> bitmapRef(bitmap, true);
177 	if (bitmap == NULL) {
178 		_SetNoClipping();
179 		return;
180 	}
181 
182 	fBits.SetTo(new(std::nothrow) UtilityBitmap(fBounds, B_GRAY8, 0), true);
183 	if (fBits == NULL)
184 		return;
185 
186 	const int32 width = fBits->Width();
187 	const int32 height = fBits->Height();
188 	uint8* source = bitmap->Bits();
189 	uint8* destination = fBits->Bits();
190 	uint32 numPixels = width * height;
191 
192 	if (fPreviousMask != NULL) {
193 		int32 previousStartX = fBounds.left - fPreviousMask->fBounds.left;
194 		int32 previousStartY = fBounds.top - fPreviousMask->fBounds.top;
195 		if (previousStartX < 0)
196 			previousStartX = 0;
197 		if (previousStartY < 0)
198 			previousStartY = 0;
199 
200 		for (int32 y = previousStartY; y < previousStartY + height; y++) {
201 			uint8* previousRow = fPreviousMask->fBuffer.row_ptr(y);
202 			for (int32 x = previousStartX; x < previousStartX + width; x++) {
203 				uint8 sourceAlpha = fInverse ? 255 - source[3] : source[3];
204 				*destination = sourceAlpha * previousRow[x] / 255;
205 				destination++;
206 				source += 4;
207 			}
208 		}
209 	} else {
210 		while (numPixels--) {
211 			*destination = fInverse ? 255 - source[3] : source[3];
212 			destination++;
213 			source += 4;
214 		}
215 	}
216 
217 	fBuffer.attach(fBits->Bits(), width, height, width);
218 	_AttachMaskToBuffer();
219 
220 	_AddToCache();
221 }
222 
223 
224 void
225 AlphaMask::_SetNoClipping()
226 {
227 	fBuffer.attach(NULL, 0, 0, 0);
228 	_AttachMaskToBuffer();
229 }
230 
231 
232 const IntRect&
233 AlphaMask::_PreviousMaskBounds() const
234 {
235 	return fPreviousMask->fBounds;
236 }
237 
238 
239 void
240 AlphaMask::_AttachMaskToBuffer()
241 {
242 	uint8 outsideOpacity = fInverse ? 255 - fBackgroundOpacity
243 		: fBackgroundOpacity;
244 
245 	AlphaMask* previousMask = fPreviousMask;
246 	while (previousMask != NULL && outsideOpacity != 0) {
247 		uint8 previousOutsideOpacity = previousMask->fInverse
248 			? 255 - previousMask->fBackgroundOpacity
249 			: previousMask->fBackgroundOpacity;
250 		outsideOpacity = outsideOpacity * previousOutsideOpacity / 255;
251 		previousMask = previousMask->fPreviousMask;
252 	}
253 
254 	const IntPoint maskOffset = _Offset();
255 	const int32 offsetX = fBounds.left + maskOffset.x + fCanvasOrigin.x;
256 	const int32 offsetY = fBounds.top + maskOffset.y + fCanvasOrigin.y;
257 
258 	fMask.attach(fBuffer, offsetX, offsetY, outsideOpacity);
259 }
260 
261 
262 // #pragma mark - UniformAlphaMask
263 
264 
265 UniformAlphaMask::UniformAlphaMask(uint8 opacity)
266 	:
267 	AlphaMask(opacity)
268 {
269 	fBounds.Set(0, 0, 0, 0);
270 	_SetNoClipping();
271 }
272 
273 
274 ServerBitmap*
275 UniformAlphaMask::_RenderSource(const IntRect&)
276 {
277 	return NULL;
278 }
279 
280 
281 IntPoint
282 UniformAlphaMask::_Offset()
283 {
284 	return IntPoint(0, 0);
285 }
286 
287 
288 void
289 UniformAlphaMask::_AddToCache()
290 {
291 }
292 
293 
294 // #pragma mark - VectorAlphaMask
295 
296 
297 template<class VectorMaskType>
298 VectorAlphaMask<VectorMaskType>::VectorAlphaMask(AlphaMask* previousMask,
299 	BPoint where, bool inverse)
300 	:
301 	AlphaMask(previousMask, inverse),
302 	fWhere(where)
303 {
304 }
305 
306 
307 template<class VectorMaskType>
308 VectorAlphaMask<VectorMaskType>::VectorAlphaMask(AlphaMask* previousMask,
309 	VectorAlphaMask* other)
310 	:
311 	AlphaMask(previousMask, other),
312 	fWhere(other->fWhere)
313 {
314 }
315 
316 
317 template<class VectorMaskType>
318 ServerBitmap*
319 VectorAlphaMask<VectorMaskType>::_RenderSource(const IntRect& canvasBounds)
320 {
321 	fBounds = static_cast<VectorMaskType*>(this)->DetermineBoundingBox();
322 
323 	if (fBounds.Width() > canvasBounds.Width()
324 		|| fBounds.Height() > canvasBounds.Height()) {
325 		fBounds = fBounds & canvasBounds;
326 		fClippedToCanvas = true;
327 	} else
328 		fClippedToCanvas = false;
329 
330 	if (fPreviousMask != NULL)
331 		fBounds = fBounds & _PreviousMaskBounds();
332 	if (!fBounds.IsValid())
333 		return NULL;
334 
335 	BReference<ServerBitmap> bitmap(_CreateTemporaryBitmap(fBounds), true);
336 	if (bitmap == NULL)
337 		return NULL;
338 
339 	// Render the picture to the bitmap
340 	BitmapHWInterface interface(bitmap);
341 	ObjectDeleter<DrawingEngine> engine(interface.CreateDrawingEngine());
342 	if (!engine.IsSet())
343 		return NULL;
344 
345 	engine->SetRendererOffset(fBounds.left, fBounds.top);
346 
347 	OffscreenCanvas canvas(engine.Get(),
348 		static_cast<VectorMaskType*>(this)->GetDrawState(), fBounds);
349 
350 	DrawState* const drawState = canvas.CurrentState();
351 	drawState->SetDrawingMode(B_OP_ALPHA);
352 	drawState->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
353 	drawState->SetDrawingModeLocked(true);
354 	canvas.PushState();
355 
356 	canvas.ResyncDrawState();
357 
358 	if (engine->LockParallelAccess()) {
359 		BRegion clipping;
360 		clipping.Set((clipping_rect)fBounds);
361 		engine->ConstrainClippingRegion(&clipping);
362 		static_cast<VectorMaskType*>(this)->DrawVectors(&canvas);
363 		engine->UnlockParallelAccess();
364 	}
365 
366 	canvas.PopState();
367 
368 	return bitmap.Detach();
369 }
370 
371 
372 template<class VectorMaskType>
373 IntPoint
374 VectorAlphaMask<VectorMaskType>::_Offset()
375 {
376 	return fWhere;
377 }
378 
379 
380 
381 // #pragma mark - PictureAlphaMask
382 
383 
384 PictureAlphaMask::PictureAlphaMask(AlphaMask* previousMask,
385 	ServerPicture* picture, const DrawState& drawState, BPoint where,
386 	bool inverse)
387 	:
388 	VectorAlphaMask<PictureAlphaMask>(previousMask, where, inverse),
389 	fPicture(picture),
390 	fDrawState(new(std::nothrow) DrawState(drawState))
391 {
392 }
393 
394 
395 PictureAlphaMask::~PictureAlphaMask()
396 {
397 }
398 
399 
400 void
401 PictureAlphaMask::DrawVectors(Canvas* canvas)
402 {
403 	fPicture->Play(canvas);
404 }
405 
406 
407 BRect
408 PictureAlphaMask::DetermineBoundingBox() const
409 {
410 	BRect boundingBox;
411 	PictureBoundingBoxPlayer::Play(fPicture, fDrawState.Get(), &boundingBox);
412 
413 	if (!boundingBox.IsValid())
414 		return boundingBox;
415 
416 	// Round up and add an additional 2 pixels on the bottom/right to
417 	// compensate for the various types of rounding used in Painter.
418 	boundingBox.left = floorf(boundingBox.left);
419 	boundingBox.right = ceilf(boundingBox.right) + 2;
420 	boundingBox.top = floorf(boundingBox.top);
421 	boundingBox.bottom = ceilf(boundingBox.bottom) + 2;
422 
423 	return boundingBox;
424 }
425 
426 
427 const DrawState&
428 PictureAlphaMask::GetDrawState() const
429 {
430 	return *fDrawState.Get();
431 }
432 
433 
434 void
435 PictureAlphaMask::_AddToCache()
436 {
437 	// currently not implemented
438 }
439 
440 
441 // #pragma mark - ShapeAlphaMask
442 
443 
444 DrawState* ShapeAlphaMask::fDrawState = NULL;
445 
446 
447 ShapeAlphaMask::ShapeAlphaMask(AlphaMask* previousMask,
448 	const shape_data& shape, BPoint where, bool inverse)
449 	:
450 	VectorAlphaMask<ShapeAlphaMask>(previousMask, where, inverse),
451 	fShape(new(std::nothrow) shape_data(shape), true)
452 {
453 	if (fDrawState == NULL)
454 		fDrawState = new(std::nothrow) DrawState();
455 
456 	fShapeBounds = fShape->DetermineBoundingBox();
457 }
458 
459 
460 ShapeAlphaMask::ShapeAlphaMask(AlphaMask* previousMask,
461 	ShapeAlphaMask* other)
462 	:
463 	VectorAlphaMask<ShapeAlphaMask>(previousMask, other),
464 	fShape(other->fShape),
465 	fShapeBounds(other->fShapeBounds)
466 {
467 }
468 
469 
470 ShapeAlphaMask::~ShapeAlphaMask()
471 {
472 }
473 
474 
475 /* static */ ShapeAlphaMask*
476 ShapeAlphaMask::Create(AlphaMask* previousMask, const shape_data& shape,
477 	BPoint where, bool inverse)
478 {
479 	// Look if we have a suitable cached mask
480 	BReference<ShapeAlphaMask> mask(AlphaMaskCache::Default()->Get(shape,
481 		previousMask, inverse), true);
482 
483 	if (mask == NULL) {
484 		// No cached mask, create new one
485 		mask.SetTo(new(std::nothrow) ShapeAlphaMask(previousMask, shape,
486 			BPoint(0, 0), inverse), true);
487 	} else {
488 		// Create new mask which reuses the parameters and the mask bitmap
489 		// of the cache entry
490 		// TODO: don't make a new mask if the cache entry has no drawstate
491 		// using it anymore, because then we ca just immediately reuse it
492 		RecursiveLocker locker(mask->fLock);
493 		mask.SetTo(new(std::nothrow) ShapeAlphaMask(previousMask, mask), true);
494 	}
495 
496 	return mask.Detach();
497 }
498 
499 
500 void
501 ShapeAlphaMask::DrawVectors(Canvas* canvas)
502 {
503 	canvas->GetDrawingEngine()->DrawShape(fBounds,
504 		fShape->opCount, fShape->opList,
505 		fShape->ptCount, fShape->ptList,
506 		true, BPoint(0, 0), 1.0);
507 }
508 
509 
510 BRect
511 ShapeAlphaMask::DetermineBoundingBox() const
512 {
513 	return fShapeBounds;
514 }
515 
516 
517 const DrawState&
518 ShapeAlphaMask::GetDrawState() const
519 {
520 	return *fDrawState;
521 }
522 
523 
524 void
525 ShapeAlphaMask::_AddToCache()
526 {
527 	AlphaMaskCache::Default()->Put(this);
528 }
529