Skip to content

Commit

Permalink
feat: Watermarks with alpha support and respect to ratio (DEV-4072) (#…
Browse files Browse the repository at this point in the history
…458)

* feat: Watermarks with alpha support and respect to ratio (DEV-4072)
* serialize watermark as golden standard
* add watermark with an alpha channel, so the watermarked test file can be replicated
* add gitignored missing test image assets to the repo
  • Loading branch information
siers authored Oct 8, 2024
1 parent f3a9a96 commit da34f6a
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 35 deletions.
74 changes: 51 additions & 23 deletions src/SipiImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ bool SipiImage::crop(const std::shared_ptr<SipiRegion> &region)


/****************************************************************************/
#define POSITION(x, y, c, n) ((n) * ((y) * nx + (x)) + c)
#define POSITION(x, y, c, n) ((n) * ((y)*nx + (x)) + c)

byte SipiImage::bilinn(byte buf[], const int nx, const double x, const double y, const int c, const int n)
{
Expand Down Expand Up @@ -1367,52 +1367,50 @@ bool SipiImage::toBitonal()

//============================================================================

#define POSITION(w, x, y, c, n) ((n) * ((y)*w + (x)) + c)

void SipiImage::add_watermark(const std::string &wmfilename)
{
int wm_nx, wm_ny, wm_nc;
byte *wmbuf = read_watermark(wmfilename, wm_nx, wm_ny, wm_nc);
if (wmbuf == nullptr) { throw SipiImageError("Cannot read watermark file " + wmfilename); }

auto xlut = shttps::make_unique<double[]>(nx);
auto ylut = shttps::make_unique<double[]>(ny);
// scaling is calculated with the middle point as point of origin
double wm_scale = ((double)wm_nx / wm_ny > (double)nx / ny) ? (double)wm_nx / nx : (double)wm_ny / ny;

// float *xlut = new float[nx];
// float *ylut = new float[ny];

for (size_t i = 0; i < nx; i++) { xlut[i] = (double)(wm_nx * i) / (double)nx; }

for (size_t j = 0; j < ny; j++) { ylut[j] = (double)(wm_ny * j) / (double)ny; }
// blending alpha coefficient (multiplies with alpha channel if there is one)
double wm_strength = 0.8;

if (bps == 8) {
auto *buf = pixels;

for (size_t j = 0; j < ny; j++) {
for (size_t i = 0; i < nx; i++) {
byte val = bilinn(wmbuf, wm_nx, xlut[i], ylut[j], 0, wm_nc);
for (size_t k = 0; k < nc; k++) {
double nval = (buf[nc * (j * nx + i) + k] / 255.) * (1.0 + val / 2550.0) + val / 2550.0;
buf[nc * (j * nx + i) + k] = (nval > 1.0) ? 255 : (unsigned char)floorl(nval * 255. + .5);
}
}
}
} else if (bps == 16) {
auto *buf = reinterpret_cast<word *>(pixels);
double wm_i = (i - nx / 2.) * wm_scale + wm_nx / 2.;
double wm_j = (j - ny / 2.) * wm_scale + wm_ny / 2.;

for (size_t j = 0; j < ny; j++) {
for (size_t i = 0; i < nx; i++) {
for (size_t k = 0; k < nc; k++) {
byte val = bilinn(wmbuf, wm_nx, xlut[i], ylut[j], 0, wm_nc);
double nval = (buf[nc * (j * nx + i) + k] / 65535.0) * (1.0 + val / 655350.0) + val / 352500.;
buf[nc * (j * nx + i) + k] = (nval > 1.0) ? (word)65535 : (word)floorl(nval * 65535. + .5);
if (!(abs(wm_i - wm_nx / 2.) < wm_nx / 2. && abs(wm_j - wm_ny / 2.) < wm_ny / 2.)) continue;

double wm_alpha = wm_nc == 4 ? bilinn(wmbuf, wm_nx, wm_i, wm_j, 3, wm_nc) : 1;
double wm_alpha_weak = wm_alpha / 255.0 * wm_strength;

double wm_color = bilinn(wmbuf, wm_nx, wm_i, wm_j, k, wm_nc) / 255.0;
double color = (buf[nc * (j * nx + i) + k] / 255.);
double blend = color * (1.0 - wm_alpha_weak) + wm_color * wm_alpha_weak;
buf[nc * (j * nx + i) + k] = std::clamp(blend * 255., 0., 255.);
}
}
}
} else if (bps == 16) {
// 16bps support was never really finished, left unimplemented
}

delete[] wmbuf;
}

#undef POSITION

/*==========================================================================*/


Expand Down Expand Up @@ -1703,6 +1701,36 @@ bool SipiImage::operator==(const SipiImage &rhs) const

/*==========================================================================*/

std::optional<double> SipiImage::compare(const SipiImage &rhs) const
{
if ((nx != rhs.nx) || (ny != rhs.ny) || (nc != rhs.nc) || (bps != rhs.bps) || (photo != rhs.photo)) { return {}; }

double diff = 0;
double niters = 0;

switch (bps) {
case 8: {
byte *ltmp1 = pixels;
byte *ltmp2 = rhs.pixels;
for (size_t j = 0; j < ny; j++) {
for (size_t i = 0; i < nx; i++) {
for (size_t k = 0; k < nc; k++) {
niters++;
diff += abs(ltmp1[nc * (j * nx + i) + k] - ltmp2[nc * (j * nx + i) + k]) / 255.;
}
}
}
break;
}
case 16:
return 1;
}

return diff / niters;
}

/*==========================================================================*/


std::ostream &operator<<(std::ostream &outstr, const SipiImage &rhs)
{
Expand Down
8 changes: 7 additions & 1 deletion src/SipiImage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,14 +546,20 @@ class SipiImage
*/
bool toBitonal();

/*!
* Conclude similarity of two SipiImages, used in tests. Only tested with small differences.
*
* \returns Returns similarity index, returns in [0..1]
*/
std::optional<double> compare(const SipiImage &rhs) const;

/*!
* Add a watermark to a file...
*
* \param[in] wmfilename Path to watermarkfile (which must be a TIFF file at the moment)
*/
void add_watermark(const std::string &wmfilename);


/*!
* Calculates the difference between 2 images.
*
Expand Down
5 changes: 0 additions & 5 deletions src/formats/SipiIOTiff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,6 @@ unsigned char *read_watermark(const std::string &wmfile, int &nx, int &ny, int &

TIFF_GET_FIELD(tif, TIFFTAG_SAMPLESPERPIXEL, &spp, 1);

if (spp != 1) {
TIFFClose(tif);
throw Sipi::SipiImageError("ERROR in read_watermark: ssp ≠ 1: " + wmfile);
}

TIFF_GET_FIELD(tif, TIFFTAG_BITSPERSAMPLE, &bps, 1);

if (bps != 8) {
Expand Down
Binary file added test/_test_data/images/unit/MaoriFigure.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/_test_data/images/unit/gradient-stars.tif
Binary file not shown.
Binary file added test/_test_data/images/unit/watermark_alpha.tif
Binary file not shown.
26 changes: 20 additions & 6 deletions test/unit/sipiimage/sipiimage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,17 +310,31 @@ TEST(SipiImage, Watermark)
Sipi::SipiImage img3;
Sipi::SipiImage img4;

std::string maori = "../../../../test/_test_data/images/unit/MaoriFigure.jpg";
std::string gradstars = "../../../../test/_test_data/images/unit/gradient-stars.tif";
std::string maoriWater = "../../../../test/_test_data/images/unit/MaoriFigureWatermark.jpg";

EXPECT_TRUE(exists_file(maori));
EXPECT_TRUE(exists_file(gradstars));

ASSERT_NO_THROW(img1.read(cielab));
EXPECT_NO_THROW(img1.add_watermark(watermark_correct));

ASSERT_NO_THROW(img2.read(cielab));
ASSERT_THROW(img2.add_watermark(watermark_incorrect), std::exception);
ASSERT_NO_THROW(img2.read(cielab16));
EXPECT_NO_THROW(img2.add_watermark(watermark_correct));

ASSERT_NO_THROW(img3.read(maori));

EXPECT_NO_THROW(img3.add_watermark(gradstars));
/* ASSERT_NO_THROW(img3.write("jpg", maoriWater)); */

ASSERT_NO_THROW(img4.read(maoriWater));
EXPECT_TRUE(img4.compare(img3).value_or(1000) < 0.007); // 0.00605

ASSERT_NO_THROW(img3.read(cielab16));
EXPECT_NO_THROW(img3.add_watermark(watermark_correct));
ASSERT_NO_THROW(img3.read(maori));
EXPECT_TRUE(img4.compare(img3) > 0.017); // 0.0174

ASSERT_NO_THROW(img4.read(cielab16));
ASSERT_THROW(img4.add_watermark(watermark_incorrect), std::exception);
ASSERT_NO_THROW(img3.rotate(90));
}

TEST(SipiImage, CMYK_With_Alpha_Conversion)
Expand Down

0 comments on commit da34f6a

Please sign in to comment.