diff --git a/src/interface/commandline/main.cpp b/src/interface/commandline/main.cpp index 665d7ef..5de8824 100644 --- a/src/interface/commandline/main.cpp +++ b/src/interface/commandline/main.cpp @@ -8,6 +8,7 @@ #include "SoftwareCanvasRenderer.h" #include "Scenery.h" #include "RenderConfig.h" +#include "ColorProfile.h" void startRender(SoftwareCanvasRenderer *renderer, char *outputpath) { @@ -15,7 +16,7 @@ void startRender(SoftwareCanvasRenderer *renderer, char *outputpath) renderer->render(); printf("\rSaving %s ... \n", outputpath); remove(outputpath); - //renderer->render_area->saveToFile(outputpath); + renderer->saveToDisk(outputpath); } void displayHelp() diff --git a/src/interface/desktop/dialogrender.cpp b/src/interface/desktop/dialogrender.cpp index 5c66121..71620bf 100644 --- a/src/interface/desktop/dialogrender.cpp +++ b/src/interface/desktop/dialogrender.cpp @@ -22,6 +22,7 @@ #include "ColorProfile.h" #include "SoftwareCanvasRenderer.h" #include "WidgetPreviewCanvas.h" +#include "Canvas.h" class RenderThread:public QThread { @@ -150,12 +151,11 @@ void DialogRender::saveRender() { filepath = filepath.append(".png"); } - std::string filepathstr = filepath.toStdString(); - /*if (canvas_renderer->render_area->saveToFile((char*)filepathstr.c_str())) + if (canvas_renderer->saveToDisk(filepath.toStdString())) { QMessageBox::information(this, "Message", QString(tr("The picture %1 has been saved.")).arg(filepath)); } - else*/ + else { QMessageBox::critical(this, "Message", QString(tr("Can't write to file : %1")).arg(filepath)); } diff --git a/src/render/software/Canvas.cpp b/src/render/software/Canvas.cpp index 4a0c7f6..6c796f7 100644 --- a/src/render/software/Canvas.cpp +++ b/src/render/software/Canvas.cpp @@ -4,6 +4,7 @@ #include "CanvasPortion.h" #include "CanvasPreview.h" +#include "CanvasPictureWriter.h" Canvas::Canvas() { @@ -86,3 +87,24 @@ CanvasPortion *Canvas::at(int x, int y) const return portions[y * horizontal_portion_count + x]; } + +CanvasPortion *Canvas::atPixel(int x, int y) const +{ + assert(x >= 0 && x < width); + assert(y >= 0 && y < height); + + int pwidth = portions[0]->getWidth(); + int pheight = portions[0]->getHeight(); + + return at(x / pwidth, y / pheight); +} + +bool Canvas::saveToDisk(const std::string &filepath, const ColorProfile &profile, int antialias) const +{ + assert(antialias >= 1); + + CanvasPictureWriter writer(this); + writer.setColorProfile(profile); + writer.setAntialias(antialias); + return writer.saveCanvas(filepath); +} diff --git a/src/render/software/Canvas.h b/src/render/software/Canvas.h index edd8e80..c326bca 100644 --- a/src/render/software/Canvas.h +++ b/src/render/software/Canvas.h @@ -24,11 +24,19 @@ public: inline int getVerticalPortionCount() const {return vertical_portion_count;} CanvasPortion *at(int x, int y) const; + CanvasPortion *atPixel(int x, int y) const; inline int getWidth() const {return width;} inline int getHeight() const {return height;} inline CanvasPreview *getPreview() const {return preview;} + /** + * Save the canvas to a picture file on disk. + * + * Returns true if the save was successful. + */ + bool saveToDisk(const std::string &filepath, const ColorProfile &profile, int antialias) const; + private: std::vector portions; int horizontal_portion_count; diff --git a/src/render/software/CanvasPictureWriter.cpp b/src/render/software/CanvasPictureWriter.cpp new file mode 100644 index 0000000..b0cdb74 --- /dev/null +++ b/src/render/software/CanvasPictureWriter.cpp @@ -0,0 +1,89 @@ +#include "CanvasPictureWriter.h" + +#include + +#include "Canvas.h" +#include "CanvasPortion.h" +#include "ColorProfile.h" +#include "PackStream.h" + +CanvasPictureWriter::CanvasPictureWriter(const Canvas *canvas): + canvas(canvas) +{ + profile = new ColorProfile(); + antialias = 1; + width = canvas->getWidth(); + height = canvas->getHeight(); +} + +CanvasPictureWriter::~CanvasPictureWriter() +{ + delete profile; +} + +void CanvasPictureWriter::setAntialias(int antialias) +{ + assert(antialias >= 1); + assert(canvas->getWidth() % antialias == 0); + assert(canvas->getHeight() % antialias == 0); + + this->antialias = antialias; + this->width = canvas->getWidth() / antialias; + this->height = canvas->getHeight() / antialias; +} + +void CanvasPictureWriter::setColorProfile(const ColorProfile &profile) +{ + profile.copy(this->profile); +} + +bool CanvasPictureWriter::saveCanvas(const std::string &filepath) +{ + return save(filepath, width, height); +} + +unsigned int CanvasPictureWriter::getPixel(int x, int y) +{ + if (antialias > 1) + { + int basex = x * antialias; + int basey = y * antialias; + double factor = 1.0 / (antialias * antialias); + Color comp = COLOR_BLACK; + + for (int ix = 0; ix < antialias; ix++) + { + for (int iy = 0; iy < antialias; iy++) + { + Color col = getRawPixel(basex + ix, basey + iy); + comp.r += col.r * factor; + comp.g += col.g * factor; + comp.b += col.b * factor; + } + } + + return profile->apply(comp).to32BitBGRA(); + } + else + { + return profile->apply(getRawPixel(x, y)).to32BitBGRA(); + } +} + +Color CanvasPictureWriter::getRawPixel(int x, int y) +{ + // Get the portion this pixel is in + CanvasPortion *portion = canvas->atPixel(x, y); + + // Get the pack stream positioned at the pixel + PackStream stream; + if (not portion->getReadStream(stream, x - portion->getXOffset(), y - portion->getYOffset())) + { + return COLOR_BLACK; + } + + // Load the pixel and apply tone mapping + Color col; + col.load(&stream); + return col; +} diff --git a/src/render/software/CanvasPictureWriter.h b/src/render/software/CanvasPictureWriter.h new file mode 100644 index 0000000..6f7895f --- /dev/null +++ b/src/render/software/CanvasPictureWriter.h @@ -0,0 +1,54 @@ +#ifndef CANVASPICTUREWRITER_H +#define CANVASPICTUREWRITER_H + +#include "software_global.h" + +#include "PictureWriter.h" + +namespace paysages { +namespace software { + +/** + * Picture writer to create the final image from canvas portions. + */ +class SOFTWARESHARED_EXPORT CanvasPictureWriter: public PictureWriter +{ +public: + CanvasPictureWriter(const Canvas *canvas); + virtual ~CanvasPictureWriter(); + + /** + * Set the antialias factor, 1 for no antialiasing. + */ + void setAntialias(int antialias); + + /** + * Set the color profile to apply to final pixels. + */ + void setColorProfile(const ColorProfile &profile); + + /** + * Start the saving process. + * + * Returns true if saving was successful. + */ + bool saveCanvas(const std::string &filepath); + +protected: + virtual unsigned int getPixel(int x, int y) override; + +private: + Color getRawPixel(int x, int y); + +private: + const Canvas *canvas; + int antialias; + int width; + int height; + ColorProfile *profile; +}; + +} +} + +#endif // CANVASPICTUREWRITER_H diff --git a/src/render/software/CanvasPortion.cpp b/src/render/software/CanvasPortion.cpp index c29fec9..4aee76f 100644 --- a/src/render/software/CanvasPortion.cpp +++ b/src/render/software/CanvasPortion.cpp @@ -89,14 +89,14 @@ void CanvasPortion::saveToDisk() { if (pixels) { - std::string filepath = FileSystem::getTempFile("paysages_portion_" + std::to_string(index) + ".dat"); + filepath = FileSystem::getTempFile("paysages_portion_" + std::to_string(index) + ".dat"); PackStream stream; stream.bindToFile(filepath, true); stream.write(&width); stream.write(&height); - for (int x = 0; x < width; x++) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) { pixels[y * width + x].getComposite().save(&stream); } @@ -104,6 +104,23 @@ void CanvasPortion::saveToDisk() } } +bool CanvasPortion::getReadStream(PackStream &stream, int x, int y) +{ + if (FileSystem::isFile(filepath)) + { + int unused_i; + double unused_d; + stream.bindToFile(filepath); + stream.skip(unused_i, 2); + stream.skip(unused_d, (y * width + x - 1) * 4); + return true; + } + else + { + return false; + } +} + void CanvasPortion::pushFragment(int x, int y, const CanvasFragment &fragment) { CHECK_COORDINATES(); diff --git a/src/render/software/CanvasPortion.h b/src/render/software/CanvasPortion.h index 326b826..1131e16 100644 --- a/src/render/software/CanvasPortion.h +++ b/src/render/software/CanvasPortion.h @@ -46,6 +46,13 @@ public: */ void saveToDisk(); + /** + * Bind a stream to pixel data, and position it on a given pixel. + * + * Returns true if the stream was successfully located, false if it was not possible. + */ + bool getReadStream(PackStream &stream, int x=0, int y=0); + /** * Add a fragment to the pixel located at (x, y). * @@ -75,6 +82,7 @@ private: int yoffset; CanvasPixel *pixels; CanvasPreview *preview; + std::string filepath; }; } diff --git a/src/render/software/CanvasPreview.h b/src/render/software/CanvasPreview.h index 94dd0ce..20f4a35 100644 --- a/src/render/software/CanvasPreview.h +++ b/src/render/software/CanvasPreview.h @@ -17,6 +17,7 @@ public: inline int getWidth() const {return width;} inline int getHeight() const {return height;} + inline const ColorProfile *getToneMapping() const {return profile;} const Color &getFinalPixel(int x, int y) const; diff --git a/src/render/software/SoftwareCanvasRenderer.cpp b/src/render/software/SoftwareCanvasRenderer.cpp index 74803ef..922b12d 100644 --- a/src/render/software/SoftwareCanvasRenderer.cpp +++ b/src/render/software/SoftwareCanvasRenderer.cpp @@ -11,6 +11,8 @@ #include "CanvasPortion.h" #include "CanvasPixelShader.h" #include "RenderConfig.h" +#include "ColorProfile.h" +#include "CanvasPreview.h" SoftwareCanvasRenderer::SoftwareCanvasRenderer() { @@ -18,6 +20,7 @@ SoftwareCanvasRenderer::SoftwareCanvasRenderer() interrupted = false; canvas = new Canvas(); progress = 0.0; + samples = 1; rasterizers.push_back(new SkyRasterizer(this, 0)); rasterizers.push_back(new WaterRasterizer(this, 1)); @@ -50,6 +53,7 @@ void SoftwareCanvasRenderer::setSize(int width, int height, int samples) if (not started) { canvas->setSize(width * samples, height * samples); + this->samples = samples; } } @@ -112,6 +116,11 @@ const Rasterizer &SoftwareCanvasRenderer::getRasterizer(int client_id) const return *(rasterizers[client_id]); } +bool SoftwareCanvasRenderer::saveToDisk(const std::string &filepath) const +{ + return getCanvas()->saveToDisk(filepath, *getCanvas()->getPreview()->getToneMapping(), samples); +} + void SoftwareCanvasRenderer::rasterize(CanvasPortion *portion) { for (auto &rasterizer:rasterizers) diff --git a/src/render/software/SoftwareCanvasRenderer.h b/src/render/software/SoftwareCanvasRenderer.h index a40d69a..e157248 100644 --- a/src/render/software/SoftwareCanvasRenderer.h +++ b/src/render/software/SoftwareCanvasRenderer.h @@ -52,6 +52,13 @@ public: */ const Rasterizer &getRasterizer(int client_id) const; + /** + * Save the rendered canvas to a picture file on disk. + * + * Returns true if the save was successful. + */ + bool saveToDisk(const std::string &filepath) const; + protected: /** * @brief Rasterize the scenery into a canvas portion. @@ -68,6 +75,7 @@ private: double progress_segment; Canvas *canvas; + int samples; std::vector rasterizers; bool started; bool interrupted; diff --git a/src/render/software/software.pro b/src/render/software/software.pro index 4543bf3..15472e7 100644 --- a/src/render/software/software.pro +++ b/src/render/software/software.pro @@ -46,7 +46,8 @@ SOURCES += SoftwareRenderer.cpp \ CanvasLiveClient.cpp \ CanvasPreview.cpp \ RenderConfig.cpp \ - CanvasPixelShader.cpp + CanvasPixelShader.cpp \ + CanvasPictureWriter.cpp HEADERS += SoftwareRenderer.h\ software_global.h \ @@ -82,7 +83,8 @@ HEADERS += SoftwareRenderer.h\ CanvasLiveClient.h \ CanvasPreview.h \ RenderConfig.h \ - CanvasPixelShader.h + CanvasPixelShader.h \ + CanvasPictureWriter.h unix:!symbian { maemo5 { diff --git a/src/render/software/software_global.h b/src/render/software/software_global.h index 0aa9636..637b85d 100644 --- a/src/render/software/software_global.h +++ b/src/render/software/software_global.h @@ -54,6 +54,7 @@ namespace software { class CanvasLiveClient; class CanvasPreview; class CanvasPixelShader; + class CanvasPictureWriter; } } diff --git a/src/system/FileSystem.cpp b/src/system/FileSystem.cpp index 2ee3e45..ae43dc2 100644 --- a/src/system/FileSystem.cpp +++ b/src/system/FileSystem.cpp @@ -1,8 +1,14 @@ #include "FileSystem.h" #include +#include std::string FileSystem::getTempFile(const std::string &filename) { return QDir::temp().filePath(QString::fromStdString(filename)).toStdString(); } + +bool FileSystem::isFile(const std::string &filepath) +{ + return QFileInfo(QString::fromStdString(filepath)).exists(); +} diff --git a/src/system/FileSystem.h b/src/system/FileSystem.h index afd2b3f..f59e596 100644 --- a/src/system/FileSystem.h +++ b/src/system/FileSystem.h @@ -15,6 +15,11 @@ public: * filename must not contain directory separators. */ static std::string getTempFile(const std::string &filename); + + /** + * Returns true if the given path points to a file. + */ + static bool isFile(const std::string &filepath); }; } diff --git a/src/system/PackStream.cpp b/src/system/PackStream.cpp index d7f9149..49010b1 100644 --- a/src/system/PackStream.cpp +++ b/src/system/PackStream.cpp @@ -114,3 +114,13 @@ std::string PackStream::readString() return ""; } } + +void PackStream::skip(const int &value, int count) +{ + stream->skipRawData(sizeof(value) * count); +} + +void PackStream::skip(const double &value, int count) +{ + stream->skipRawData(sizeof(value) * count); +} diff --git a/src/system/PackStream.h b/src/system/PackStream.h index 127adf6..2de2d03 100644 --- a/src/system/PackStream.h +++ b/src/system/PackStream.h @@ -32,6 +32,9 @@ public: void read(char* value, int max_length); std::string readString(); + void skip(const int &value, int count=1); + void skip(const double &value, int count=1); + private: QFile* file; QDataStream* stream; diff --git a/src/tests/PackStream_Test.cpp b/src/tests/PackStream_Test.cpp index d2e7407..92b5c4e 100644 --- a/src/tests/PackStream_Test.cpp +++ b/src/tests/PackStream_Test.cpp @@ -49,3 +49,46 @@ TEST(PackStream, All) } delete stream; } + +TEST(PackStream, Skip) +{ + PackStream* stream; + int i1=1, i2=2, i3=3; + double d1=1.1, d2=2.2; + + stream = new PackStream(); + stream->bindToFile("/tmp/test_paysages_pack", true); + + stream->write(&i1); + stream->write(&i2); + stream->write(&d1); + stream->write(&d2); + stream->write(&i3); + + delete stream; + + int resi; + double resd; + + stream = new PackStream(); + stream->bindToFile("/tmp/test_paysages_pack"); + stream->skip(i1, 1); + stream->read(&resi); + EXPECT_EQ(2, resi); + delete stream; + + stream = new PackStream(); + stream->bindToFile("/tmp/test_paysages_pack"); + stream->skip(i1, 2); + stream->read(&resd); + EXPECT_DOUBLE_EQ(1.1, resd); + delete stream; + + stream = new PackStream(); + stream->bindToFile("/tmp/test_paysages_pack"); + stream->skip(i1, 2); + stream->skip(d1, 2); + stream->read(&resi); + EXPECT_EQ(3, resi); + delete stream; +}