Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
474 lines
10 KiB
C++
474 lines
10 KiB
C++
/* SPDX-FileCopyrightText: 2008-2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup freestyle
|
|
* \brief Class to define a canvas designed to draw style modules
|
|
*/
|
|
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "Canvas.h"
|
|
#include "StrokeRenderer.h"
|
|
#include "StyleModule.h"
|
|
|
|
#include "../image/GaussianFilter.h"
|
|
#include "../image/Image.h"
|
|
#include "../image/ImagePyramid.h"
|
|
|
|
#include "../system/FreestyleConfig.h"
|
|
#include "../system/PseudoNoise.h"
|
|
#include "../system/TimeStamp.h"
|
|
|
|
#include "../view_map/SteerableViewMap.h"
|
|
|
|
#include "BLI_sys_types.h"
|
|
|
|
#include "BKE_global.h"
|
|
|
|
// soc #include <qimage.h>
|
|
// soc #include <QString>
|
|
|
|
#include "IMB_imbuf.h"
|
|
#include "IMB_imbuf_types.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace Freestyle {
|
|
|
|
Canvas *Canvas::_pInstance = nullptr;
|
|
|
|
const char *Canvas::_MapsPath = nullptr;
|
|
|
|
Canvas::Canvas()
|
|
{
|
|
_SelectedFEdge = nullptr;
|
|
_pInstance = this;
|
|
PseudoNoise::init(42);
|
|
_Renderer = nullptr;
|
|
_current_sm = nullptr;
|
|
_steerableViewMap = new SteerableViewMap(NB_STEERABLE_VIEWMAP - 1);
|
|
_basic = false;
|
|
}
|
|
|
|
Canvas::Canvas(const Canvas &iBrother)
|
|
{
|
|
_SelectedFEdge = iBrother._SelectedFEdge;
|
|
_pInstance = this;
|
|
PseudoNoise::init(42);
|
|
_Renderer = iBrother._Renderer;
|
|
_current_sm = iBrother._current_sm;
|
|
_steerableViewMap = new SteerableViewMap(*(iBrother._steerableViewMap));
|
|
_basic = iBrother._basic;
|
|
}
|
|
|
|
Canvas::~Canvas()
|
|
{
|
|
_pInstance = nullptr;
|
|
|
|
Clear();
|
|
if (_Renderer) {
|
|
delete _Renderer;
|
|
_Renderer = nullptr;
|
|
}
|
|
// FIXME: think about an easy control for the maps memory management...
|
|
if (!_maps.empty()) {
|
|
for (mapsMap::iterator m = _maps.begin(), mend = _maps.end(); m != mend; ++m) {
|
|
delete ((*m).second);
|
|
}
|
|
_maps.clear();
|
|
}
|
|
delete _steerableViewMap;
|
|
}
|
|
|
|
void Canvas::preDraw() {}
|
|
|
|
void Canvas::Draw()
|
|
{
|
|
if (_StyleModules.empty()) {
|
|
return;
|
|
}
|
|
preDraw();
|
|
TimeStamp *timestamp = TimeStamp::instance();
|
|
|
|
for (uint i = 0; i < _StyleModules.size(); ++i) {
|
|
_current_sm = _StyleModules[i];
|
|
|
|
if (i < _Layers.size() && _Layers[i]) {
|
|
delete _Layers[i];
|
|
}
|
|
|
|
_Layers[i] = _StyleModules[i]->execute();
|
|
if (!_Layers[i]) {
|
|
continue;
|
|
}
|
|
|
|
stroke_count += _Layers[i]->strokes_size();
|
|
|
|
timestamp->increment();
|
|
}
|
|
postDraw();
|
|
}
|
|
|
|
void Canvas::postDraw()
|
|
{
|
|
update();
|
|
}
|
|
|
|
void Canvas::Clear()
|
|
{
|
|
if (!_Layers.empty()) {
|
|
for (deque<StrokeLayer *>::iterator sl = _Layers.begin(), slend = _Layers.end(); sl != slend;
|
|
++sl) {
|
|
if (*sl) {
|
|
delete (*sl);
|
|
}
|
|
}
|
|
_Layers.clear();
|
|
}
|
|
|
|
if (!_StyleModules.empty()) {
|
|
for (deque<StyleModule *>::iterator s = _StyleModules.begin(), send = _StyleModules.end();
|
|
s != send;
|
|
++s)
|
|
{
|
|
if (*s) {
|
|
delete (*s);
|
|
}
|
|
}
|
|
_StyleModules.clear();
|
|
}
|
|
if (_steerableViewMap) {
|
|
_steerableViewMap->Reset();
|
|
}
|
|
|
|
stroke_count = 0;
|
|
}
|
|
|
|
void Canvas::Erase()
|
|
{
|
|
if (!_Layers.empty()) {
|
|
for (deque<StrokeLayer *>::iterator sl = _Layers.begin(), slend = _Layers.end(); sl != slend;
|
|
++sl) {
|
|
if (*sl) {
|
|
(*sl)->clear();
|
|
}
|
|
}
|
|
}
|
|
if (_steerableViewMap) {
|
|
_steerableViewMap->Reset();
|
|
}
|
|
update();
|
|
|
|
stroke_count = 0;
|
|
}
|
|
|
|
void Canvas::PushBackStyleModule(StyleModule *iStyleModule)
|
|
{
|
|
StrokeLayer *layer = new StrokeLayer();
|
|
_StyleModules.push_back(iStyleModule);
|
|
_Layers.push_back(layer);
|
|
}
|
|
|
|
void Canvas::InsertStyleModule(uint index, StyleModule *iStyleModule)
|
|
{
|
|
uint size = _StyleModules.size();
|
|
StrokeLayer *layer = new StrokeLayer();
|
|
if (_StyleModules.empty() || (index == size)) {
|
|
_StyleModules.push_back(iStyleModule);
|
|
_Layers.push_back(layer);
|
|
return;
|
|
}
|
|
_StyleModules.insert(_StyleModules.begin() + index, iStyleModule);
|
|
_Layers.insert(_Layers.begin() + index, layer);
|
|
}
|
|
|
|
void Canvas::RemoveStyleModule(uint index)
|
|
{
|
|
uint i = 0;
|
|
if (!_StyleModules.empty()) {
|
|
for (deque<StyleModule *>::iterator s = _StyleModules.begin(), send = _StyleModules.end();
|
|
s != send;
|
|
++s, ++i)
|
|
{
|
|
if (i == index) {
|
|
// remove shader
|
|
if (*s) {
|
|
delete *s;
|
|
}
|
|
_StyleModules.erase(s);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!_Layers.empty()) {
|
|
i = 0;
|
|
for (deque<StrokeLayer *>::iterator sl = _Layers.begin(), slend = _Layers.end(); sl != slend;
|
|
++sl, ++i)
|
|
{
|
|
if (i == index) {
|
|
// remove layer
|
|
if (*sl) {
|
|
delete *sl;
|
|
}
|
|
_Layers.erase(sl);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Canvas::SwapStyleModules(uint i1, uint i2)
|
|
{
|
|
StyleModule *tmp;
|
|
tmp = _StyleModules[i1];
|
|
_StyleModules[i1] = _StyleModules[i2];
|
|
_StyleModules[i2] = tmp;
|
|
|
|
StrokeLayer *tmp2;
|
|
tmp2 = _Layers[i1];
|
|
_Layers[i1] = _Layers[i2];
|
|
_Layers[i2] = tmp2;
|
|
}
|
|
|
|
void Canvas::ReplaceStyleModule(uint index, StyleModule *iStyleModule)
|
|
{
|
|
uint i = 0;
|
|
for (deque<StyleModule *>::iterator s = _StyleModules.begin(), send = _StyleModules.end();
|
|
s != send;
|
|
++s, ++i)
|
|
{
|
|
if (i == index) {
|
|
if (*s) {
|
|
delete *s;
|
|
}
|
|
*s = iStyleModule;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Canvas::setVisible(uint index, bool iVisible)
|
|
{
|
|
_StyleModules[index]->setDisplayed(iVisible);
|
|
}
|
|
|
|
void Canvas::setModified(uint index, bool iMod)
|
|
{
|
|
_StyleModules[index]->setModified(iMod);
|
|
}
|
|
|
|
void Canvas::resetModified(bool iMod /* = false */)
|
|
{
|
|
uint size = _StyleModules.size();
|
|
for (uint i = 0; i < size; ++i) {
|
|
setModified(i, iMod);
|
|
}
|
|
}
|
|
|
|
void Canvas::causalStyleModules(vector<uint> &vec, uint index)
|
|
{
|
|
uint size = _StyleModules.size();
|
|
|
|
for (uint i = index; i < size; ++i) {
|
|
if (_StyleModules[i]->getCausal()) {
|
|
vec.push_back(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Canvas::Render(const StrokeRenderer *iRenderer)
|
|
{
|
|
for (uint i = 0; i < _StyleModules.size(); ++i) {
|
|
if (!_StyleModules[i]->getDisplayed() || !_Layers[i]) {
|
|
continue;
|
|
}
|
|
_Layers[i]->Render(iRenderer);
|
|
}
|
|
}
|
|
|
|
void Canvas::RenderBasic(const StrokeRenderer *iRenderer)
|
|
{
|
|
for (uint i = 0; i < _StyleModules.size(); ++i) {
|
|
if (!_StyleModules[i]->getDisplayed() || !_Layers[i]) {
|
|
continue;
|
|
}
|
|
_Layers[i]->RenderBasic(iRenderer);
|
|
}
|
|
}
|
|
|
|
void Canvas::loadMap(const char *iFileName, const char *iMapName, uint iNbLevels, float iSigma)
|
|
{
|
|
// check whether this map was already loaded:
|
|
if (!_maps.empty()) {
|
|
mapsMap::iterator m = _maps.find(iMapName);
|
|
if (m != _maps.end()) {
|
|
// lazy check for size changes
|
|
ImagePyramid *pyramid = (*m).second;
|
|
if ((pyramid->width() != width()) || (pyramid->height() != height())) {
|
|
delete pyramid;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
string filePath;
|
|
if (_MapsPath) {
|
|
filePath = _MapsPath;
|
|
filePath += iFileName;
|
|
}
|
|
else {
|
|
filePath = iFileName;
|
|
}
|
|
|
|
#if 0 // soc
|
|
QImage *qimg;
|
|
QImage newMap(filePath.c_str());
|
|
if (newMap.isNull()) {
|
|
cerr << "Could not load image file " << filePath << endl;
|
|
return;
|
|
}
|
|
qimg = &newMap;
|
|
#endif
|
|
/* OCIO_TODO: support different input color space */
|
|
ImBuf *qimg = IMB_loadiffname(filePath.c_str(), 0, nullptr);
|
|
if (qimg == nullptr) {
|
|
cerr << "Could not load image file " << filePath << endl;
|
|
return;
|
|
}
|
|
|
|
#if 0 // soc
|
|
// resize
|
|
QImage scaledImg;
|
|
if ((newMap.width() != width()) || (newMap.height() != height())) {
|
|
scaledImg = newMap.scaled(width(), height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
|
qimg = &scaledImg;
|
|
}
|
|
#endif
|
|
ImBuf *scaledImg;
|
|
if ((qimg->x != width()) || (qimg->y != height())) {
|
|
scaledImg = IMB_dupImBuf(qimg);
|
|
IMB_scaleImBuf(scaledImg, width(), height());
|
|
}
|
|
|
|
// deal with color image
|
|
#if 0
|
|
if (newMap->depth() != 8) {
|
|
int w = newMap->width();
|
|
int h = newMap->height();
|
|
QImage *tmp = new QImage(w, h, 8);
|
|
for (uint y = 0; y < h; ++y) {
|
|
for (uint x = 0; x < w; ++x) {
|
|
int c = qGray(newMap->pixel(x, y));
|
|
tmp->setPixel(x, y, c);
|
|
}
|
|
}
|
|
delete newMap;
|
|
newMap = tmp;
|
|
}
|
|
#endif
|
|
|
|
int x, y;
|
|
int w = qimg->x;
|
|
int h = qimg->y;
|
|
int rowbytes = w * 4;
|
|
GrayImage tmp(w, h);
|
|
uchar *pix;
|
|
|
|
for (y = 0; y < h; ++y) {
|
|
for (x = 0; x < w; ++x) {
|
|
pix = qimg->byte_buffer.data + y * rowbytes + x * 4;
|
|
float c = (pix[0] * 11 + pix[1] * 16 + pix[2] * 5) / 32;
|
|
tmp.setPixel(x, y, c);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
GrayImage blur(w, h);
|
|
GaussianFilter gf(4.0f);
|
|
// int bound = gf.getBound();
|
|
for (y = 0; y < h; ++y) {
|
|
for (x = 0; x < w; ++x) {
|
|
int c = gf.getSmoothedPixel<GrayImage>(&tmp, x, y);
|
|
blur.setPixel(x, y, c);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
GaussianPyramid *pyramid = new GaussianPyramid(tmp, iNbLevels, iSigma);
|
|
int ow = pyramid->width(0);
|
|
int oh = pyramid->height(0);
|
|
string base(iMapName); // soc
|
|
for (int i = 0; i < pyramid->getNumberOfLevels(); ++i) {
|
|
// save each image:
|
|
#if 0
|
|
w = pyramid.width(i);
|
|
h = pyramid.height(i);
|
|
#endif
|
|
|
|
// soc QImage qtmp(ow, oh, QImage::Format_RGB32);
|
|
ImBuf *qtmp = IMB_allocImBuf(ow, oh, 32, IB_rect);
|
|
|
|
// int k = (1 << i);
|
|
for (y = 0; y < oh; ++y) {
|
|
for (x = 0; x < ow; ++x) {
|
|
int c = pyramid->pixel(x, y, i); // 255 * pyramid->pixel(x, y, i);
|
|
// soc qtmp.setPixel(x, y, qRgb(c, c, c));
|
|
pix = qtmp->byte_buffer.data + y * rowbytes + x * 4;
|
|
pix[0] = pix[1] = pix[2] = c;
|
|
}
|
|
}
|
|
// soc qtmp.save(base + QString::number(i) + ".bmp", "BMP");
|
|
stringstream filepath;
|
|
filepath << base;
|
|
filepath << i << ".bmp";
|
|
qtmp->ftype = IMB_FTYPE_BMP;
|
|
IMB_saveiff(qtmp, const_cast<char *>(filepath.str().c_str()), 0);
|
|
}
|
|
|
|
#if 0
|
|
QImage *qtmp = new QImage(w, h, 32);
|
|
for (y = 0; y < h; ++y) {
|
|
for (x = 0; x < w; ++x) {
|
|
int c = int(blur.pixel(x, y));
|
|
qtmp->setPixel(x, y, qRgb(c, c, c));
|
|
}
|
|
}
|
|
delete newMap;
|
|
newMap = qtmp;
|
|
#endif
|
|
|
|
_maps[iMapName] = pyramid;
|
|
// newMap->save("toto.bmp", "BMP");
|
|
}
|
|
|
|
float Canvas::readMapPixel(const char *iMapName, int level, int x, int y)
|
|
{
|
|
if (_maps.empty()) {
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
cout << "readMapPixel warning: no map was loaded " << endl;
|
|
}
|
|
return -1;
|
|
}
|
|
mapsMap::iterator m = _maps.find(iMapName);
|
|
if (m == _maps.end()) {
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
cout << "readMapPixel warning: no map was loaded with the name " << iMapName << endl;
|
|
}
|
|
return -1;
|
|
}
|
|
ImagePyramid *pyramid = (*m).second;
|
|
if ((x < 0) || (x >= pyramid->width()) || (y < 0) || (y >= pyramid->height())) {
|
|
return 0;
|
|
}
|
|
|
|
return pyramid->pixel(x, height() - 1 - y, level);
|
|
}
|
|
|
|
} /* namespace Freestyle */
|