1/*
2 * Copyright 2015 Julian Harnath <julian.harnath@rwth-aachen.de>
3 * All rights reserved. Distributed under the terms of the MIT license.
4 */
5#include "Layer.h"
6
7#include "AlphaMask.h"
8#include "BitmapHWInterface.h"
9#include "DrawingEngine.h"
10#include "DrawState.h"
11#include "IntRect.h"
12#include "PictureBoundingBoxPlayer.h"
13#include "ServerBitmap.h"
14#include "View.h"
15
16
17class LayerCanvas : public Canvas {
18public:
19	LayerCanvas(DrawingEngine* drawingEngine, DrawState* drawState,
20		BRect bitmapBounds)
21		:
22		Canvas(),
23		fDrawingEngine(drawingEngine),
24		fBitmapBounds(bitmapBounds)
25	{
26		fDrawState.SetTo(drawState);
27	}
28
29	virtual DrawingEngine* GetDrawingEngine() const
30	{
31		return fDrawingEngine;
32	}
33
34	virtual ServerPicture* GetPicture(int32 token) const
35	{
36		return NULL;
37	}
38
39	virtual void RebuildClipping(bool)
40	{
41	}
42
43	virtual void ResyncDrawState()
44	{
45		fDrawingEngine->SetDrawState(fDrawState.Get());
46	}
47
48	virtual void UpdateCurrentDrawingRegion()
49	{
50		bool hasDrawStateClipping = fDrawState->GetCombinedClippingRegion(
51			&fCurrentDrawingRegion);
52
53		BRegion bitmapRegion(fBitmapBounds);
54		if (hasDrawStateClipping)
55			fCurrentDrawingRegion.IntersectWith(&bitmapRegion);
56		else
57			fCurrentDrawingRegion = bitmapRegion;
58
59		fDrawingEngine->ConstrainClippingRegion(&fCurrentDrawingRegion);
60	}
61
62	virtual	IntRect Bounds() const
63	{
64		return fBitmapBounds;
65	}
66
67protected:
68	virtual void _LocalToScreenTransform(SimpleTransform&) const
69	{
70	}
71
72	virtual void _ScreenToLocalTransform(SimpleTransform&) const
73	{
74	}
75
76private:
77	DrawingEngine*	fDrawingEngine;
78	BRegion			fCurrentDrawingRegion;
79	BRect			fBitmapBounds;
80};
81
82
83Layer::Layer(uint8 opacity)
84	:
85	fOpacity(opacity),
86	fLeftTopOffset(0, 0)
87{
88}
89
90
91Layer::~Layer()
92{
93}
94
95
96void
97Layer::PushLayer(Layer* layer)
98{
99	PushPicture(layer);
100}
101
102
103Layer*
104Layer::PopLayer()
105{
106	return static_cast<Layer*>(PopPicture());
107}
108
109
110UtilityBitmap*
111Layer::RenderToBitmap(Canvas* canvas)
112{
113	BRect boundingBox = _DetermineBoundingBox(canvas);
114	if (!boundingBox.IsValid())
115		return NULL;
116
117	fLeftTopOffset = boundingBox.LeftTop();
118
119	BReference<UtilityBitmap> layerBitmap(_AllocateBitmap(boundingBox), true);
120	if (layerBitmap == NULL)
121		return NULL;
122
123	BitmapHWInterface layerInterface(layerBitmap);
124	ObjectDeleter<DrawingEngine> const layerEngine(layerInterface.CreateDrawingEngine());
125	if (!layerEngine.IsSet())
126		return NULL;
127
128	layerEngine->SetRendererOffset(boundingBox.left, boundingBox.top);
129		// Drawing commands of the layer's picture use coordinates in the
130		// coordinate space of the underlying canvas. The coordinate origin
131		// of the layer bitmap is at boundingBox.LeftTop(). So all the drawing
132		// from the picture needs to be offset to be moved into the bitmap.
133		// We use a low-level offsetting via the AGG renderer here because the
134		// offset needs to be processed independently, after all other
135		// transforms, even after the BAffineTransforms (which are processed in
136		// Painter), to prevent this origin from being further transformed by
137		// e.g. scaling.
138
139	LayerCanvas layerCanvas(layerEngine.Get(), canvas->DetachDrawState(), boundingBox);
140
141	AlphaMask* const mask = layerCanvas.GetAlphaMask();
142	IntPoint oldOffset;
143	if (mask != NULL) {
144		// Move alpha mask to bitmap origin
145		oldOffset = mask->SetCanvasGeometry(IntPoint(0, 0), boundingBox);
146	}
147
148	layerCanvas.CurrentState()->SetDrawingMode(B_OP_ALPHA);
149	layerCanvas.CurrentState()->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
150
151	layerCanvas.ResyncDrawState();
152		// Apply state to the new drawing engine of the layer canvas
153
154	if (layerEngine->LockParallelAccess()) {
155		layerCanvas.UpdateCurrentDrawingRegion();
156
157		// Draw recorded picture into bitmap
158		Play(&layerCanvas);
159		layerEngine->UnlockParallelAccess();
160	}
161
162	if (mask != NULL) {
163		// Move alpha mask back to its old position
164		// Note: this needs to be adapted if setting alpha masks is
165		// implemented as BPicture command (the mask now might be a different
166		// one than before).
167		layerCanvas.CurrentState()->CombinedTransform().Apply(oldOffset);
168		mask->SetCanvasGeometry(oldOffset, boundingBox);
169		layerCanvas.ResyncDrawState();
170	}
171
172	canvas->SetDrawState(layerCanvas.DetachDrawState());
173		// Update state in canvas (the top-of-stack state could be a different
174		// state instance now, if the picture commands contained push/pop
175		// commands)
176
177	return layerBitmap.Detach();
178}
179
180
181IntPoint
182Layer::LeftTopOffset() const
183{
184	return fLeftTopOffset;
185}
186
187
188uint8
189Layer::Opacity() const
190{
191	return fOpacity;
192}
193
194
195BRect
196Layer::_DetermineBoundingBox(Canvas* canvas)
197{
198	BRect boundingBox;
199	PictureBoundingBoxPlayer::Play(this, canvas->CurrentState(), &boundingBox);
200
201	if (!boundingBox.IsValid())
202		return boundingBox;
203
204	// Round up and add an additional 2 pixels on the bottom/right to
205	// compensate for the various types of rounding used in Painter.
206	boundingBox.left = floorf(boundingBox.left);
207	boundingBox.right = ceilf(boundingBox.right) + 2;
208	boundingBox.top = floorf(boundingBox.top);
209	boundingBox.bottom = ceilf(boundingBox.bottom) + 2;
210
211	// TODO: for optimization, crop the bounding box to the underlying
212	// view bounds here
213
214	return boundingBox;
215}
216
217
218UtilityBitmap*
219Layer::_AllocateBitmap(const BRect& bounds)
220{
221	BReference<UtilityBitmap> layerBitmap(new(std::nothrow) UtilityBitmap(bounds,
222		B_RGBA32, 0), true);
223	if (layerBitmap == NULL)
224		return NULL;
225	if (!layerBitmap->IsValid())
226		return NULL;
227
228	memset(layerBitmap->Bits(), 0, layerBitmap->BitsLength());
229
230	return layerBitmap.Detach();
231}
232