/* * Copyright 2015 Julian Harnath * All rights reserved. Distributed under the terms of the MIT license. */ #include "Layer.h" #include "AlphaMask.h" #include "BitmapHWInterface.h" #include "DrawingEngine.h" #include "DrawState.h" #include "IntRect.h" #include "PictureBoundingBoxPlayer.h" #include "ServerBitmap.h" #include "View.h" class LayerCanvas : public Canvas { public: LayerCanvas(DrawingEngine* drawingEngine, DrawState* drawState, BRect bitmapBounds) : Canvas(), fDrawingEngine(drawingEngine), fBitmapBounds(bitmapBounds) { fDrawState.SetTo(drawState); } virtual DrawingEngine* GetDrawingEngine() const { return fDrawingEngine; } virtual ServerPicture* GetPicture(int32 token) const { return NULL; } virtual void RebuildClipping(bool) { } virtual void ResyncDrawState() { fDrawingEngine->SetDrawState(fDrawState.Get()); } virtual void UpdateCurrentDrawingRegion() { bool hasDrawStateClipping = fDrawState->GetCombinedClippingRegion( &fCurrentDrawingRegion); BRegion bitmapRegion(fBitmapBounds); if (hasDrawStateClipping) fCurrentDrawingRegion.IntersectWith(&bitmapRegion); else fCurrentDrawingRegion = bitmapRegion; fDrawingEngine->ConstrainClippingRegion(&fCurrentDrawingRegion); } virtual IntRect Bounds() const { return fBitmapBounds; } protected: virtual void _LocalToScreenTransform(SimpleTransform&) const { } virtual void _ScreenToLocalTransform(SimpleTransform&) const { } private: DrawingEngine* fDrawingEngine; BRegion fCurrentDrawingRegion; BRect fBitmapBounds; }; Layer::Layer(uint8 opacity) : fOpacity(opacity), fLeftTopOffset(0, 0) { } Layer::~Layer() { } void Layer::PushLayer(Layer* layer) { PushPicture(layer); } Layer* Layer::PopLayer() { return static_cast(PopPicture()); } UtilityBitmap* Layer::RenderToBitmap(Canvas* canvas) { BRect boundingBox = _DetermineBoundingBox(canvas); if (!boundingBox.IsValid()) return NULL; fLeftTopOffset = boundingBox.LeftTop(); BReference layerBitmap(_AllocateBitmap(boundingBox), true); if (layerBitmap == NULL) return NULL; BitmapHWInterface layerInterface(layerBitmap); ObjectDeleter const layerEngine(layerInterface.CreateDrawingEngine()); if (!layerEngine.IsSet()) return NULL; layerEngine->SetRendererOffset(boundingBox.left, boundingBox.top); // Drawing commands of the layer's picture use coordinates in the // coordinate space of the underlying canvas. The coordinate origin // of the layer bitmap is at boundingBox.LeftTop(). So all the drawing // from the picture needs to be offset to be moved into the bitmap. // We use a low-level offsetting via the AGG renderer here because the // offset needs to be processed independently, after all other // transforms, even after the BAffineTransforms (which are processed in // Painter), to prevent this origin from being further transformed by // e.g. scaling. LayerCanvas layerCanvas(layerEngine.Get(), canvas->DetachDrawState(), boundingBox); AlphaMask* const mask = layerCanvas.GetAlphaMask(); IntPoint oldOffset; if (mask != NULL) { // Move alpha mask to bitmap origin oldOffset = mask->SetCanvasGeometry(IntPoint(0, 0), boundingBox); } layerCanvas.CurrentState()->SetDrawingMode(B_OP_ALPHA); layerCanvas.CurrentState()->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); layerCanvas.ResyncDrawState(); // Apply state to the new drawing engine of the layer canvas if (layerEngine->LockParallelAccess()) { layerCanvas.UpdateCurrentDrawingRegion(); // Draw recorded picture into bitmap Play(&layerCanvas); layerEngine->UnlockParallelAccess(); } if (mask != NULL) { // Move alpha mask back to its old position // Note: this needs to be adapted if setting alpha masks is // implemented as BPicture command (the mask now might be a different // one than before). layerCanvas.CurrentState()->CombinedTransform().Apply(oldOffset); mask->SetCanvasGeometry(oldOffset, boundingBox); layerCanvas.ResyncDrawState(); } canvas->SetDrawState(layerCanvas.DetachDrawState()); // Update state in canvas (the top-of-stack state could be a different // state instance now, if the picture commands contained push/pop // commands) return layerBitmap.Detach(); } IntPoint Layer::LeftTopOffset() const { return fLeftTopOffset; } uint8 Layer::Opacity() const { return fOpacity; } BRect Layer::_DetermineBoundingBox(Canvas* canvas) { BRect boundingBox; PictureBoundingBoxPlayer::Play(this, canvas->CurrentState(), &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; // TODO: for optimization, crop the bounding box to the underlying // view bounds here return boundingBox; } UtilityBitmap* Layer::_AllocateBitmap(const BRect& bounds) { BReference layerBitmap(new(std::nothrow) UtilityBitmap(bounds, B_RGBA32, 0), true); if (layerBitmap == NULL) return NULL; if (!layerBitmap->IsValid()) return NULL; memset(layerBitmap->Bits(), 0, layerBitmap->BitsLength()); return layerBitmap.Detach(); }