Makefile.am | 3 common/Session.cpp | 16 ++ common/Session.hpp | 12 + kit/ChildSession.hpp | 2 kit/Kit.cpp | 224 ++++++---------------------------- kit/Watermark.hpp | 178 +++++++++++++++++++++++++++ loleaflet/src/core/Socket.js | 3 loleaflet/src/layer/tile/TileLayer.js | 2 wsd/ClientSession.cpp | 7 - wsd/DocumentBroker.cpp | 4 10 files changed, 264 insertions(+), 187 deletions(-)
New commits: commit ed5d304a6e17d98569a75fa9b73639ee07dbdee6 Author: Mert Tumer <mert.tu...@collabora.com> AuthorDate: Wed Oct 16 14:00:16 2019 +0300 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Sat Oct 19 20:14:08 2019 +0200 Moved class Watermark to a seperate file Added hash method for watermarktext and find it from normalizedviewid for rendering watermarks on the correct tiles Change-Id: I4b22ae1493d2957afb93028d1ece967cf766268f Reviewed-on: https://gerrit.libreoffice.org/81047 Reviewed-by: Andras Timar <andras.ti...@collabora.com> Tested-by: Andras Timar <andras.ti...@collabora.com> diff --git a/Makefile.am b/Makefile.am index 14d64158a..dea71963d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -220,7 +220,8 @@ kit_headers = kit/ChildSession.hpp \ kit/Delta.hpp \ kit/DummyLibreOfficeKit.hpp \ kit/Kit.hpp \ - kit/KitHelper.hpp + kit/KitHelper.hpp \ + kit/Watermark.hpp noinst_HEADERS = $(wsd_headers) $(shared_headers) $(kit_headers) \ bundled/include/LibreOfficeKit/LibreOfficeKit.h \ diff --git a/common/Session.cpp b/common/Session.cpp index f7ed7f33f..040bbc25e 100644 --- a/common/Session.cpp +++ b/common/Session.cpp @@ -250,6 +250,22 @@ void Session::getIOStats(uint64_t &sent, uint64_t &recv) } } + void Session::setHash(const std::string& text) + { + int hash = 0x811C9DC5; + int prime = 0x1000193; + + if (!text.empty()) + { + for (unsigned int i = 0; i < text.length(); ++i) + { + hash += hash ^ text[i]; + hash *= prime; + } + } + _hash = abs(hash); + } + void Session::dumpState(std::ostream& os) { WebSocketHandler::dumpState(os); diff --git a/common/Session.hpp b/common/Session.hpp index 49f48b2e9..1934c9465 100644 --- a/common/Session.hpp +++ b/common/Session.hpp @@ -122,6 +122,13 @@ public: const std::string& getJailedFilePathAnonym() const { return _jailedFilePathAnonym; } + int getHash() { return _hash; } + + void setHash(const std::string& text); + + void setHash(const int hash) { _hash = hash; }; + + protected: Session(const std::string& name, const std::string& id, bool readonly); virtual ~Session(); @@ -212,6 +219,11 @@ private: /// Language for the document based on what the user has in the UI. std::string _lang; + + /// Hash for normalizedViewId which is basically an identity for the tile to + /// choose what to render on and send it to its subscribers + /// it is the close-to-unique integer representation of a string like Watermarks etc. + int _hash; }; #endif diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp index f6bcf60a7..bbcc2c773 100644 --- a/kit/ChildSession.hpp +++ b/kit/ChildSession.hpp @@ -24,6 +24,7 @@ #include "Kit.hpp" #include "Session.hpp" +class Watermark; class ChildSession; enum class LokEventTargetEnum @@ -238,6 +239,7 @@ public: } using Session::sendTextFrame; + std::shared_ptr<Watermark> _docWatermark; private: bool loadDocument(const char* buffer, int length, const std::vector<std::string>& tokens); diff --git a/kit/Kit.cpp b/kit/Kit.cpp index ad0df946e..3adb5347e 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -67,6 +67,7 @@ #include <UserMessages.hpp> #include <Util.hpp> #include "Delta.hpp" +#include "Watermark.hpp" #ifndef MOBILEAPP #include <common/SigUtil.hpp> @@ -629,171 +630,6 @@ public: } }; -class Watermark -{ -public: - Watermark(const std::shared_ptr<lok::Document>& loKitDoc, const std::string& text, - const std::shared_ptr<ChildSession> & session) - : _loKitDoc(loKitDoc) - , _text(text) - , _font("Liberation Sans") - , _width(0) - , _height(0) - , _alphaLevel(session->getWatermarkOpacity()) - { - } - - ~Watermark() - { - } - - void blending(unsigned char* tilePixmap, - int offsetX, int offsetY, - int tilesPixmapWidth, int tilesPixmapHeight, - int tileWidth, int tileHeight, - LibreOfficeKitTileMode /*mode*/) - { - // set requested watermark size a little bit smaller than tile size - int width = tileWidth * 0.9; - int height = tileHeight * 0.9; - - const std::vector<unsigned char>* pixmap = getPixmap(width, height); - - if (pixmap && tilePixmap) - { - // center watermark - const int maxX = std::min(tileWidth, _width); - const int maxY = std::min(tileHeight, _height); - offsetX += (tileWidth - maxX) / 2; - offsetY += (tileHeight - maxY) / 2; - - alphaBlend(*pixmap, _width, _height, offsetX, offsetY, tilePixmap, tilesPixmapWidth, tilesPixmapHeight); - } - } - -private: - /// Alpha blend pixels from 'from' over the 'to'. - void alphaBlend(const std::vector<unsigned char>& from, int from_width, int from_height, int from_offset_x, int from_offset_y, - unsigned char* to, int to_width, int to_height) - { - for (int to_y = from_offset_y, from_y = 0; (to_y < to_height) && (from_y < from_height) ; ++to_y, ++from_y) - for (int to_x = from_offset_x, from_x = 0; (to_x < to_width) && (from_x < from_width); ++to_x, ++from_x) - { - const unsigned char* f = from.data() + 4 * (from_y * from_width + from_x); - double src_r = f[0]; - double src_g = f[1]; - double src_b = f[2]; - double src_a = f[3] / 255.0; - - unsigned char* t = to + 4 * (to_y * to_width + to_x); - double dst_r = t[0]; - double dst_g = t[1]; - double dst_b = t[2]; - double dst_a = t[3] / 255.0; - - double out_a = src_a + dst_a * (1.0 - src_a); - unsigned char out_r = src_r + dst_r * (1.0 - src_a); - unsigned char out_g = src_g + dst_g * (1.0 - src_a); - unsigned char out_b = src_b + dst_b * (1.0 - src_a); - - t[0] = out_r; - t[1] = out_g; - t[2] = out_b; - t[3] = static_cast<unsigned char>(out_a * 255.0); - } - } - - /// Create bitmap that we later use as the watermark for every tile. - const std::vector<unsigned char>* getPixmap(int width, int height) - { - if (!_pixmap.empty() && width == _width && height == _height) - return &_pixmap; - - _pixmap.clear(); - - _width = width; - _height = height; - - if (!_loKitDoc) - { - LOG_ERR("Watermark rendering requested without a valid document."); - return nullptr; - } - - // renderFont returns a buffer based on RGBA mode, where r, g, b - // are always set to 0 (black) and the alpha level is 0 everywhere - // except on the text area; the alpha level take into account of - // performing anti-aliasing over the text edges. - unsigned char* textPixels = _loKitDoc->renderFont(_font.c_str(), _text.c_str(), &_width, &_height); - - if (!textPixels) - { - LOG_ERR("Watermark: rendering failed."); - } - - const unsigned int pixel_count = width * height * 4; - - std::vector<unsigned char> text(textPixels, textPixels + pixel_count); - // No longer needed. - std::free(textPixels); - - _pixmap.reserve(pixel_count); - - // Create the white blurred background - // Use box blur, it's enough for our purposes - const int r = 2; - const double weight = (r+1) * (r+1); - for (int y = 0; y < height; ++y) - { - for (int x = 0; x < width; ++x) - { - double t = 0; - for (int ky = std::max(y - r, 0); ky <= std::min(y + r, height - 1); ++ky) - { - for (int kx = std::max(x - r, 0); kx <= std::min(x + r, width - 1); ++kx) - { - // Pre-multiplied alpha; the text is black, so all the - // information is only in the alpha channel - t += text[4 * (ky * width + kx) + 3]; - } - } - - // Clamp the result. - double avg = t / weight; - if (avg > 255.0) - avg = 255.0; - - // Pre-multiplied alpha, but use white for the resulting color - const double alpha = avg / 255.0; - _pixmap[4 * (y * width + x) + 0] = 0xff * alpha; - _pixmap[4 * (y * width + x) + 1] = 0xff * alpha; - _pixmap[4 * (y * width + x) + 2] = 0xff * alpha; - _pixmap[4 * (y * width + x) + 3] = avg; - } - } - - // Now copy the (black) text over the (white) blur - alphaBlend(text, _width, _height, 0, 0, _pixmap.data(), _width, _height); - - // Make the resulting pixmap semi-transparent - for (unsigned char* p = _pixmap.data(); p < _pixmap.data() + pixel_count; p++) - { - *p = static_cast<unsigned char>(*p * _alphaLevel); - } - - return &_pixmap; - } - -private: - std::shared_ptr<lok::Document> _loKitDoc; - std::string _text; - std::string _font; - int _width; - int _height; - double _alphaLevel; - std::vector<unsigned char> _pixmap; -}; - #ifndef MOBILEAPP static FILE* ProcSMapsFile = nullptr; #endif @@ -1030,6 +866,22 @@ public: " ms (" << area / elapsed << " MP/s)."); const auto mode = static_cast<LibreOfficeKitTileMode>(_loKitDocument->getTileMode()); + int nViewId = tile.getNormalizedViewId(); + const auto it = std::find_if(_sessions.begin(), _sessions.end(), [nViewId](const std::pair<std::string, std::shared_ptr<ChildSession>>& val){ return (val.second)->getHash() == nViewId; }); + const auto& session = it->second; + + if (it == _sessions.end()) + { + LOG_ERR("Session is not found. Maybe exited after rendering request."); + return; + } + + int pixelWidth = tile.getWidth(); + int pixelHeight = tile.getHeight(); + + if (session->_docWatermark) + session->_docWatermark->blending(pixmap.data(), 0, 0, pixelWidth, pixelHeight, pixelWidth, pixelHeight, mode); + const TileBinaryHash hash = Png::hashBuffer(pixmap.data(), tile.getWidth(), tile.getHeight()); TileWireId wid = _pngCache.hashToWireId(hash); TileWireId oldWireId = tile.getOldWireId(); @@ -1046,12 +898,6 @@ public: // Send back the request with all optional parameters given in the request. std::string response = ADD_DEBUG_RENDERID(tile.serialize("tile:")) + "\n"; - int pixelWidth = tile.getWidth(); - int pixelHeight = tile.getHeight(); - - if (_docWatermark) - _docWatermark->blending(pixmap.data(), 0, 0, pixelWidth, pixelHeight, pixelWidth, pixelHeight, mode); - std::shared_ptr<std::vector<char>> output = std::make_shared<std::vector<char>>(); output->reserve(response.size() + pixmapDataSize); output->resize(response.size()); @@ -1134,6 +980,16 @@ public: " rendered in " << (elapsed/1000.) << " ms (" << area / elapsed << " MP/s)."); const auto mode = static_cast<LibreOfficeKitTileMode>(_loKitDocument->getTileMode()); + int nViewId = tileCombined.getNormalizedViewId(); + const auto it = std::find_if(_sessions.begin(), _sessions.end(), [nViewId](const std::pair<std::string, std::shared_ptr<ChildSession>>& val){ return (val.second)->getHash() == nViewId; }); + const auto& session = it->second; + + if (it == _sessions.end()) + { + LOG_ERR("Session is not found. Maybe exited after rendering request."); + return; + } + std::vector<char> output; output.reserve(pixmapSize); @@ -1150,6 +1006,12 @@ public: const int offsetX = positionX * pixelWidth; const int offsetY = positionY * pixelHeight; + if (session->_docWatermark) + session->_docWatermark->blending(pixmap.data(), offsetX, offsetY, + pixmapWidth, pixmapHeight, + pixelWidth, pixelHeight, + mode); + const uint64_t hash = Png::hashSubBuffer(pixmap.data(), offsetX, offsetY, pixelWidth, pixelHeight, pixmapWidth, pixmapHeight); @@ -1164,12 +1026,6 @@ public: continue; } - if (_docWatermark) - _docWatermark->blending(pixmap.data(), offsetX, offsetY, - pixmapWidth, pixmapHeight, - pixelWidth, pixelHeight, - mode); - if (!_pngCache.encodeSubBufferToPNG(pixmap.data(), offsetX, offsetY, pixelWidth, pixelHeight, pixmapWidth, pixmapHeight, output, mode, hash, wireId, oldWireId)) @@ -1738,9 +1594,6 @@ private: // Only save the options on opening the document. // No support for changing them after opening a document. _renderOpts = renderOpts; - - if (!watermarkText.empty()) - _docWatermark.reset(new Watermark(_loKitDocument, watermarkText, session)); } else { @@ -1796,6 +1649,14 @@ private: sessionId << "] loaded view [" << viewId << "]. Have " << viewCount << " view" << (viewCount != 1 ? "s." : ".")); + if (!watermarkText.empty()) + { + session->_docWatermark.reset(new Watermark(_loKitDocument, watermarkText, session)); + session->setHash(watermarkText); + } + else + session->setHash(0); + return _loKitDocument; } @@ -2107,9 +1968,6 @@ private: // Whether password is required to view the document, or modify it PasswordType _docPasswordType; - // Document watermark - std::unique_ptr<Watermark> _docWatermark; - std::atomic<bool> _stop; mutable std::mutex _mutex; diff --git a/kit/Watermark.hpp b/kit/Watermark.hpp new file mode 100644 index 000000000..063db0385 --- /dev/null +++ b/kit/Watermark.hpp @@ -0,0 +1,178 @@ +#ifndef INCLUDED_WATERMARK_HPP +#define INCLUDED_WATERMARK_HPP + +#define LOK_USE_UNSTABLE_API +#include <LibreOfficeKit/LibreOfficeKitInit.h> +#include <LibreOfficeKit/LibreOfficeKit.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <vector> +#include <Log.hpp> +#include <cstdlib> +#include <string> +#include "ChildSession.hpp" + +class Watermark +{ +public: + Watermark(const std::shared_ptr<lok::Document>& loKitDoc, const std::string& text, + const std::shared_ptr<ChildSession> & session) + : _loKitDoc(loKitDoc) + , _text(text) + , _font("Liberation Sans") + , _width(0) + , _height(0) + , _alphaLevel(session->getWatermarkOpacity()) + { + } + + ~Watermark() + { + } + + void blending(unsigned char* tilePixmap, + int offsetX, int offsetY, + int tilesPixmapWidth, int tilesPixmapHeight, + int tileWidth, int tileHeight, + LibreOfficeKitTileMode /*mode*/) + { + // set requested watermark size a little bit smaller than tile size + int width = tileWidth * 0.9; + int height = tileHeight * 0.9; + + const std::vector<unsigned char>* pixmap = getPixmap(width, height); + + if (pixmap && tilePixmap) + { + // center watermark + const int maxX = std::min(tileWidth, _width); + const int maxY = std::min(tileHeight, _height); + offsetX += (tileWidth - maxX) / 2; + offsetY += (tileHeight - maxY) / 2; + + alphaBlend(*pixmap, _width, _height, offsetX, offsetY, tilePixmap, tilesPixmapWidth, tilesPixmapHeight); + } + } + +private: + /// Alpha blend pixels from 'from' over the 'to'. + void alphaBlend(const std::vector<unsigned char>& from, int from_width, int from_height, int from_offset_x, int from_offset_y, + unsigned char* to, int to_width, int to_height) + { + for (int to_y = from_offset_y, from_y = 0; (to_y < to_height) && (from_y < from_height) ; ++to_y, ++from_y) + for (int to_x = from_offset_x, from_x = 0; (to_x < to_width) && (from_x < from_width); ++to_x, ++from_x) + { + const unsigned char* f = from.data() + 4 * (from_y * from_width + from_x); + double src_r = f[0]; + double src_g = f[1]; + double src_b = f[2]; + double src_a = f[3] / 255.0; + + unsigned char* t = to + 4 * (to_y * to_width + to_x); + double dst_r = t[0]; + double dst_g = t[1]; + double dst_b = t[2]; + double dst_a = t[3] / 255.0; + + double out_a = src_a + dst_a * (1.0 - src_a); + unsigned char out_r = src_r + dst_r * (1.0 - src_a); + unsigned char out_g = src_g + dst_g * (1.0 - src_a); + unsigned char out_b = src_b + dst_b * (1.0 - src_a); + + t[0] = out_r; + t[1] = out_g; + t[2] = out_b; + t[3] = static_cast<unsigned char>(out_a * 255.0); + } + } + + /// Create bitmap that we later use as the watermark for every tile. + const std::vector<unsigned char>* getPixmap(int width, int height) + { + if (!_pixmap.empty() && width == _width && height == _height) + return &_pixmap; + + _pixmap.clear(); + + _width = width; + _height = height; + + if (!_loKitDoc) + { + LOG_ERR("Watermark rendering requested without a valid document."); + return nullptr; + } + + // renderFont returns a buffer based on RGBA mode, where r, g, b + // are always set to 0 (black) and the alpha level is 0 everywhere + // except on the text area; the alpha level take into account of + // performing anti-aliasing over the text edges. + unsigned char* textPixels = _loKitDoc->renderFont(_font.c_str(), _text.c_str(), &_width, &_height); + + if (!textPixels) + { + LOG_ERR("Watermark: rendering failed."); + } + + const unsigned int pixel_count = width * height * 4; + + std::vector<unsigned char> text(textPixels, textPixels + pixel_count); + // No longer needed. + std::free(textPixels); + + _pixmap.reserve(pixel_count); + + // Create the white blurred background + // Use box blur, it's enough for our purposes + const int r = 2; + const double weight = (r+1) * (r+1); + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + double t = 0; + for (int ky = std::max(y - r, 0); ky <= std::min(y + r, height - 1); ++ky) + { + for (int kx = std::max(x - r, 0); kx <= std::min(x + r, width - 1); ++kx) + { + // Pre-multiplied alpha; the text is black, so all the + // information is only in the alpha channel + t += text[4 * (ky * width + kx) + 3]; + } + } + + // Clamp the result. + double avg = t / weight; + if (avg > 255.0) + avg = 255.0; + + // Pre-multiplied alpha, but use white for the resulting color + const double alpha = avg / 255.0; + _pixmap[4 * (y * width + x) + 0] = 0xff * alpha; + _pixmap[4 * (y * width + x) + 1] = 0xff * alpha; + _pixmap[4 * (y * width + x) + 2] = 0xff * alpha; + _pixmap[4 * (y * width + x) + 3] = avg; + } + } + + // Now copy the (black) text over the (white) blur + alphaBlend(text, _width, _height, 0, 0, _pixmap.data(), _width, _height); + + // Make the resulting pixmap semi-transparent + for (unsigned char* p = _pixmap.data(); p < _pixmap.data() + pixel_count; p++) + { + *p = static_cast<unsigned char>(*p * _alphaLevel); + } + + return &_pixmap; + } + +private: + std::shared_ptr<lok::Document> _loKitDoc; + std::string _text; + std::string _font; + int _width; + int _height; + double _alphaLevel; + std::vector<unsigned char> _pixmap; +}; +#endif diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js index ffe478d81..b6c8101d5 100644 --- a/loleaflet/src/core/Socket.js +++ b/loleaflet/src/core/Socket.js @@ -969,6 +969,9 @@ L.Socket = L.Class.extend({ else if (tokens[i].substring(0, 7) === 'viewid=') { command.viewid = tokens[i].substring(7); } + else if (tokens[i].substring(0, 8) === 'nviewid=') { + command.nviewid = tokens[i].substring(8); + } else if (tokens[i].substring(0, 7) === 'params=') { command.params = tokens[i].substring(7).split(','); } diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 57acac5f6..2fb2cb859 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -1476,7 +1476,7 @@ L.TileLayer = L.GridLayer.extend({ L.Log.log(textMsg, L.INCOMING, key); // Send acknowledgment, that the tile message arrived - var tileID = command.part + ':' + command.x + ':' + command.y + ':' + command.tileWidth + ':' + command.tileHeight; + var tileID = command.part + ':' + command.x + ':' + command.y + ':' + command.tileWidth + ':' + command.tileHeight + ':' + command.nviewid; this._map._socket.sendMessage('tileprocessed tile=' + tileID); }, diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index c5182616d..86a79754e 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -591,6 +591,7 @@ bool ClientSession::sendTile(const char * /*buffer*/, int /*length*/, const std: try { TileDesc tileDesc = TileDesc::parse(tokens); + tileDesc.setNormalizedViewId(getHash()); docBroker->handleTileRequest(tileDesc, shared_from_this()); } catch (const std::exception& exc) @@ -608,6 +609,7 @@ bool ClientSession::sendCombinedTiles(const char* /*buffer*/, int /*length*/, co try { TileCombined tileCombined = TileCombined::parse(tokens); + tileCombined.setNormalizedViewId(getHash()); docBroker->handleTileCombinedRequest(tileCombined, shared_from_this()); } catch (const std::exception& exc) @@ -1322,7 +1324,7 @@ void ClientSession::handleTileInvalidation(const std::string& message, if( part == -1 ) // If no part is specifed we use the part used by the client part = _clientSelectedPart; - int normalizedViewId=0; + int normalizedViewId=getHash(); std::vector<TileDesc> invalidTiles; if(part == _clientSelectedPart || _isTextDocument) @@ -1355,6 +1357,7 @@ void ClientSession::handleTileInvalidation(const std::string& message, if(!invalidTiles.empty()) { TileCombined tileCombined = TileCombined::create(invalidTiles); + tileCombined.setNormalizedViewId(getHash()); docBroker->handleTileCombinedRequest(tileCombined, shared_from_this()); } } @@ -1429,7 +1432,7 @@ std::string ClientSession::generateTileID(const TileDesc& tile) const { std::ostringstream tileID; tileID << tile.getPart() << ":" << tile.getTilePosX() << ":" << tile.getTilePosY() << ":" - << tile.getTileWidth() << ":" << tile.getTileHeight(); + << tile.getTileWidth() << ":" << tile.getTileHeight() << ":" << tile.getNormalizedViewId(); return tileID.str(); } diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 5e89ee736..3119a14c1 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -623,6 +623,10 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s session->setUserName(username); session->setUserExtraInfo(userExtraInfo); session->setWatermarkText(watermarkText); + if(!watermarkText.empty()) + session->setHash(watermarkText); + else + session->setHash(0); // Basic file information was stored by the above getWOPIFileInfo() or getLocalFileInfo() calls const StorageBase::FileInfo fileInfo = _storage->getFileInfo(); _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits