Să analizăm Azure Functions din perspectiva programatorului. În acest articol vom scrie o funcţie Azure (Azure Function) care adaugă coordonatele GPS pentru o imagine sub formă de watermark. Dacă vă interesează doar partea de cod, fără explicaţii, vă invit să vizitaţi GitHub:
Cea mai bună explicaţie pe care o putem da este că Azure Functions sunt AWS Lambda din lumea Azure. Funcţiile ne permit să executăm codul fără server, fără a ne gândi unde este găzduit codul. Pur şi simplu scrieţi codul şi rulaţi-l ca Azure Function.
Caracterizând Azure Functions, reies următoarele trăsături:
Codul poate fi scris în limbaje diferite (C#, Node.JS, PHP, Python, Bash, Batch, PowerShell).
Există suport pentru conectori multipli şi triggere, de la queues la Blob Storage şi Google Drive.
Modelul de încărcare este flexibil şi transparent. Plătiţi doar pentru ceea ce utilizaţi.
Procesarea se face în timp real.
Misiunea noastră este de a scrie o funcţie Azure care:
Este declanşată de fiecare dată când o nouă imagine este adăugată în fişierul OneDrive.
Extrage coordonatele GPS din atributele unei imagini (dacă sunt disponibile).
Scrie coordonatele GPS pe imagine.
Primul pas este crearea unei funcţii Azure. Acest lucru se poate realiza de pe Azure Portal, iar o documentaţie explicită în acest sens se poate găsi aici. Aici putem crea iniţial o funcţie 'GenericWebhookCSharp', apoi vom elimina părţile de care nu avem nevoie.
După ce facem acest lucru, vom accesa tabul Integrate şi ne vom specifica triggerul. Ştergeţi toate triggerele şi outputurile care sunt deja definite. Le vom defini din nou.
În cazul de faţă va trebui să folosim External File, iar în câmpul connection vom crea unul nou care va face referire la OneDrive. Este important de ştiut că, deşi credenţialele de acces sunt nişte interogări, acestea nu sunt stocate în Azure Functions. Acestea sunt stocate drept API Connections, iar aceeaşi conexiune poate fi folosită în funcţii multiple dacă este necesar. Acest trigger va fi apelat când un fişier nou va fi copiat pe calea dată. Drept output, vom folosi acelaşi External File. Acum, putem refolosi aceeaşi conexiune pe care am creat-o pentru trigger - onedrive_ONEDRIVE.
Acum, să analizăm legăturile (bindings).
{
"bindings": [
{
"type": "apiHubFileTrigger",
"name": "inputFile",
"path": "Imagini/Peliculă/{filename}",
"connection": "onedrive_ONEDRIVE",
"direction": "in"
},
{
"type": "apiHubFile",
"name": "outputFile",
"path": "Imagini/Peliculă/pictureswithgpswatermark/{rand-guid}.jpg",
"connection": "onedrive_ONEDRIVE",
"direction": "out"
}
],
"disabled": false
}
Primul binding specifică triggerul. După cum se poate observa, direction este "in", iar connection este racordat la OneDrive. Câmpul path este relativ la OneDrive-ul nostru. În cazul nostru, fişierul monitorizat este 'Imagini/Peliculă'. {filename} este parametrul de fişier. În Azure Function, ne vom referi la input file utilizând atributul 'name' - inputFile.
În mod similar, avem outputul care este înregistrat în fişierul "Imagini/Peliculă/pictureswithgpswatermark". '{rand-guid}' este folosit pentru a se genera un nume aleatoriu pentru fiecare imagine.
După cum putem observa, inputFile şi outputFile sunt parametrii metodei Run. Această metodă este punctul de intrare de fiecare dată când rulează triggere. Dacă doriţi să scrieţi ceva în loguri, puteţi folosi cu succes TraceWriter, care trebui specificat drept parametru.
public static void Run(Stream inputFile,
Stream outputFile, TraceWriter log)
{
log.Info("Image Process Starts");
log.Info("Image Process Ends");
}
Putem să ne definim propriile clase, librării bazate pe referinţe sau pachete Nuget. Pentru a putea lucra cu acest tip de fişiere şi bindings, trebuie să adăugăm o referinţă în ApiHub. Altfel, rezultatul va fi o eroare criptată:
Exception while executing function: Functions.SaasFileTriggerCSharp1. Microsoft.Azure.WebJobs.Host: One or more errors occurred. Exception binding parameter 'input'. Microsoft.Azure.ApiHub.Sdk
Referinţa este adăugată, dar specifică Azure Functions pentru a încărca ansamblul (the assembly) din propriul shared repository.
#r "Microsoft.Azure.WebJobs.Extensions.ApiHub" //
Înainte de a apăsa butonul Save, asiguraţi-vă că Logs window este vizibilă. Acest lucru este util, deoarece, de fiecare dată când apăsaţi butonul Save, funcţia este compilată. Orice eroare din timpul buildului este afişată în Logs window.
De acum înainte, de fiecare dată când copiaţi/încărcaţi un fişier nou în fişierul OneDrive, funcţia voastră va fi apelată automat. În loguri veţi putea vedea logurile de output.
Pentru a putea citi localizarea GPS din imagini, vom folosi ExifLib. Acest pachet Nuget ne permite să citim informaţia GPS cu uşurinţă. Pentru a face o operaţiune push pentru Nuget package, vom deschide project.json şi vom adăuga o dependinţă pachetului Nuget. Mai jos găsiţi cum ar trebuie să arate JSON. Am mai adăugat pachetul Nuget care va fi utilizat ulterior pentru a scrie coordonatele pe imagine.
{
"frameworks": {
"net46":{
"dependencies": {
"ExifLib": "1.7.0.0",
"System.Drawing.Primitives": "4.3.0"
}
}
}
}
Când apăsaţi butonul Save, veţi observa că funcţia este compilată, că pachetul Nuget şi toate dependinţele pachetelor sunt descărcate. Când rulăm codul care extrage locaţia GPS vom lua în considerare cazurile în care imaginea nu are această informaţie.
private static string GetCoordinate(Stream image, TraceWriter log)
{
log.Info("Extract location information");
ExifReader exifReader = new ExifReader(image);
double[] latitudeComponents;
exifReader.GetTagValue(ExifTags.GPSLatitude,
out latitudeComponents);
double[] longitudeComponents;
exifReader.GetTagValue(ExifTags.GPSLongitude,
out longitudeComponents);
log.Info("Prepare string content");
string location = string.Empty;
if (latitudeComponents == null ||
longitudeComponents == null)
{
location = "No GPS location";
}
else
{
double latitude = 0;
double longitude = 0;
latitude = latitudeComponents[0] +
latitudeComponents[1] / 60 +
latitudeComponents[2] / 3600;
longitude = longitudeComponents[0] +
longitudeComponents[1] / 60 +
longitudeComponents[2] / 3600;
location = $"Latitude: '{latitude}' |
Longitude: '{longitude}'";
}
return location;
}
Următorul pas este apelarea metodei noastre din metoda Run (string locationText = GetCoordinate(inputFile, log). După ce salvăm, localizarea GPS pentru fiecare imagine se poate găsi în log window.
string locationText = GetCoordinate(inputFile, log);
log.Info($"Text to be written: '{locationText}'");
---- log window ----
2016-12-06T00:06:35.680 Image Process Starts
2016-12-06T00:06:35.680 Extract location information
2016-12-06T00:06:35.680 Prepare string content
2016-12-06T00:06:35.680 Text to be written: 'Latitude: '46.7636219722222' | Longitude: '23.5550620833333''
Scrierea coordonatelor watermark
Ultimul pas este scrierea textului pe imagine şi copierea streamului de imagine în output. Codul este acelaşi precum cel de care e nevoie de aplicaţia consolă pentru acelaşi task.
private static void WriteWatermark(string watermarkContent, Stream originalImage, Stream newImage, TraceWriter log)
{
log.Info("Write text to picture");
using (Image inputImage = Image
.FromStream(originalImage, true))
{
using (Graphics graphic = Graphics
.FromImage(inputImage))
{
graphic.SmoothingMode = SmoothingMode.HighQuality;
graphic.InterpolationMode = InterpolationMode
.HighQualityBicubic;
graphic.PixelOffsetMode = PixelOffsetMode
.HighQuality;
graphic.DrawString(watermarkContent,
new Font("Tahoma", 100, FontStyle.Bold),
Brushes.Red, 200, 200);
graphic.Flush();
log.Info("Write to the output stream");
inputImage.Save(newImage, ImageFormat.Jpeg);
}
}
}
Nu uitaţi să resetaţi poziţia cursorului streamului inputFile înainte de a apela WriteWatermark. Acest lucru este necesar, deoarece citirea coordonatelor va muta cursorul din poziţia 0. La final, metoda Run trebuie să arate astfel:
public static void Run(Stream inputFile,
Stream outputFile, TraceWriter log)
{
log.Info("Image Process Starts");
string locationText = GetCoordinate(inputFile, log);
log.Info($"Text to be written: '{locationText}'");
// Reset position. After Exif operations the cursor
// location is not on position 0 anymore;
inputFile.Position = 0;
WriteWatermark(locationText, inputFile, outputFile,
log);
log.Info("Image Process Ends");
}
Rezultatul final al funcţiei noastre va fi o imagine cu coordonatele scrise în ROŞU. A se vedea în figura alăturată.
În acest articol am analizat cum putem scrie o funcţie Azure care adaugă, sub forma unui watermark, coordonatele GPS ale locului unde a fost făcută poza.
Azure Functions este un tool puternic ce ne permite să ne concentrăm doar pe cod fără a ne gândi la infrastructură sau la alte aspecte.
Codul complet se găseşte pe GitHub.
de Cassian Lup
de Claudia Mihu
de Ovidiu Mățan
de Oana Călugar