Oczywiście obrazek zawsze można umieścić w systemie plików, a w samej bazie zapisać ścieżkę dostępu. Jednak w ten sposób musimy dbać o dwie bazy: relacyjną i plikową. Może to powodować niepotrzebne komplikacje i z punktu widzenia szybkości działania programu jest rozwiązaniem wolniejszym.
W tym wpisie zostanie pokazane jak przygotować małą, graficzną bazę danych w oparciu o CImg i Sqlite.
Biblioteka Sqlite świetnie nadaje się do takich zastosowań, ponieważ działa w obrębie tworzonego programu - baza danych jest wbudowywana w aplikację. Jest to zupełnie inne podejście, aniżeli to spotykane w dużych, komercyjnych bazach danych działających w trybie klient-serwer. W tym wypadku mamy bazę plikową, która jest zalecana do pracy z jednym klientem.
Aby rozpocząć pracę z Sqlite należy zainstalować następujące pakiety (przykład dla Ubuntu):
sudo apt-get install sqlite3 libsqlite3-0 libsqlite3-dev
Następnie utworzymy testową bazę danych, a w niej jedną tabelę do gromadzenia obrazów. Będą one zapisywane w polu photo typu blob.
sqlite3 image_db.sqlite3 create table photos(id integer primary key autoincrement, photo blob, filename char, dimx integer, dimy integer, dimz integer, dimv integer); .q
Poniżej został zaprezentowany przykładowy program pozwalający na dodawanie obrazów do bazy danych oraz ich odczyt. Analiza kodu oraz komentarzy w nim zawartych powinna umożliwić szybkie zrozumienie jego logiki działania.
#include <stdio.h> #include <stdlib.h> #include <sqlite3.h> #include "CImg.h" using namespace std; using namespace cimg_library; int main(int argc, char **argv){ sqlite3 *db; char *zErrMsg = 0; int rc; int dx,dy,dz,dv; int rownum=0, num_bytes; const unsigned char *filename; const unsigned char *data_buffer; //Parametry wejściowe const char* file_i = cimg_option("-i", (char*)0, "Input image"); const char* db_name = cimg_option("-d", (char*)0, "Database file"); const int option = cimg_option("-o", 1,"Option (1-show, 2-add)"); //Sprawdzamy poprawność parametrów wejściowych if (option != 1 && option != 2) throw CImgException("Please specify correct option (1-show, 2-add)"); if (db_name==(char*)0) throw CImgException("Please specify database file (option -d)"); if (option == 2 && file_i==(char*)0) { throw CImgException("Please specify image file name (option -i)"); exit(1); } //Otwarcie bazy danych rc = sqlite3_open(db_name, &db); if( rc ){ fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); sqlite3_close(db); exit(1); } //Definicja zapytań sqlite3_stmt *insert_stmt, *select_stmt; char insert_str[] = "insert into photos(photo,filename,dimx,dimy,dimz,dimv) values (?,?,?,?,?,?)"; char select_str[] = "select id, photo,filename,dimx,dimy,dimz,dimv from photos"; char begin_str[] = "begin transaction"; char commit_str[] = "commit transaction"; //Zapis do bazy if (option == 2) { //Odczyt obrazka z pliku CImg<unsigned char> image(file_i); //Poczatek tranzakcji sqlite3_exec (db, begin_str, NULL, NULL, NULL); sqlite3_prepare(db, insert_str, -1, &insert_stmt, NULL); //Wstawienie danych do tabeli sqlite3_bind_blob(insert_stmt, 1, image.ptr(), image.size() * sizeof(unsigned char), SQLITE_STATIC); sqlite3_bind_text(insert_stmt, 2, file_i, strlen(file_i)* sizeof(unsigned char), SQLITE_STATIC); sqlite3_bind_int (insert_stmt, 3, image.dimx()); sqlite3_bind_int (insert_stmt, 4, image.dimy()); sqlite3_bind_int (insert_stmt, 5, image.dimz()); sqlite3_bind_int (insert_stmt, 6, image.dimv()); sqlite3_step (insert_stmt); sqlite3_reset(insert_stmt); //Zakończenie tranzakcji sqlite3_exec(db, commit_str, NULL, NULL, NULL); } else { //option==1 //Odczyt danych z tabeli sqlite3_prepare(db, select_str,-1, &select_stmt, NULL); while (sqlite3_step(select_stmt) == SQLITE_ROW) { //odczytujemy nazwe pliku filename = sqlite3_column_text(select_stmt, 2); //odczytujemy rozmiary obrazu dx = sqlite3_column_int(select_stmt, 3); dy = sqlite3_column_int(select_stmt, 4); dz = sqlite3_column_int(select_stmt, 5); dv = sqlite3_column_int(select_stmt, 6); //pobieramy obraz int num_bytes = sqlite3_column_bytes(select_stmt,1); num_bytes = num_bytes / sizeof(unsigned char); data_buffer = new unsigned char[num_bytes]; data_buffer = (const unsigned char*)sqlite3_column_blob(select_stmt, 1); printf("%s (%d,%d,%d,%d)\n",filename,dx,dy,dz,dv); //tworzymy i wyświetlamy obraz CImg<unsigned char> tmp(data_buffer,dx,dy,dz,dv); CImgDisplay main_img(tmp, "Obraz wejsciowy"); while (!main_img.is_closed); } } sqlite3_close(db); return 0; }
Podczas kompilacji poza standardowymi bibliotekami wymaganymi przez CImg dołączamy również bibliotekę sqlite.
g++ imagedb1.cpp -o imagedb1 -lX11 -lpthread -lsqlite3
Przykładowe wywołanie programu dodające 2 obrazki do bazy:
./imagedb1 -d image_db.sqlite3 -o 2 -i input1.bmp ./imagedb1 -d image_db.sqlite3 -o 2 -i input2.bmp
Wyświetlenie obrazów z bazy:
./imagedb1 -d image_db.sqlite3
Analizując wyżej przedstawiony przykład łatwo można dostrzec zalety korzystania z baz danych w aplikacjach służących do przetwarzania obrazu. Najważniejszą z nich jest możliwość zapisywania stanu aplikacji i kontynuowania pracy po jego kolejnym uruchomieniu.