Initial support for intensity-tinted alpha watermark

This commit is contained in:
Lauri Kasanen 2023-02-17 14:04:32 +02:00
parent 61613c4e65
commit ae93cd6840
19 changed files with 407 additions and 3 deletions

View File

@ -148,6 +148,9 @@ endif()
# Check for zlib
find_package(ZLIB REQUIRED)
# Check for libpng
find_package(PNG REQUIRED)
# Check for libjpeg
find_package(JPEG REQUIRED)

View File

@ -1,4 +1,4 @@
include_directories(${CMAKE_SOURCE_DIR}/common ${JPEG_INCLUDE_DIR}
include_directories(${CMAKE_SOURCE_DIR}/common ${JPEG_INCLUDE_DIR} ${PNG_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd)
set(RFB_SOURCES
@ -65,6 +65,7 @@ set(RFB_SOURCES
VNCServerST.cxx
ZRLEEncoder.cxx
ZRLEDecoder.cxx
Watermark.cxx
cpuid.cxx
encodings.cxx
util.cxx
@ -79,7 +80,7 @@ if(WIN32)
set(RFB_SOURCES ${RFB_SOURCES} WinPasswdValidator.cxx)
endif(WIN32)
set(RFB_LIBRARIES ${JPEG_LIBRARIES} os rdr Xregion)
set(RFB_LIBRARIES ${JPEG_LIBRARIES} ${PNG_LIBRARIES} os rdr Xregion)
if(HAVE_PAM)
set(RFB_SOURCES ${RFB_SOURCES} UnixPasswordValidator.cxx

View File

@ -285,6 +285,8 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings)
// QOI-specific overrides
if (supportsQOI)
useCopyRect = false;
if (Server::DLP_WatermarkImage[0])
useCopyRect = false;
}
void ConnParams::setLEDState(unsigned int state)

View File

@ -34,6 +34,7 @@
#include <rfb/UpdateTracker.h>
#include <rfb/LogWriter.h>
#include <rfb/Exception.h>
#include <rfb/Watermark.h>
#include <rfb/RawEncoder.h>
#include <rfb/RREEncoder.h>
@ -162,6 +163,7 @@ static void updateMaxVideoRes(uint16_t *x, uint16_t *y) {
EncodeManager::EncodeManager(SConnection* conn_, EncCache *encCache_) : conn(conn_),
dynamicQualityMin(-1), dynamicQualityOff(-1),
areaCur(0), videoDetected(false), videoTimer(this),
watermarkStats(0),
maxEncodingTime(0), framesSinceEncPrint(0),
encCache(encCache_)
{
@ -299,6 +301,11 @@ void EncodeManager::logStats()
vlog.info(" Total: %s, %s", a, b);
iecPrefix(bytes, "B", a, sizeof(a));
vlog.info(" %s (1:%g ratio)", a, ratio);
if (watermarkData) {
siPrefix(watermarkStats, "B", a, sizeof(a));
vlog.info(" Watermark data sent: %s", a);
}
}
bool EncodeManager::supported(int encoding)
@ -408,8 +415,14 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
nRects += copypassed.size();
nRects += computeNumRects(changed);
nRects += computeNumRects(cursorRegion);
if (watermarkData)
nRects++;
}
if (watermarkData)
packWatermark(changed);
conn->writer()->writeFramebufferUpdateStart(nRects);
writeCopyRects(copied, copyDelta);
@ -427,6 +440,23 @@ void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
if (!videoDetected) // In case detection happened between the calls
writeRects(cursorRegion, renderedCursor);
if (watermarkData) {
beforeLength = conn->getOutStream(conn->cp.supportsUdp)->length();
const Rect rect(0, 0, pb->width(), pb->height());
TightEncoder *encoder = ((TightEncoder *) encoders[encoderTight]);
conn->writer()->startRect(rect, encoder->encoding);
encoder->writeWatermarkRect(watermarkData, watermarkDataLen,
watermarkInfo.r,
watermarkInfo.g,
watermarkInfo.b,
watermarkInfo.a);
conn->writer()->endRect();
watermarkStats += conn->getOutStream(conn->cp.supportsUdp)->length() - beforeLength;
}
updateQualities();
conn->writer()->writeFramebufferUpdateEnd();

View File

@ -193,6 +193,7 @@ namespace rfb {
unsigned updates;
EncoderStats copyStats;
StatsVector stats;
unsigned long long watermarkStats;
int activeType;
int beforeLength;
size_t curMaxUpdateSize;

View File

@ -239,3 +239,8 @@ void rfb::Region::debug_print(const char* prefix) const
xrgn->rects[i].y2-xrgn->rects[i].y1);
}
}
bool rfb::Region::contains(int x, int y) const
{
return XPointInRegion(xrgn, x, y);
}

View File

@ -73,6 +73,8 @@ namespace rfb {
void debug_print(const char *prefix) const;
bool contains(int x, int y) const;
protected:
struct _XRegion* xrgn;

View File

@ -185,6 +185,23 @@ rfb::BoolParameter rfb::Server::DLP_RegionAllowRelease
"Allow click releases inside the blacked-out region",
true);
rfb::IntParameter rfb::Server::DLP_WatermarkRepeatSpace
("DLP_WatermarkRepeatSpace",
"Number of pixels between repeats of the watermark",
0, 0, 4096);
rfb::StringParameter rfb::Server::DLP_WatermarkImage
("DLP_WatermarkImage",
"PNG file to use as a watermark",
"");
rfb::StringParameter rfb::Server::DLP_WatermarkLocation
("DLP_WatermarkLocation",
"Place the watermark at this position from the corner.",
"");
rfb::StringParameter rfb::Server::DLP_WatermarkTint
("DLP_WatermarkTint",
"Tint the greyscale watermark by this color.",
"255,255,255,255");
rfb::StringParameter rfb::Server::maxVideoResolution
("MaxVideoResolution",
"When in video mode, downscale the screen to max this size.",

View File

@ -48,9 +48,13 @@ namespace rfb {
static IntParameter DLP_ClipAcceptMax;
static IntParameter DLP_ClipDelay;
static IntParameter DLP_KeyRateLimit;
static IntParameter DLP_WatermarkRepeatSpace;
static StringParameter DLP_ClipLog;
static StringParameter DLP_Region;
static StringParameter DLP_Clip_Types;
static StringParameter DLP_WatermarkImage;
static StringParameter DLP_WatermarkLocation;
static StringParameter DLP_WatermarkTint;
static BoolParameter DLP_RegionAllowClick;
static BoolParameter DLP_RegionAllowRelease;
static IntParameter jpegVideoQuality;

View File

@ -25,7 +25,8 @@ namespace rfb {
const unsigned int tightPng = 0x0a;
const unsigned int tightWebp = 0x0b;
const unsigned int tightQoi = 0x0c;
const unsigned int tightMaxSubencoding = 0x0c;
const unsigned int tightIT = 0x0d;
const unsigned int tightMaxSubencoding = 0x0d;
// Filters to improve compression efficiency
const unsigned int tightFilterCopy = 0x00;

View File

@ -277,6 +277,28 @@ void TightEncoder::resetZlib()
zlibNeedsReset = true;
}
void TightEncoder::writeWatermarkRect(const rdr::U8 *data, const unsigned len,
const rdr::U8 r,
const rdr::U8 g,
const rdr::U8 b,
const rdr::U8 a)
{
rdr::OutStream* os;
os = conn->getOutStream(conn->cp.supportsUdp);
os->writeU8(tightIT << 4);
writeCompact(os, len + 4);
os->writeU8(r);
os->writeU8(g);
os->writeU8(b);
os->writeU8(a);
os->writeBytes(data, len);
}
//
// Including BPP-dependent implementation of the encoder.
//

View File

@ -39,6 +39,11 @@ namespace rfb {
virtual void writeSolidRect(int width, int height,
const PixelFormat& pf,
const rdr::U8* colour);
void writeWatermarkRect(const rdr::U8 *data, const unsigned len,
const rdr::U8 r,
const rdr::U8 g,
const rdr::U8 b,
const rdr::U8 a);
void resetZlib();
protected:

View File

@ -1777,6 +1777,9 @@ void VNCSConnectionST::udpDowngrade(const bool byServer)
cp.useCopyRect = true;
encodeManager.resetZlib();
if (Server::DLP_WatermarkImage[0])
cp.useCopyRect = false;
vlog.info("Client %s downgrading from udp by %s", sock->getPeerAddress(),
byServer ? "the server" : "its own request");
}

View File

@ -62,6 +62,7 @@
#include <rfb/ServerCore.h>
#include <rfb/VNCServerST.h>
#include <rfb/VNCSConnectionST.h>
#include <rfb/Watermark.h>
#include <rfb/util.h>
#include <rfb/ledStates.h>
@ -1048,6 +1049,9 @@ void VNCServerST::writeUpdate()
memset(&jpegstats, 0, sizeof(EncodeManager::codecstats_t));
memset(&webpstats, 0, sizeof(EncodeManager::codecstats_t));
if (watermarkData)
updateWatermark();
for (ci = clients.begin(); ci != clients.end(); ci = ci_next) {
ci_next = ci; ci_next++;

View File

@ -256,6 +256,8 @@ namespace rfb {
bool getComparerState();
void updateWatermark();
QueryConnectionHandler* queryConnectionHandler;
KeyRemapper* keyRemapper;

233
common/rfb/Watermark.cxx Normal file
View File

@ -0,0 +1,233 @@
/* Copyright (C) 2023 Kasm
*
* This 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 2 of the License, or
* (at your option) any later version.
*
* This software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#include <rfb/LogWriter.h>
#include <rfb/ServerCore.h>
#include <rfb/VNCServerST.h>
#include "Watermark.h"
using namespace rfb;
static LogWriter vlog("watermark");
watermarkInfo_t watermarkInfo;
uint8_t *watermarkData, *watermarkUnpacked, *watermarkTmp;
uint32_t watermarkDataLen;
static uint16_t rw, rh;
#define MAXW 4096
#define MAXH 4096
static bool loadimage(const char path[]) {
FILE *f = fopen(path, "r");
if (!f) {
vlog.error("Can't open %s", path);
return false;
}
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
if (!png_ptr) return false;
png_infop info = png_create_info_struct(png_ptr);
if (!info) return false;
if (setjmp(png_jmpbuf(png_ptr))) return false;
png_init_io(png_ptr, f);
png_read_png(png_ptr, info,
PNG_TRANSFORM_PACKING |
PNG_TRANSFORM_STRIP_16 |
PNG_TRANSFORM_STRIP_ALPHA |
PNG_TRANSFORM_EXPAND, NULL);
uint8_t **rows = png_get_rows(png_ptr, info);
const unsigned imgw = png_get_image_width(png_ptr, info);
const unsigned imgh = png_get_image_height(png_ptr, info);
watermarkInfo.w = imgw;
watermarkInfo.h = imgh;
watermarkInfo.src = (uint8_t *) calloc(imgw, imgh);
unsigned x, y;
for (y = 0; y < imgh; y++) {
for (x = 0; x < imgw; x++) {
const uint8_t r = rows[y][x * 3 + 0];
const uint8_t g = rows[y][x * 3 + 1];
const uint8_t b = rows[y][x * 3 + 2];
const uint8_t grey = r * .2126f +
g * .7152f +
b * .0722f;
const uint8_t out = (grey + 8) >> 4;
watermarkInfo.src[y * imgw + x] = out < 16 ? out : 15;
}
}
fclose(f);
png_destroy_info_struct(png_ptr, &info);
png_destroy_read_struct(&png_ptr, NULL, NULL);
return true;
}
bool watermarkInit() {
memset(&watermarkInfo, 0, sizeof(watermarkInfo_t));
watermarkData = watermarkUnpacked = watermarkTmp = NULL;
rw = rh = 0;
if (!Server::DLP_WatermarkImage[0])
return true;
if (!loadimage(Server::DLP_WatermarkImage))
return false;
if (Server::DLP_WatermarkRepeatSpace && Server::DLP_WatermarkLocation[0]) {
vlog.error("Repeat and location can't be used together");
return false;
}
if (sscanf(Server::DLP_WatermarkTint, "%hhu,%hhu,%hhu,%hhu",
&watermarkInfo.r,
&watermarkInfo.g,
&watermarkInfo.b,
&watermarkInfo.a) != 4) {
vlog.error("Invalid tint");
return false;
}
watermarkInfo.repeat = Server::DLP_WatermarkRepeatSpace;
if (Server::DLP_WatermarkLocation[0]) {
if (sscanf(Server::DLP_WatermarkLocation, "%hd,%hd",
&watermarkInfo.x,
&watermarkInfo.y) != 2) {
vlog.error("Invalid location");
return false;
}
}
watermarkUnpacked = (uint8_t *) calloc(MAXW, MAXH);
watermarkTmp = (uint8_t *) calloc(MAXW, MAXH / 2);
watermarkData = (uint8_t *) calloc(MAXW, MAXH / 2);
return true;
}
// update the screen-size rendered watermark whenever the screen is resized
void VNCServerST::updateWatermark() {
if (rw == pb->width() &&
rh == pb->height())
return;
rw = pb->width();
rh = pb->height();
memset(watermarkUnpacked, 0, rw * rh);
uint16_t x, y, srcy;
if (watermarkInfo.repeat) {
for (y = 0, srcy = 0; y < rh; y++) {
for (x = 0; x < rw;) {
if (x + watermarkInfo.w < rw)
memcpy(&watermarkUnpacked[y * rw + x],
&watermarkInfo.src[srcy * watermarkInfo.w],
watermarkInfo.w);
else
memcpy(&watermarkUnpacked[y * rw + x],
&watermarkInfo.src[srcy * watermarkInfo.w],
rw - x);
x += watermarkInfo.w + watermarkInfo.repeat;
}
srcy++;
if (srcy == watermarkInfo.h) {
srcy = 0;
y += watermarkInfo.repeat;
}
}
} else {
int16_t sx, sy;
if (!watermarkInfo.x)
sx = (rw - watermarkInfo.w) / 2;
else if (watermarkInfo.x > 0)
sx = watermarkInfo.x;
else
sx = rw - watermarkInfo.w + watermarkInfo.x;
if (sx < 0)
sx = 0;
if (!watermarkInfo.y)
sy = (rh - watermarkInfo.h) / 2;
else if (watermarkInfo.y > 0)
sy = watermarkInfo.y;
else
sy = rh - watermarkInfo.h + watermarkInfo.y;
if (sy < 0)
sy = 0;
for (y = 0; y < watermarkInfo.h; y++) {
if (sx + watermarkInfo.w < rw)
memcpy(&watermarkUnpacked[(sy + y) * rw + sx],
&watermarkInfo.src[y * watermarkInfo.w],
watermarkInfo.w);
else
memcpy(&watermarkUnpacked[(sy + y) * rw + sx],
&watermarkInfo.src[y * watermarkInfo.w],
rw - sx);
}
}
}
void packWatermark(const Region &changed) {
// Take the expanded 4-bit data, filter it by the changed rects, pack
// to shared bytes, and compress with zlib
uint16_t x, y;
uint8_t pix[2], cur = 0;
uint8_t *dst = watermarkTmp;
for (y = 0; y < rh; y++) {
for (x = 0; x < rw; x++) {
pix[cur] = 0;
if (changed.contains(x, y))
pix[cur] = watermarkUnpacked[y * rw + x];
if (cur || (y == rh - 1 && x == rw - 1))
*dst++ = pix[0] | (pix[1] << 4);
cur ^= 1;
}
}
uLong destLen = MAXW * MAXH / 2;
if (compress2(watermarkData, &destLen, watermarkTmp, rw * rh / 2 + 1, 1) != Z_OK)
vlog.error("Zlib compression error");
watermarkDataLen = destLen;
}

43
common/rfb/Watermark.h Normal file
View File

@ -0,0 +1,43 @@
/* Copyright (C) 2023 Kasm
*
* This 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 2 of the License, or
* (at your option) any later version.
*
* This software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifndef WATERMARK_H
#define WATERMARK_H
#include <stdint.h>
#include <rfb/Region.h>
struct watermarkInfo_t {
uint8_t *src;
uint16_t w, h;
int16_t x, y;
uint16_t repeat;
uint8_t r, g, b, a;
};
extern watermarkInfo_t watermarkInfo;
bool watermarkInit();
void packWatermark(const rfb::Region &changed); // filter and pack the watermark for sending
extern uint8_t *watermarkData;
extern uint32_t watermarkDataLen;
#endif

View File

@ -341,6 +341,28 @@ Log clipboard and keyboard actions. Info logs just clipboard direction and size,
verbose adds the contents for both.
.
.TP
.B \-DLP_WatermarkImage \fIpath/to/file.png\fP
Add a watermark. The PNG file should be greyscale, black is treated as transparent
and white as opaque.
.
.TP
.B \-DLP_WatermarkLocation \fIx,y\fP
Place the watermark at this position from the corner. Positive numbers are from top-left,
negative from bottom-right. Negative numbers count from the bottom-right edge of the image.
If not set, the watermark will be centered. Cannot be used together with repeat.
.
.TP
.B \-DLP_WatermarkRepeatSpace \fInum\fP
If set, repeat the watermark over the entire image, with \fBnum\fP pixels between
repetitions. Cannot be used together with location.
.
.TP
.B \-DLP_WatermarkTint \fIr,g,b,a\fP
Tint the greyscale watermark by this color. Default is 255,255,255,255 - full white.
The color components can be used to colorize the greyscale watermark, and the alpha
can be used to make it fainter.
.
.TP
.B \-selfBench
Run a set of self-benchmarks and exit.
.

View File

@ -37,6 +37,7 @@
#include <rfb/Hostname.h>
#include <rfb/Region.h>
#include <rfb/ledStates.h>
#include <rfb/Watermark.h>
#include <network/iceip.h>
#include <network/TcpSocket.h>
#include <network/UnixSocket.h>
@ -232,6 +233,9 @@ void vncExtensionInit(void)
dummyY < 16)
vncFatalError("Invalid value to %s", Server::maxVideoResolution.getName());
if (!watermarkInit())
vncFatalError("Invalid watermark params");
pipe(wakeuppipe);
const int flags = fcntl(wakeuppipe[0], F_GETFL, 0);
fcntl(wakeuppipe[0], F_SETFL, flags | O_NONBLOCK);