Skip to content

Commit 144b0f5

Browse files
committed
Refactor of watermarking code to allow it to be applied to JPEG2000 and JPEG source images when requesting a region through the CVT function. Watermarking now applied as the very final step after all other processing carried out and if image dimensions are greater than 2x the tile size of the source image, the image is divided into blocks and multiple watermarks applied, one per block.
Resolves #255. Implementation partially derived from #219
1 parent 75ea660 commit 144b0f5

10 files changed

+158
-104
lines changed

ChangeLog

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
14/02/2024:
1+
19/02/2025:
2+
- Refactor of watermarking code to allow it to be applied to JPEG2000 and JPEG source images when
3+
requesting a region through the CVT function. Watermarking now applied as the very final step after
4+
all other processing carried out and if image dimensions are greater than 2x the tile size of the
5+
source image, the image is divided into blocks and multiple watermarks applied, one per block.
6+
Resolves https://github.com/ruven/iipsrv/issues/255. Implementation partially derived from
7+
https://github.com/ruven/iipsrv/pull/219
8+
9+
10+
14/02/2025:
211
- Harmonizied ICC profile handling between JTL and CVT and replaced EMBED_ICC startup parameter with
312
more flexible MAX_ICC parameter, which defines the maximum acceptable size for an ICC profile.
413
Profiles larger than this are stripped out of the output image. If set to -1, profiles are always

src/CVT.cc

+18-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ void CVT::send( Session* session ){
173173

174174

175175
// Set up our TileManager object
176-
TileManager tilemanager( session->tileCache, *session->image, session->watermark, compressor, session->logfile, session->loglevel );
176+
TileManager tilemanager( session->tileCache, *session->image, compressor, session->logfile, session->loglevel );
177177

178178

179179
// First calculate histogram if we have asked for either binarization,
@@ -496,6 +496,23 @@ void CVT::send( Session* session ){
496496
}
497497

498498

499+
// Apply the watermark if we have one. This should always be applied last
500+
if( session->watermark && (session->watermark)->isSet() ){
501+
502+
if( session->loglevel >= 5 ) function_timer.start();
503+
504+
unsigned int tw = (*session->image)->getTileWidth();
505+
unsigned int th = (*session->image)->getTileHeight();
506+
507+
// Use a watermark block size of 2x the tile size of the image
508+
session->watermark->apply( complete_image.data, complete_image.width, complete_image.height,
509+
complete_image.channels, complete_image.bpc, (tw>th ? tw : th)*2 );
510+
511+
if( session->loglevel >= 5 ) *(session->logfile) << "CVT :: Watermark applied in " << function_timer.getTime()
512+
<< " microseconds" << endl;
513+
}
514+
515+
499516
// Add metadata
500517
compressor->setMetadata( (*session->image)->metadata );
501518

src/JTL.cc

+14-4
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ void JTL::send( Session* session, int resolution, int tile ){
9090
else compressor = session->jpeg;
9191

9292

93-
TileManager tilemanager( session->tileCache, *session->image, session->watermark, compressor, session->logfile, session->loglevel );
93+
TileManager tilemanager( session->tileCache, *session->image, compressor, session->logfile, session->loglevel );
9494

9595

9696
// First calculate histogram if we have asked for either binarization,
@@ -127,6 +127,7 @@ void JTL::send( Session* session, int resolution, int tile ){
127127
(*session->image)->getNumChannels()==3 && (*session->image)->getNumBitsPerPixel()==8 )
128128
|| session->view->floatProcessing() || session->view->equalization
129129
|| session->view->getRotation() != 0.0 || session->view->flip != 0
130+
|| ( session->watermark && (session->watermark)->isSet() )
130131
) ct = ImageEncoding::RAW;
131132

132133

@@ -225,7 +226,7 @@ void JTL::send( Session* session, int resolution, int tile ){
225226
// Reset our contrast
226227
session->view->contrast = 1.0;
227228

228-
if( session->loglevel >= 5 ){
229+
if( session->loglevel >= 4 ){
229230
*(session->logfile) << "JTL :: Applying contrast stretch for image range of "
230231
<< n0 << " - " << n1 << endl;
231232
}
@@ -398,13 +399,13 @@ void JTL::send( Session* session, int resolution, int tile ){
398399
// Apply flip
399400
if( session->view->flip != 0 ){
400401
Timer flip_timer;
401-
if( session->loglevel >= 5 ){
402+
if( session->loglevel >= 4 ){
402403
flip_timer.start();
403404
}
404405

405406
session->processor->flip( rawtile, session->view->flip );
406407

407-
if( session->loglevel >= 5 ){
408+
if( session->loglevel >= 4 ){
408409
*(session->logfile) << "JTL :: Flipping image ";
409410
if( session->view->flip == 1 ) *(session->logfile) << "horizontally";
410411
else *(session->logfile) << "vertically";
@@ -427,6 +428,15 @@ void JTL::send( Session* session, int resolution, int tile ){
427428
}
428429

429430

431+
// Apply the watermark if we have one. This should always be applied last
432+
if( session->watermark && (session->watermark)->isSet() ){
433+
if( session->loglevel >= 4 ) function_timer.start();
434+
session->watermark->apply( rawtile.data, rawtile.width, rawtile.height, rawtile.channels, rawtile.bpc );
435+
if( session->loglevel >= 4 ) *(session->logfile) << "JTL :: Watermark applied in " << function_timer.getTime()
436+
<< " microseconds" << endl;
437+
}
438+
439+
430440
// Compress to requested output format
431441
if( rawtile.compressionType == ImageEncoding::RAW ){
432442
if( session->loglevel >= 4 ){

src/PFL.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
IIP Profile Command Handler Class Member Function
33
4-
Copyright (C) 2013-2022 Ruven Pillay.
4+
Copyright (C) 2013-2025 Ruven Pillay.
55
66
This program is free software; you can redistribute it and/or modify
77
it under the terms of the GNU General Public License as published by
@@ -118,7 +118,7 @@ void PFL::run( Session* session, const std::string& argument ){
118118

119119

120120
// Create our tilemanager object
121-
TileManager tilemanager( session->tileCache, *session->image, session->watermark, session->jpeg, session->logfile, session->loglevel );
121+
TileManager tilemanager( session->tileCache, *session->image, session->jpeg, session->logfile, session->loglevel );
122122

123123

124124
// Use our horizontal views function to get a list of available spectral images

src/SPECTRA.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
IIP SPECTRA Command Handler Class Member Function
33
4-
Copyright (C) 2009-2023 Ruven Pillay.
4+
Copyright (C) 2009-2025 Ruven Pillay.
55
66
This program is free software; you can redistribute it and/or modify
77
it under the terms of the GNU General Public License as published by
@@ -78,7 +78,7 @@ void SPECTRA::run( Session* session, const std::string& argument ){
7878
}
7979

8080

81-
TileManager tilemanager( session->tileCache, *session->image, session->watermark, session->jpeg, session->logfile, session->loglevel );
81+
TileManager tilemanager( session->tileCache, *session->image, session->jpeg, session->logfile, session->loglevel );
8282

8383
// Use our horizontal views function to get a list of available spectral images
8484
list <int> views = (*session->image)->getHorizontalViewsList();

src/TIL.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
IIP TIL Command Handler Class Member Function
33
4-
Copyright (C) 2006-2023 Ruven Pillay.
4+
Copyright (C) 2006-2025 Ruven Pillay.
55
66
This program is free software; you can redistribute it and/or modify
77
it under the terms of the GNU General Public License as published by
@@ -127,7 +127,7 @@ void TIL::run( Session* session, const std::string& a ){
127127
int n = i + (j*ntlx);
128128

129129
// Get our tile using our tile manager
130-
TileManager tilemanager( session->tileCache, *session->image, session->watermark, session->jpeg, session->logfile, session->loglevel );
130+
TileManager tilemanager( session->tileCache, *session->image, session->jpeg, session->logfile, session->loglevel );
131131
RawTile rawtile = tilemanager.getTile( resolution, n, session->view->xangle,
132132
session->view->yangle, session->view->getLayers(), ImageEncoding::JPEG );
133133

src/TileManager.cc

+1-13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
/* IIP Server: Tile Cache Handler
66
7-
Copyright (C) 2005-2024 Ruven Pillay
7+
Copyright (C) 2005-2025 Ruven Pillay
88
99
This program is free software; you can redistribute it and/or modify
1010
it under the terms of the GNU General Public License as published by
@@ -42,18 +42,6 @@ RawTile TileManager::getNewTile( int resolution, int tile, int xangle, int yangl
4242
if( loglevel >= 2 ) *logfile << "TileManager :: Tile decoding time: " << insert_timer.getTime()
4343
<< " microseconds" << endl;
4444

45-
46-
// Apply the watermark if we have one.
47-
// Do this before inserting into cache so that we cache watermarked tiles
48-
if( watermark && watermark->isSet() ){
49-
50-
if( loglevel >= 4 ) insert_timer.start();
51-
watermark->apply( ttt.data, ttt.width, ttt.height, ttt.channels, ttt.bpc );
52-
if( loglevel >= 4 ) *logfile << "TileManager :: Watermark applied: " << insert_timer.getTime()
53-
<< " microseconds" << endl;
54-
}
55-
56-
5745
// If our tile is already correctly encoded, no need to re-encode, but may need to inject metadata
5846
if( (ttt.compressionType == ctype) && (ctype != ImageEncoding::RAW) ){
5947

src/TileManager.h

+7-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/* IIP Image Server
44
5-
Copyright (C) 2005-2023 Ruven Pillay.
5+
Copyright (C) 2005-2025 Ruven Pillay.
66
77
This program is free software; you can redistribute it and/or modify
88
it under the terms of the GNU General Public License as published by
@@ -39,7 +39,6 @@
3939
#endif
4040
#include "Cache.h"
4141
#include "Timer.h"
42-
#include "Watermark.h"
4342
#include "Logger.h"
4443

4544

@@ -53,7 +52,6 @@ class TileManager{
5352
Cache* tileCache;
5453
Compressor* compressor;
5554
IIPImage* image;
56-
Watermark* watermark;
5755
Logger* logfile;
5856
int loglevel;
5957
Timer compression_timer, tile_timer, insert_timer;
@@ -81,19 +79,16 @@ class TileManager{
8179
/**
8280
* @param tc pointer to tile cache object
8381
* @param im pointer to IIPImage object
84-
* @param w pointer to watermark object
8582
* @param c pointer to Compressor object
8683
* @param s pointer to Logger object
8784
* @param l logging level
8885
*/
89-
TileManager( Cache* tc, IIPImage* im, Watermark* w, Compressor* c, Logger* s, int l ){
90-
tileCache = tc;
91-
image = im;
92-
watermark = w;
93-
compressor = c;
94-
logfile = s ;
95-
loglevel = l;
96-
};
86+
TileManager( Cache* tc, IIPImage* im, Compressor* c, Logger* s, int l ) :
87+
tileCache( tc ),
88+
compressor( c ),
89+
image( im ),
90+
logfile( s ),
91+
loglevel( l ) {};
9792

9893

9994

src/Watermark.cc

+82-47
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Culture of the Czech Republic.
88
99
10-
Copyright (C) 2010-2023 Ruven Pillay.
10+
Copyright (C) 2010-2025 Ruven Pillay.
1111
1212
This program is free software; you can redistribute it and/or modify
1313
it under the terms of the GNU General Public License as published by
@@ -87,62 +87,97 @@ void Watermark::init()
8787

8888

8989
// Apply the watermark to a buffer of data
90-
void Watermark::apply( void* data, unsigned int width, unsigned int height, unsigned int channels, unsigned int bpc )
90+
void Watermark::apply( void* data, unsigned int width, unsigned int height, unsigned int channels, unsigned int bpc, unsigned int block )
9191
{
92-
9392
// Sanity check
9493
if( !_isSet || (_probability==0) || (_opacity==0) ) return;
9594

96-
// Get random number as a float between 0 and 1
97-
float random = (float)( rand() / RAND_MAX );
95+
/* Calculate the size of the blocks into which we paste the watermark and the number of blocks horizontally and vertically.
96+
For tile requests we define the block as being the whole image and, thus, apply a single watermark.
97+
For larger regions, we divide the image into blocks and allow multiple watermarks to be placed, one within each block
98+
*/
99+
unsigned int ntlx = 1;
100+
unsigned int ntly = 1;
101+
unsigned int tile_width = width;
102+
unsigned int tile_height = height;
103+
unsigned rem_x = 0;
104+
unsigned rem_y = 0;
105+
106+
// If block has been defined and our image is larger than the block size, apply multiple watermarks, each randomly placed within a block
107+
if( (block > 0) && (width > block || height > block) ){
108+
tile_width = block;
109+
tile_height = block;
110+
rem_x = width % tile_width;
111+
ntlx = (width / tile_width) + (rem_x == 0 ? 0 : 1);
112+
rem_y = height % tile_height;
113+
ntly = (height / tile_height) + (rem_y == 0 ? 0 : 1);
114+
}
115+
116+
117+
// Loop through each block
118+
for( unsigned int ty=0; ty<ntly; ty++ ){
119+
for( unsigned int tx=0; tx<ntlx; tx++ ){
120+
121+
// Get random number as a float between 0 and 1
122+
float random = rand() / (float)RAND_MAX;
98123

99-
// Only apply if our random number is less than our given probability
100-
if( random < _probability ){
101-
102-
// Vary watermark position randomly within the tile depending on available space
103-
unsigned int xoffset = 0;
104-
if( width > _width ){
105-
random = (float)( rand() / RAND_MAX );
106-
xoffset = random * (width - _width);
107-
}
124+
// Only apply if our random number is less than our given probability
125+
if( random < _probability ){
126+
127+
// Block width and height
128+
unsigned int tw = tile_width;
129+
unsigned int th = tile_height;
130+
131+
// Update block size if this is last row
132+
if( (tx == ntlx - 1) && ( rem_x != 0 ) ) tw = rem_x;
133+
if( (ty == ntly - 1) && ( rem_y != 0 ) ) th = rem_y;
134+
135+
// Vary watermark position randomly within the block depending on available space
136+
unsigned int xoffset = 0;
137+
if( tw > _width ){
138+
random = rand() / (float)RAND_MAX;
139+
xoffset = random * (tw - _width);
140+
}
108141

109-
unsigned int yoffset = 0;
110-
if( height > _height ){
111-
random = (float)( rand() / RAND_MAX );
112-
yoffset = random * (height - _height);
113-
}
142+
unsigned int yoffset = 0;
143+
if( th > _height ){
144+
random = rand() / (float)RAND_MAX;
145+
yoffset = random * (th - _height);
146+
}
114147

115-
// Limit the area of the watermark to the size of the tile
116-
unsigned int xlimit = _width;
117-
unsigned int ylimit = _height;
118-
if( _width > width ) xlimit = width;
119-
if( _height > height ) ylimit = height;
120-
121-
for( unsigned int j=0; j<ylimit; j++ ){
122-
for( unsigned int i=0; i<xlimit; i++ ){
123-
for( unsigned int k=0; k<channels; k++ ){
124-
125-
unsigned int id = (j+yoffset)*width*channels + (i+xoffset)*channels + k;
126-
127-
// For 16bit images we need to multiply up as our watermark data is always 8bit
128-
// We do our maths in unsigned int to allow us to clip correctly
129-
if( bpc == 16 ){
130-
unsigned short* d = (unsigned short*) data;
131-
unsigned int t = (unsigned int)( d[id] + _watermark[j*_width*_channels + i*_channels + k]*256 );
132-
if( t > 65535 ) t = 65535;
133-
d[id] = (unsigned short) t;
134-
}
135-
// TIFFReadRGBAImage always scales to 8bit, so never any need for downscaling, but clip to 255
136-
// We do our maths in unsigned short to allow us to clip correctly after
137-
else{
138-
unsigned char* d = (unsigned char*) data;
139-
unsigned short t = (unsigned short)( d[id] + _watermark[j*_width*_channels + i*_channels + k] );
140-
if( t > 255 ) t = 255;
141-
d[id] = (unsigned char) t;
148+
// Limit the area of the watermark to the size of the block
149+
unsigned int xlimit = _width;
150+
unsigned int ylimit = _height;
151+
if( _width > tw ) xlimit = tw;
152+
if( _height > th ) ylimit = th;
153+
154+
for( unsigned int j=0; j<ylimit; j++ ){
155+
for( unsigned int i=0; i<xlimit; i++ ){
156+
for( unsigned int k=0; k<channels; k++ ){
157+
158+
// Calculate the index on the image buffer that is to be watermarked
159+
uint32_t id = ((ty*tile_width)+j+yoffset)*width*channels + ((tx*tile_height)+i+xoffset)*channels + k;
160+
161+
// For 16bit images we need to multiply up as our watermark data is always 8bit
162+
// We do our maths in unsigned int to allow us to clip correctly
163+
if( bpc == 16 ){
164+
unsigned short* d = (unsigned short*) data;
165+
unsigned int t = (unsigned int)( d[id] + _watermark[j*_width*_channels + i*_channels + k]*256 );
166+
if( t > 65535 ) t = 65535;
167+
d[id] = (unsigned short) t;
168+
}
169+
// TIFFReadRGBAImage always scales to 8bit, so never any need for downscaling, but clip to 255
170+
// We do our maths in unsigned short to allow us to clip correctly after
171+
else{
172+
unsigned char* d = (unsigned char*) data;
173+
unsigned short t = (unsigned short)( d[id] + _watermark[j*_width*_channels + i*_channels + k] );
174+
if( t > 255 ) t = 255;
175+
d[id] = (unsigned char) t;
176+
}
177+
}
142178
}
143179
}
144180
}
145181
}
146182
}
147-
148183
}

0 commit comments

Comments
 (0)