Mercurial > hg > orthanc-stone
view Framework/Widgets/LayeredSceneWidget.cpp @ 20:946377d1c992
skeleton for unit tests
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 17 Nov 2016 14:19:00 +0100 |
parents | ff1e935768e7 |
children | 7207a407bcd8 |
line wrap: on
line source
/** * Stone of Orthanc * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * In addition, as a special exception, the copyright holders of this * program give permission to link the code of its release with the * OpenSSL project's "OpenSSL" library (or with modified versions of it * that use the same license as the "OpenSSL" library), and distribute * the linked executables. You must obey the GNU General Public License * in all respects for all of the code used other than "OpenSSL". If you * modify file(s) with this exception, you may extend this exception to * your version of the file(s), but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source files * in the program, then also delete it here. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #define _USE_MATH_DEFINES // To access M_PI in Visual Studio #include <cmath> #include "LayeredSceneWidget.h" #include "../../Resources/Orthanc/Core/OrthancException.h" namespace OrthancStone { class LayeredSceneWidget::Renderers : public boost::noncopyable { private: boost::mutex mutex_; std::vector<ILayerRenderer*> renderers_; std::vector<bool> assigned_; void Assign(size_t index, ILayerRenderer* renderer) { if (index >= renderers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } if (renderers_[index] != NULL) { delete renderers_[index]; } renderers_[index] = renderer; assigned_[index] = true; } public: Renderers(size_t size) { renderers_.resize(size); assigned_.resize(size, false); } ~Renderers() { for (size_t i = 0; i < renderers_.size(); i++) { Assign(i, NULL); } } static void Merge(Renderers& target, Renderers& source) { boost::mutex::scoped_lock lockSource(source.mutex_); boost::mutex::scoped_lock lockTarget(target.mutex_); size_t count = target.renderers_.size(); if (count != source.renderers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } for (size_t i = 0; i < count; i++) { if (source.assigned_[i]) { target.Assign(i, source.renderers_[i]); // Transfers ownership source.renderers_[i] = NULL; source.assigned_[i] = false; } } } void SetRenderer(size_t index, ILayerRenderer* renderer) // Takes ownership { boost::mutex::scoped_lock lock(mutex_); Assign(index, renderer); } bool RenderScene(CairoContext& context, const ViewportGeometry& view) { boost::mutex::scoped_lock lock(mutex_); bool fullQuality = true; for (size_t i = 0; i < renderers_.size(); i++) { if (renderers_[i] != NULL && !renderers_[i]->RenderLayer(context, view)) { return false; } if (renderers_[i] != NULL && !renderers_[i]->IsFullQuality()) { fullQuality = false; } } if (!fullQuality) { double x, y; view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10); cairo_t *cr = context.GetObject(); cairo_translate(cr, x, y); cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2 * M_PI); cairo_set_line_width(cr, 2.0 / view.GetZoom()); cairo_set_source_rgb(cr, 1, 1, 1); cairo_stroke_preserve(cr); cairo_set_source_rgb(cr, 1, 0, 0); cairo_fill(cr); } return true; } void SetLayerStyle(size_t index, const RenderStyle& style) { boost::mutex::scoped_lock lock(mutex_); if (renderers_[index] != NULL) { renderers_[index]->SetLayerStyle(style); } } }; class LayeredSceneWidget::PendingLayers : public boost::noncopyable { private: boost::mutex mutex_; boost::condition_variable elementAvailable_; size_t layerCount_; std::list<size_t> queue_; std::vector<bool> layersToUpdate_; bool continue_; void TagAllLayers() { queue_.clear(); for (unsigned int i = 0; i < layerCount_; i++) { queue_.push_back(i); layersToUpdate_[i] = true; } if (layerCount_ != 0) { elementAvailable_.notify_one(); } } public: PendingLayers() : layerCount_(0), continue_(true) { } void Stop() { continue_ = false; elementAvailable_.notify_one(); } void SetLayerCount(size_t count) { boost::mutex::scoped_lock lock(mutex_); layerCount_ = count; layersToUpdate_.resize(count); TagAllLayers(); } void InvalidateAllLayers() { boost::mutex::scoped_lock lock(mutex_); TagAllLayers(); } void InvalidateLayer(size_t layer) { boost::mutex::scoped_lock lock(mutex_); if (layer < layerCount_) { if (layersToUpdate_[layer]) { // The layer is already scheduled for update, ignore this // invalidation } else { queue_.push_back(layer); layersToUpdate_[layer] = true; elementAvailable_.notify_one(); } } } bool Dequeue(size_t& layer, bool& isLast) { boost::mutex::scoped_lock lock(mutex_); // WARNING: Do NOT use "timed_wait" on condition variables, as // sleeping is not properly supported by Boost for Google NaCl while (queue_.empty() && continue_) { elementAvailable_.wait(lock); } if (!continue_) { return false; } layer = queue_.front(); layersToUpdate_[layer] = false; queue_.pop_front(); isLast = queue_.empty(); return true; } }; class LayeredSceneWidget::Layer : public ISliceableVolume::IChangeObserver { private: boost::mutex mutex_; std::auto_ptr<ILayerRendererFactory> factory_; PendingLayers& layers_; size_t index_; std::auto_ptr<RenderStyle> style_; public: Layer(ILayerRendererFactory* factory, PendingLayers& layers, size_t index) : factory_(factory), layers_(layers), index_(index) { if (factory == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } } virtual void NotifyChange(const OrthancStone::ISliceableVolume&) { layers_.InvalidateLayer(index_); } void Start() { if (factory_->HasSourceVolume()) { factory_->GetSourceVolume().Register(*this); } } void Stop() { if (factory_->HasSourceVolume()) { factory_->GetSourceVolume().Unregister(*this); } } bool GetExtent(double& x1, double& y1, double& x2, double& y2, const SliceGeometry& displaySlice) { boost::mutex::scoped_lock lock(mutex_); assert(factory_.get() != NULL); return factory_->GetExtent(x1, y1, x2, y2, displaySlice); } RenderStyle GetStyle() { boost::mutex::scoped_lock lock(mutex_); if (style_.get() == NULL) { return RenderStyle(); } else { return *style_; } } void SetStyle(const RenderStyle& style) { boost::mutex::scoped_lock lock(mutex_); style_.reset(new RenderStyle(style)); } ILayerRenderer* CreateRenderer(const SliceGeometry& displaySlice) { boost::mutex::scoped_lock lock(mutex_); assert(factory_.get() != NULL); std::auto_ptr<ILayerRenderer> renderer(factory_->CreateLayerRenderer(displaySlice)); if (renderer.get() != NULL && style_.get() != NULL) { renderer->SetLayerStyle(*style_); } return renderer.release(); } }; SliceGeometry LayeredSceneWidget::GetSlice() { boost::mutex::scoped_lock lock(sliceMutex_); return slice_; } void LayeredSceneWidget::UpdateStep() { size_t layer = 0; bool isLast = true; if (!pendingLayers_->Dequeue(layer, isLast)) { return; } SliceGeometry slice = GetSlice(); std::auto_ptr<ILayerRenderer> renderer; renderer.reset(layers_[layer]->CreateRenderer(slice)); if (renderer.get() != NULL) { pendingRenderers_->SetRenderer(layer, renderer.release()); } else { pendingRenderers_->SetRenderer(layer, NULL); } if (isLast) { Renderers::Merge(*renderers_, *pendingRenderers_); NotifyChange(); } } bool LayeredSceneWidget::RenderScene(CairoContext& context, const ViewportGeometry& view) { assert(IsStarted()); return renderers_->RenderScene(context, view); } LayeredSceneWidget::LayeredSceneWidget() { pendingLayers_.reset(new PendingLayers); SetBackgroundCleared(true); } LayeredSceneWidget::~LayeredSceneWidget() { for (size_t i = 0; i < layers_.size(); i++) { assert(layers_[i] != NULL); delete layers_[i]; } } void LayeredSceneWidget::GetSceneExtent(double& x1, double& y1, double& x2, double& y2) { boost::mutex::scoped_lock lock(sliceMutex_); bool first = true; for (size_t i = 0; i < layers_.size(); i++) { double ax, ay, bx, by; assert(layers_[i] != NULL); if (layers_[i]->GetExtent(ax, ay, bx, by, slice_)) { if (ax > bx) { std::swap(ax, bx); } if (ay > by) { std::swap(ay, by); } if (first) { x1 = ax; y1 = ay; x2 = bx; y2 = by; first = false; } else { x1 = std::min(x1, ax); y1 = std::min(y1, ay); x2 = std::max(x2, bx); y2 = std::max(y2, by); } } } if (first) { x1 = -1; y1 = -1; x2 = 1; y2 = 1; } // Ensure the extent is non-empty if (x1 >= x2) { double tmp = x1; x1 = tmp - 0.5; x2 = tmp + 0.5; } if (y1 >= y2) { double tmp = y1; y1 = tmp - 0.5; y2 = tmp + 0.5; } } ILayerRendererFactory& LayeredSceneWidget::AddLayer(size_t& layerIndex, ILayerRendererFactory* factory) { if (IsStarted()) { // Start() has already been invoked throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } layerIndex = layers_.size(); layers_.push_back(new Layer(factory, *pendingLayers_, layers_.size())); return *factory; } void LayeredSceneWidget::AddLayer(ILayerRendererFactory* factory) { size_t layerIndex; // Ignored AddLayer(layerIndex, factory); } RenderStyle LayeredSceneWidget::GetLayerStyle(size_t layer) { if (layer >= layers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } return layers_[layer]->GetStyle(); } void LayeredSceneWidget::SetLayerStyle(size_t layer, const RenderStyle& style) { if (layer >= layers_.size()) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } layers_[layer]->SetStyle(style); if (renderers_.get() != NULL) { renderers_->SetLayerStyle(layer, style); } InvalidateLayer(layer); } struct LayeredSceneWidget::SliceChangeFunctor { const SliceGeometry& slice_; SliceChangeFunctor(const SliceGeometry& slice) : slice_(slice) { } void operator() (ISliceObserver& observer, const LayeredSceneWidget& source) { observer.NotifySliceChange(source, slice_); } }; void LayeredSceneWidget::SetSlice(const SliceGeometry& slice) { { boost::mutex::scoped_lock lock(sliceMutex_); slice_ = slice; } InvalidateAllLayers(); SliceChangeFunctor functor(slice); observers_.Notify(this, functor); } void LayeredSceneWidget::InvalidateLayer(unsigned int layer) { pendingLayers_->InvalidateLayer(layer); //NotifyChange(); // TODO Understand why this makes the SDL engine not update the display subsequently } void LayeredSceneWidget::InvalidateAllLayers() { pendingLayers_->InvalidateAllLayers(); //NotifyChange(); // TODO Understand why this makes the SDL engine not update the display subsequently } void LayeredSceneWidget::Start() { if (IsStarted()) { // Start() has already been invoked throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } for (size_t i = 0; i < layers_.size(); i++) { layers_[i]->Start(); } renderers_.reset(new Renderers(layers_.size())); pendingRenderers_.reset(new Renderers(layers_.size())); pendingLayers_->SetLayerCount(layers_.size()); WorldSceneWidget::Start(); } void LayeredSceneWidget::Stop() { if (!IsStarted()) { // Stop() has already been invoked throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } pendingLayers_->Stop(); WorldSceneWidget::Stop(); renderers_.reset(NULL); pendingRenderers_.reset(NULL); for (size_t i = 0; i < layers_.size(); i++) { layers_[i]->Stop(); } } }