Simple photo editor

Ne vom propune pentru aceasta aplicatie sa putem modifica expunerea unei fotografii. Inainte de a incepe trebuie sa intelegem din ce este alcatuita o fotografie digitala, adica cum “intelege” calculatorul informatia fotografica si cum este ea reprezentata.

In spatele unei imagini stau 3 canale, RGB – RED, GREEN, BLUE. Culoarea unui pixel este data de suma intensitatilor subpixelilor din fiecare canal. Pentru un canal standard de 8 biti, intensitatea fiecare culoare poate lua valori intre [0,255], unde 0- negru/stins, 255- intensitate maxima.

Astfel, pentru a manipula expuenrea fotografiei, tot ce trebuie sa facem este sa manipulam valorile intensitatii tuturor canalelor simultan, crescand valorile pentru a creste expunerea respectiv scazand pentru a subexpune.

Necesitati:

  1. Domeniu de dezvoltare, ex. VS Code
  2. Git for Windows pentru a descarca bibliotecile care ne vor ajuta in realizarea aplicatiei.
  3. Instalare vcpkg care se ocupa de compilarea bibliotecilor pentru noi.
  4. Comenzi care trebuie executate in Powershell
  • cd C:\
  • git clone https://github.com/microsoft/vcpkg.git
  • cd vcpkg
  • .\bootstrap-vcpkg.bat
  • .\vcpkg integrate install
  • .\vcpkg install glfw3 imgui[opengl3-binding,glfw-binding] stb

De ce toate acestea?

  • GLFW3 – pentru creare ferestre si manipulare mouse
  • OpenGl – discutie cu placa video
  • Dear ImGui – pt sliderul de care vom trage pt expunere
  • stb_image – ne va ajuta sa trasnformam un fisier cu extensia .jpg in vectori de pixeli pe care sa ii putem manipula cum vrem.

Vom discuta acum cum m-am gandit sa abordez aplicatia.  In primul rand trebuie sa pot citi imaginea pe care o voi modifica. Am nevoie din ea de intesitatile pixelilor din fiecare canal, pe care ii voi pune intr-un vector.  Apoi ii voi putea manipula modificandu-le valoarea. Va trebui apoi sa ii retrimit spre a fi cititi.

Ce e cu fiecare biblioteca/header?

#include “stb_image.h” – cititorul de imagine. Cu ajutorul ei vom trasnforma poza intr-un sir urias de numere in RAM, adica pixelii.

#include <GLFW/glfw3.h> – partea  a GLFW explicat mai sus

#include “imgui.h” – pentru butoane si slidere

#include “imgui_impl_glfw.h” – legatura ImGUI si fereastra creata de GLFW – unde e mouse ul pe ecran

#include “imgui_impl_opengl3.h” – desenare folosind placa video

Trebuie sa intelegem ca imaginea pe care noi o vom prelucra este defapt un pointer catre o zona din memoria RAM.  Astfel, ne vom folosit de tipul de data abstract struct raw_pic pt a rezerva loc atat pentru imaginea originala, pe care nu o vom modifica, cat si pentru cea modificatata, care va fi o copie modificata a originalului.

Trecem la logica de expunere. Trebuie sa parcurgem tot vectorul de valori. Cum imaginea noastra are o latime si o inaltime numarul de valori de prelucrat va fi egal cu produsul celor doua inmultite cu 3. De ce am inmultit totusi cu 4? Mai exista un canal alpha despre care nu am vorbit, care ne spune cat de opac sau transparent este un pixel. Cum nu ne intereseaza pentru expunere, il vom ignora atunci cand vom da de el.

Ne vom ajuta de o functia pentru acest calcul. Ea va fi de tip void deoarece nu imi doresc sa mi returneze ceva, ci sa imi prelucreze niste date. Deoarece doresc sa pastrez valorile modificate ale imaginii sunt nevoit sa folosesc transferul prin referinta raw_pic& img. Este de asemena si mai efficient, caci nu mai este creata o copie la apelarea functiei. Al doilea argument este evident expunerea.

Functia este lesne de inteles. Dupa cum am spus cand ajung pe canalul nr 4 (alpha) il ignor si trec mai departe. Foarte important este sa ne asiguram ca nu iesim din limitele intensitatii pixelilor, adica valoarea pixelului sa fie intre [0, 255].

Funcția glBindTexture setează un pointer intern. Din acest moment, orice comandă catre GL_TEXTURE_2D va fi direcționată către locația de memorie video indexată de img.textureID. In alte cuvinte, functia are rolul de a arata unde facem operatiile.

Functia glTexSubImage2D este o funcție de actualizare a conținutului. Argumentele le vom lua ca atare, este sufficient pt nivelul de complexitate al aplicatiei noastre.

In aceasta functie decomprimam imaginea noastra din format jpg in siruri de valori pt fiecare canal. Functia stbi_load cu asta se ocupa. Daca totul merge cym trebuie va trebui sa alocam dinamic memorie in Heap. Acest buffer va fi destinatia calculelor matematice inainte ca datele sa fie retrimise catre procesorul grapfic.

Functia glGenTextures: Interogheaza driverul grafic pentru a rezerva un identificator unic in VRAM. Este, in esenta, o alocare de tip “handle”.

  • glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  • glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Aceste instrucțiuni definesc comportamentul Unității de Procesare a Texturilor (TMU) din GPU:

Interpolare Liniară: În cazul în care imaginea este afișată la o rezoluție diferită de cea nativă, GPU-ul va efectua o medie ponderată între pixelii vecini (bilinear filtering). Din punct de vedere vizual, acest lucru previne artefactele de tip “aliasing” (zimțarea marginilor). (Citat de  AI, nu aveam nicio idee cu ce se ocupa).

Functia glTexImage2D se ocupa de alocarea fizica de spatiu in memoria placii grafice, transfer date de la imaginea originala catre procesorul grafic, stabilirea formatului, adica o matrice de W x H cu 4 componente/pixel, adica fiecare canal.

Urmeaza main ul unde initial dechidem sau “desenam” o fereastra unde vom incarca poza si un slider de unde vom manipula valoarea expunerii.

Functia ImGui::CreateContext(); pentru initializare ImGui?

ImGui_ImplGlfw_InitForOpenGL(window, true); pentru integrare cu sistemul de operare. Astfel, daca miscam mouse ul sau apasam o tasta ImGui intercepteaza aceste evenimente.

Incarcam poza si definim parametrii de care avem nevoie.

Tot ce facem este sa cream fereastra unde vom rula aplicatia cat si slider-ul cu care vom modifica expunerea. Functiile din if (myImage.textureID != 0) {…} au rolul de a imcarca corect imaginea.

Functia ImGui::Render() nu desenează nimic pe ecran. Ea doar prehateste un pachet de date pe care GPU ul il poate intelege.

ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()) : executa desenarea interfetei ImGui. Acest ape, trebuie pus dupa desenarea pozei, altfel el va aparea in spatele pozei.

glfwSwapBuffers: fara acesta am vedea un ecran static deoarece nicio modificare nu ar fi transmisa catre monitor.

Restul tine de curatenie.

Multumesc pentru atentie!

Codul poate fi gasit integral la https://github.com/renea-bogdan/Simple-photo-editor

Mentiune: AI-ul mi a fost de ajutor in intelegerea unor concepte care au fost explicate apoi pe larg in acest tutorial, cat si in realizarea aplicatiei.