Vodeni žigovi za slike internet prodavnice: case study batch obrade stotina hiljada fajlova
2014-05-25
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".
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.
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.
Kada slika sadrži tekst, vodeni žig ne treba da ga potpuno prekrije. Balans transparentnosti teksta i senke pomaže da čitljivost ostane prihvatljiva.
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.
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.
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 S3Za 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.