Vodeni žigovi za slike internet prodavnice: case study batch obrade stotina hiljada fajlova

2014-05-25

← Nazad na blog

Ovo je stariji, ali i dalje koristan case study: zadatak je bio da se napravi pouzdan pipeline za obradu nekoliko stotina hiljada slika za internet prodavnice, sa vodenim žigovima, resize-om i ponovljivim batch izvršavanjem bez ručnog rada.

1. Case sa poslovne strane

Cilj nije bio "napraviti lepu sliku u Photoshopu", već produkcioni pipeline za katalog:

  • obrada velikog broja slika (stotine hiljada fajlova),
  • nanošenje vodenog žiga koji ostaje čitljiv na različitim pozadinama,
  • resize za dimenzije kataloga/kartica,
  • skladištenje gotovih rezultata u AWS S3,
  • izbegavanje ručnog GUI rada i trošenja vremena dizajnera na rutinu.

Postojao je i zahtev za više prodavnica: vodeni žig je morao da se generiše za različite nazive prodavnica i zatim koristi u automatizovanom batch procesu.

Rezultat je bio batch proces koji može da radi nad velikim skupovima podataka i daje konzistentan izlaz bez "ljudskog faktora".

Pošaljite brief

2. Traženje rešenja: koji vodeni žig zaista radi (sa primerima)

Ispod je put do konačnog rešenja. Cilj je bio praktičan: da žig bude vidljiv, a da ne uništi čitljivost slike, uključujući slučajeve kada slika već sadrži tekst.

Zašto jedan poluprovidan natpis nije dovoljno dobar

Beli poluprovidan natpis nestaje na svetlim delovima slike, a taman na tamnim. Jedna boja nije dovoljno stabilna.

Primer slabe čitljivosti jednoslojnog vodenog žiga

Radna varijanta: svetao tekst + tamna poluprovidna senka

Bolji rezultat daje kombinacija belog poluprovidnog natpisa i tamne poluprovidne senke. Tako žig ostaje vidljiv na mešovitim pozadinama.

Vodeni žig sa belim tekstom i tamnom senkom

Kada slika sadrži tekst, vodeni žig ne treba da ga potpuno prekrije. Balans transparentnosti teksta i senke pomaže da čitljivost ostane prihvatljiva.

Primer vodenog žiga preko slike koja već sadrži tekst

Zašto je jedan centralni žig lakše ukloniti

Ako je žig uvek isti i uvek na istom mestu (najčešće u centru), relativno je lako lokalno ga ukloniti i zameniti.

Primer slabosti jednog centralnog vodenog žiga

Konačan pristup: popločavanje cele slike

Da bi uklanjanje bilo teže, žig se nije nanosio samo u jednoj tački, već kao mreža preko cele slike. Centralni element je služio kao referenca, a ostali su raspoređeni oko njega. Rotacija natpisa je vizuelno uredila rezultat.

Popločani rotirani vodeni žigovi preko slike

Slike sa bobicama i pomorandžom: Kirill Krasnov. Shutterstock: http://www.shutterstock.com/cat.mhtml?gallery_id=419308.

3. Tehnički detalji

Ovo je implementacija iz 2014. godine. U to vreme Cloudflare Images još nije postojao. Danas za mnoge slične zadatke ima smisla koristiti managed rešenje "iz kutije" (storage, transformacije/resize, delivery). Ova custom implementacija ostaje ovde u prezentativne svrhe: pokazuje kako je batch obrada slika ručno sastavljena.

Tadašnji pipeline je izgledao ovako:

  • generisanje PNG vodenog žiga za konkretnu prodavnicu,
  • čitanje izvorne slike,
  • resize na ciljane dimenzije kataloga,
  • nanošenje popločanih vodenih žigova,
  • čuvanje rezultata i upload u AWS S3.

3.1. Generisanje vodenog žiga (Perl + ImageMagick)

Ispod je skript koji generiše PNG vodeni žig sa senkom i rotacijom. Taj asset se zatim koristi za popločavanje.

#!/usr/bin/perl -w

use strict;
use Image::Magick;

die `pod2text $0` unless @ARGV;

# Kreiramo platno sa providnom pozadinom
my $image = Image::Magick->new(size=>'1000x70');
$image->ReadImage('canvas:transparent');

# Iscrtavamo natpis crnom bojom sa 30% providnosti
$image->Annotate(
	text      => $ARGV[0],
	geometry  => "+50+50",
	pen       => $image->QueryColorname('rgba(0,0,0,0.3)'),
	font      => 'Bookman-Demi',
	pointsize => 40,
	kerning   => 3,
);

# Zamućujemo ga da dobijemo senku
$image->Blur(
	radius  => 0,
	sigma   => 6,
	channel => 'RGBA'
);

# Kreiramo masku za glavni tekst
my $mask = Image::Magick->new(size=>'1000x70');
$mask->ReadImage('canvas:transparent');
$mask->Annotate(
	text      => $ARGV[0],
	geometry  => "+50+50",
	pen       =>  $image->QueryColorname('rgba(255,255,255,1)'),
	font      => 'Bookman-Demi',
	pointsize => 40,
	kerning   => 3,
);

# Čistimo centralni deo senke ispod belog teksta
$image->Composite(
	image   => $mask,
	mask    => $mask,
	compose => 'Clear',
);

# Iscrtavamo poluprovidan beli tekst preko svega
$image->Annotate(
	text      => $ARGV[0],
	geometry  => "+50+50",
	pen       => $image->QueryColorname('rgba(255,255,255,0.3)'),
	font      => 'Bookman-Demi',
	pointsize => 40,
	kerning   => 3,
);

$image->Trim();

# Rotiramo natpis
$image->Rotate(
	degrees    => -45,
	background => 'transparent',
);

# Čuvamo kao PNG
if ($ARGV[1]) {
	$image->Write("$ARGV[1]");
}
else {
	$image->Write("$ARGV[0].png");
}

3.2. Nanošenje popločanih vodenih žigova na sliku

Drugi skript računa mrežu, centrira je u odnosu na izvornu sliku i popločava vodeni žig preko cele slike.

#!/usr/bin/perl -w

use strict;
use Image::Magick;
use POSIX qw/ceil/;

die `pod2text $0` unless @ARGV;

# Izvorna slika
my $image = Image::Magick->new;
$image->Read("jpg:$ARGV[0]");
my ($image_height, $image_width) = $image->Get('base-rows', 'base-columns');

# Slika vodenog žiga
my $watermark = Image::Magick->new;
$watermark->Read("png:$ARGV[1]");
my ($watermark_height, $watermark_width) = $watermark->Get('base-rows', 'base-columns');

# Platno je potrebno ako je žig veći od izvorne slike
my $canvas_height = ( $image_height > $watermark_height ? $image_height : $watermark_height );
my $canvas_width  = ( $image_width  > $watermark_width  ? $image_width  : $watermark_width  );

my $canvas = Image::Magick->new;
$canvas->Set(size => "${canvas_width}x${canvas_height}");
$canvas->Read('NULL:');

my $tiled_layer = Image::Magick->new;
$tiled_layer->Set(size => "${canvas_width}x${canvas_height}");
$tiled_layer->Read('NULL:');

# Veličina mreže
my $tile_columns = ceil($image_width / $watermark_width);
my $tile_rows    = ceil($image_height / $watermark_height);

# Pravimo neparnu mrežu da bismo imali centralni element
$tile_columns++ if $tile_columns % 2 == 0;
$tile_rows++    if $tile_rows % 2 == 0;

my $center_col = ceil($tile_columns / 2);
my $center_row = ceil($tile_rows / 2);
my $center_x = ($image_width - $watermark_width) * 0.5;
my $center_y = ($image_height - $watermark_height) * 0.5;

for my $col (1 .. $tile_columns) {
	for my $row (1 .. $tile_rows) {
		my $x = $center_x + ($col - $center_col) * $watermark_width;
		my $y = $center_y + ($row - $center_row) * $watermark_height;

		$tiled_layer->Composite(
			image   => $watermark,
			compose => 'over',
			x       => $x,
			y       => $y,
			gravity => 'NorthWest',
		);
	}
}

$canvas->Composite(
	image   => $image,
	compose => 'over',
	gravity => 'center',
);

$canvas->Composite(
	image   => $tiled_layer,
	compose => 'over',
);

$canvas->Crop(
	x      => ($canvas_width - $image_width) * 0.5,
	y      => ($canvas_height - $image_height) * 0.5,
	width  => $image_width,
	height => $image_height,
);

$canvas->Set(quality => 88);
$canvas->Write("jpg:$ARGV[2]");

3.3. Gde su resize i S3 u ovom procesu

U produkciji su ovi skriptovi bili deo većeg batch pipeline-a: nakon čitanja izvornog fajla radio se resize za potrebne formate kataloga, zatim watermarking, a na kraju upload gotovih verzija u AWS S3. Dakle, nije bio u pitanju samo "jedan efekat", već tok pripreme asset-a za prodavnicu.

Pojednostavljen CLI primer (radi ilustracije procesa):

# 1) Generisanje PNG vodenog žiga za prodavnicu
perl gen-watermark.pl "example-shop.rs" /tmp/example-shop-watermark.png

# 2) Priprema resize verzije slike (resize se radio u pipeline-u)
# 3) Nanošenje vodenog žiga
perl watermark.pl input.jpg /tmp/example-shop-watermark.png output.jpg

# 4) Zatim upload rezultata u AWS S3

Konačan rezultat sa popločanim vodenim žigovima

Za nove projekte danas bih najpre gledao Cloudflare Images ili slične managed servise. Ali kao case koji pokazuje logiku custom obrade velikog broja slika, ovaj primer je i dalje koristan.