Flussi di I/O – operazioni elementari
Il C++ dispone di una libreria fstream per l’I/O su file, derivata dalla libreria iostream (quella, già
incontrata, che contiene le dichiarazioni per l’I/O su console).
I flussi vengono descritti tramite classi. Semplificando, come “classe” possiamo intendere un elenco di dati e
funzioni (rispettivamente chiamati proprietà e metodi), normalmente collegati tra loro. Variabili chiamate
oggetti, dichiarate come appartenenti1 alla classe, dispongono di tutte le proprietà e possono essere elaborate
con tutti i metodi associati.
Ad esempio, nella libreria iostream sono dichiarate le classi istream e ostream:
 cin è un oggetto di classe istream; ad esso è associata l’operazione indicata con >>
 cout è un oggetto della classe ostream; ad esso è associata l’operazione indicata con <<
Questi oggetti non vanno nemmeno dichiarati, fanno parte delle variabili globali (note in ogni parte di un
programma), per la precisione sono due flussi di dati globali (global streams).
Nel caso dell’I/O su disco, i flussi vanno invece dichiarati; inoltre vanno associati ad un nome di file
(filename) nell’operazione di apertura (altrimenti il file system non saprebbe in quale cartella registrare i
dati). È anche buona norma chiudere il flusso alla fine delle operazioni, anche se la chiusura viene effettuata
automaticamente alla chiusura del programma.
Vediamo nell’esempio come registrare (output) informazioni in formato testo (txt):
#include <fstream>
using namespace std;
int main()
{
ofstream flow;
//
stream di output
flow.open( "testo.txt");
//
apertura (con filename)
flow << "Cochrane Foxx Bishop" << endl;
flow << "Boley Dykes Simmons" << endl;
//
registrazione righe
flow << "Miller Haas Grove" << endl;
flow.close();
//
chiusura
getchar();
}
Nell’esempio specifico, la dichiarazione ofstream implica l’apertura in output (il file viene “creato” se
inesistente, sovrascritto se esistente). Come (quasi) sempre avviene con gli oggetti, i metodi vengono attivati
con l’operatore punto (dot operator), che segnala l’appartenenza alla classe: secondo la terminologia OOP2,
si può dire che “il metodo open appartiene all’oggetto flow“.
Il programma appena visto fa in modo che nella cartella di esecuzione venga a trovarsi un nuovo file testo, di
nome testo.txt, che può essere aperto con un apposito programma (blocco note, notepad++, …), oppure
visualizzato nello shell (prompt dei comandi) con il comando type.
Modifica con “blocco note”
1
Esame da shell con type
Appartenere ad una classe è un’espressione tecnicamente scorretta, dal momento che una classe è una descrizione,
non un contenitore: essendo però entrata nel gergo, il suo uso non provoca problemi.
2
Object Oriented Programming, o “programmazione orientata agli oggetti”. La pronuncia dell’acronimo è molto
soggettiva: su www.sitepoint.com è stato anche proposto un sondaggio, secondo il quale la maggior parte scandisce
le lettere “O O P”, un po’ meno preferisce la parola intera ”u:p”, poi seguono altre scelte come “double-O P”. Non
manca chi disdegna la sigla e usa le parole intere.
Questa visualizzazione dei dati è certamente meglio di quella nota, in tempi storici, come dump (discarica),
che agli albori dell’informatica era l’unico modo di esaminare i dati. Se volessimo metterci nei panni delle
ragazze dell’ENIAC3, il vero contenuto del file comprende dai seguenti 496 bit:
0100001101101111011000110110100001110010011000010110111001100101001000000100011001101111011110000111100000100
0000100001001101001011100110110100001101111011100000000110100001010010000100110111101101100011001010111100100
1000000100010001111001011010110110010101110011001000000101001101101001011011010110110101101111011011100111001
1000011010000101001001101011010010110110001101100011001010111001000100000010010000110000101100001011100110010
000001000111011100100110111101110110011001010000110100001010
(in rosso la "v" di "Grove", valore 118)
Un tuffo nel passato: due “ragazze dell’ENIAC”, Gloria Ruth Gordon ed Ester Gerston,
girano per il computer collegando un cavo per ogni bit.
In realtà, per esaminare il contenuto del file direttamente, in alcuni text editor4 esiste un’opzione che
permette di visualizzare l’esatto contenuto del file, byte per byte. Altri programmi, detti hex editor5, si
aprono direttamente in questa modalità, mostrando da una parte il dump esatto di ciò che è registrato sul
disco, dall’altra i corrispondenti caratteri visualizzabili. Nell’esempio è stato usato il “Free Hex Editor Neo”.
Questo esame permette di risolvere un’apparente incongruenza: il file risulta lungo 62 byte, mentre la somma
dei caratteri presenti nelle tre righe scritte è di 56. In realtà i byte mancanti sono i tre newline (a capo),
ciascuno formato da due caratteri, che hanno i valori decimali 13 e 10. Il primo indica un “carriage return”
(ritorno carrello), il secondo un “line feed” avanzamento riga), termini che ricordano le vecchie macchine da
scrivere. Non essendoci caratteri visualizzabili corrispondenti, nella parte di testo sono segnalati con dei
punti. Alcuni esempi di codifica:
CARATTERE
cr/lf (newline)
blank
'a' (minuscola)
'S' (maiuscola)
3
ESADECIMALE
0d 0a
20
61
53
BINARIO
0000 1101 0000 1010
0010 0000
0110 0001
0101 0011
DECIMALE
13 10
32
97
83
Dette inizialmente “computer”, formavano un gruppo di circa 80 agguerrite scienziate, il cui contributo allo sviluppo
dell’informatica fu fondamentale.
4
Sono applicazioni con limitate possibilità di effetti grafici, normalmente usati dai programmatori per scrivere il codice
sorgente dei programmi.
5
Così chiamati perché ogni byte è visualizzato in base 16, con una cifra esadecimale per ogni nibble.
Per leggere il file testo appena registrato si può usare un flusso ifstream, come nel prossimo esempio. Il
nuovo programma richiede un ciclo, dal momento che il testo contiene varie parole. Prestiamo particolare
attenzione a come viene scritto il ciclo, per costruire un programma correttamente strutturato.
Il ciclo termina quando il metodo eof (end of file) restituisce true.
ifstream flow;
string z;
È fondamentale, nella struttura del ciclo, leggere un dato
(solitamente è il primo) prima di entrare. Questo
flow.open( "testo.txt");
permette di gestire ogni dato nello stesso modo: altri
flow >> z;
//
1
costrutti rendono il ciclo più complesso, perché sorge la
while( ! flow.eof())
//
2
necessità di verificare se il flusso è terminato o meno.
{
cout << z << " "; //
3
La tecnica è detta read ahead (leggere prima), ed è
flow >> z;
//
4
usata anche nella gestione del traffico dei dati tra la
}
RAM e le memorie periferiche.
flow.close();
Si può notare che l’operatore >> considera terminato l’input non solo al “fine linea”, ma anche ad ogni
spazio; il risultato della lettura è il seguente:
Cochrane Foxx Bishop Boley Dykes Simmons Miller Haas Grove
Per leggere le righe esattamente come sono state registrate si usa la funzione getline:, che permette di
leggere una riga completa, compreso il doppio carattere di newline:
ifstream flow;
string z;
Il risultato del nuovo programma corrisponde
flow.open( "testo.txt");
al modo in cui sono stati registrati i dati:
getline( flow, z);
//
1
while( ! flow.eof())
//
2
Cochrane Foxx Bishop
{
Boley Dykes Simmons
cout << z;
//
3
Miller Haas Grove
getline( flow, z);
//
2
}
flow.close();
Oltre alle due classi di flusso incontrati, ne esiste una più completa, detta fstream, che può essere usata in
input o in output a seconda della modalità di apertura. Questa viene specificata come secondo parametro del
metodo open, (detto open mode). Ci sono svariate possibilità, tra le quali quelle interessanti sono 3: input,
output, append (aggiunta). La sintassi è un po’ criptica, ma si può intuire; un’altra stranezza è il prefisso che
descrive l’open mode, che può essere ios (input/output stream) oppure fstream (file stream): Le modalità
possono essere multiple, consentendo quindi più operazioni: vanno separate da un operatore |.
fstream flow;
flow.open("testo.txt",
flow.open("testo.txt",
flow.open("testo.txt",
flow.open("testo.txt",
flow.open("testo.txt",
flow.open("testo.txt",
ios::out);
fstream::out);
//
ios, fstream: EQUIVALENTI
ios::in);
fstream::in);
ios::in | ios::out | ios::app);
fstream::in | fstream::out | fstream::app);
Ad esempio, un programma simile a quello iniziale, con l’apertura del file testo in append e gli stessi output,
aggiunge nuovi dati in coda a quelli già presenti, e il file può essere raddoppiato, come nell’esempio:
Binary I/O
Le operazioni di I/O su file di solo testo sono molto rare (tranne che, per ragioni probabilmente legate alla
semplicità della correzione, nei corsi e negli esami scolastici): nella realtà i dati da scrivere, oltre ad essere
più complessi, solitamente necessitano di essere registrati in formati numerici. In particolare, osserveremo
come memorizzare i numeri interi. Nel C++ si definisce binary un flusso di dati in formato “non di
testo”, e come tale va indicato nel metodo open.
fstream flow; char w; int i;
flow.open("bin.dat", ios::out | ios::binary);
w = 66;
flow.write( &w, 1);
Per registrare i dati nel file serve un apposito
w = 'A';
metodo del flusso, che sostituisce l’operatore
flow.write( &w, 1);
<<, riservato per le operazioni di testo:
i = +1000000;
flow.write((char*)&i, sizeof(int));
flow.write(<dato>,<lunghezza>)
i = -1000000;
flow.write((char*)&i, sizeof(int));
Le caratteristiche del metodo write sono 3:



(char*) trasforma (temporaneamente) un dato in una sequenza di char. È necessaria perché
anche un file binario è un flusso di byte. Questa operazione, detta typecasting (to cast =dare
forma), induce il programma a trattare un dato in un modo prestabilito;
& rende disponibile l’indirizzo del dato (una scelta un po’ curiosa);
sizeof(...) serve perché il metodo richiede quanti sono i byte da registrare; con questa
funzione lasciamo che sia il programma a fare i calcoli, qualunque sia il tipo di dato interessato
(ad esempio, a seconda del compilatore C++, il tipo int potrebbe essere di 2 o di 4 byte).
Osservando il dump del file, si può notare che, mentre i due char (che corrispondono alle lettere B e A) sono
registrati esattamente come scritto nello snippet; i due numeri interi (in notazione binaria complementare a
32 bit), hanno i byte registrati in ordine inverso:
ESADECIMALE
42 41
40 42 0f 00
c0 bd f0 ff
BINARIO
01000010 01000001
01000000 01000010 00001111 00000000
11000000 10111101 11110000 11111111
ASCII/DECIMALE
BA
+1000000
-1000000
Rovesciando l’ordine dei byte troviamo l’effettiva codifica dei dati int (+1000000 e -1000000) :
0000 0000
0000 1111
0100 0010
0
0
0
f
4
2
1111 1111
1111 0000
1011 1101
f
f
f
0
b
d
11111111111100001011110111000000
00000000000011110100001000111111
00000000000000000000000000000001
00000000000011110100001001000000
0100 0000
4
0
0*160+4*161+2*162+4*163+15*164
1100 0000
c
0
calcolo del valore di questo numero negativo
rovesciamento dei bit
+1
l’opposto del numero (corrisponde a +1000000)
Gestione delle immagini
A volte capita anche di dover raggruppare i dati per rispettare gli standard di
registrazione, proprio come nel caso delle immagini. Il formato che verrà preso
in esame è il più semplice: un’immagine “raster” in formato “bitmap”
monocromatico (bianco e nero, o B/W), senza compressione.
Un’immagine raster6 è una matrice di punti colorati (pixel7), che può essere
memorizzata in vari formati8. L’aspetto e la qualità dell’immagine sono
determinati dalla sua risoluzione e dal numero di colori usati: una bassa
risoluzione (o uno fattore di zoom elevato) comporta un’immagine più sgranata.
Il termine raster deriva dal latino rastrum (rastrello), ed è nato per descrivere la modalità di scansione dei
vecchi monitor CRT, nei quali un fascio di elettroni disegna l’immagine passando lo schermo riga per riga.
Nel formato bitmap, ogni pixel dell’immagine è memorizzato separatamente (proprio come in una “mappa”),
con tutte le informazioni necessarie per la sua visualizzazione.
Il numero di bit associati ad un pixel varia a seconda numero di colori:
COLORI
2
16
256
65536
16M
4G
BIT PER PIXEL N. EFFETTIVO
1
4
8
16
24
16.777.216
32 4.294.967.296
Nel formato monocromatico è sufficiente un bit per descrivere un pixel, quindi la mappa dell’immagine
occuperà all’incirca 1/8 del numero totale di pixel (calcolato con il prodotto larghezza * altezza).
In realtà i calcoli sono leggermente diversi, dato che i dati sono memorizzati in blocchi da 4 byte; la
corrispondenza è tanto più precisa quanto la larghezza è vicina ad un multiplo di 32 pixel. In quel caso tutto
il blocco di 4 byte è significativo, altrimenti ci sarà un certo numero di bit di “tara”, il cui numero può anche
essere calcolato con precisione.
Ad esempio, calcoliamo lo spazio necessario per una riga di 173 pixel. (173+7)/8=22 (il numero di byte
necessario per i 173 bit). In 22 byte ci sono 176 bit, quindi i bit di tara per ogni blocco sono almeno 3 (ma
non è finita) 22/4+1=6, 6*4=24 (la riga contiene un multiplo di 4 byte), quindi ci sono altri 16 bit di tara. In
totale, per ogni riga ci sono 19 bit di tara, il cui contenuto non viene elaborato. Alla fine, il numero di byte
necessario per ogni riga è 24. Moltiplicando per l’altezza dell’immagine, si ottiene lo spazio totale necessario
per memorizzare i dati dell’immagine (in realtà, come vedremo nel prossimo paragrafo, il file dell’immagine
contiene anche altre informazioni di servizio).
Le formule generali sono le seguenti:
CALCOLO SPAZIO OCCUPATO (w=width; h=height)
1) Byte per riga
b = int((w+7)/8) 1 byte=8 pixel
b = 4*((b-1)/4+1) blocchi di 4 byte
2) Byte totali
b = b * h
6
In computer graphics le immagini raster sono generalmente usate come sfondo; le figure geometriche sono in un altro
formato, detto vettoriale, che non è oggetto di questo capitolo.
7
Pixel=Pictorial element
8
I più comuni: BMP (bitmap), GIF (Graphic Interchange Format), JPG (actually JPEG, Joint Photographic Experts
Group), TIFF (Tagged Image File Format), PNG (Portable Network Graphics), MrSID (Multiresolution Seamless
Image Database).
Formato immagine BMP
Il formato più semplice è il bitmap monocromatico (B/W), che esamineremo in dettaglio.
Un file BMP di questo tipo è composto dalle seguenti parti:




File header (14 byte)
Image header (40 byte)
Palette (2*4 = 8 byte)
Dati
contiene “firma” e informazioni sulle dimensioni del file
contiene informazioni sull’immagine
2 colori nel formato RGB (solo bianco e nero)
ogni byte rappresenta 8 pixel; l’immagine è in blocchi di 4 byte
Le informazioni sono codificate con numeri interi che occupano 1, 2 o 4 byte; storicamente questi tipi di dato
sono noti come BYTE, WORD e DWORD (DOUBLE WORD), quindi sono stati usati questi nomi per
semplificare il contenuto del sorgente. Tranne dove indicato, i dati sono DWORD (la maggior parte). I valori
sono quasi tutti fissi, tranne quelli legati alle dimensioni: si possono calcolare una volta note larghezza e
altezza dell’immagine, in pixel. La dimensione della palette, ovviamente, è diversa nei formati a colori.
File header





(W) Signature (firma, valore fisso = 19778, che corrisponde alle lettere BM);
Dimensione totale (62 + dimensione immagine in byte); classificato come “unreliable”;
(W) Dato Riservato (valore fisso = 0): in teoria dipende dal software grafico usato;
(W) Dato Riservato (valore fisso = 0): in teoria come sopra;
Offset dell’immagine (valore fisso = 62).
Il numero 62 è dato dalla somma delle lunghezze delle informazioni di servizio (14+40+8 byte).
La dimensione di 14 byte è data da 2+4+2+2+4.
Image header











Dimensione header (valore fisso = 40)
Larghezza (in pixel)
Altezza (in pixel)
(W) Piani di colore (valore fisso = 1)
(W) Profondità colori (1=B/W; 4=16 colori; 8=256 colori; 24=16M)
Compressione (0=nessuna compressione)
Dimensione immagine in byte; classificato come “unreliable”;
Bit per metro orizzontali (valore fisso = 0); classificato come “unreliable”
Bit per metro verticali (valore fisso = 0) ; classificato come “unreliable”
Numero colori (valore fisso = 0)
Numero colori “importanti” (valore fisso = 0)
La dimensione di 40 byte è data da 4+4+4+2+2+4+4+4+4+4+4.
Palette (monocromatica)








(B) Nero: valore del blu (default: 0)
(B) Nero: valore del verde (default: 0)
(B) Nero: valore del rosso (default: 0)
(B) Nero: valore riservato (default: 0)
(B) Bianco: valore del blu (default: 255)
(B) Bianco: valore del verde (default: 255)
(B) Bianco: valore del rosso (default: 255)
(B) Bianco: valore riservato (default: 0)
Con i raggi luminosi, i colori primari sono rosso,
verde, blu. Con la luce riflessa (come nei quadri),
i colori primari sono rosso, giallo, blu.
Il nero è dato dall’assenza dei colori primari
(valore 0), il bianco dalla somma di tutti, nel loro
valore massimo (255). I colori con valori uguali
dei 3 componenti formano la scala dei grigi.
Modificare i valori dei colori è sconsigliabile, non avendo un effetto uniforme: alcuni software riescono a
rispettare la nuova palette, altri no. Tra i possibili effetti malefici, ogni colore diverso dal bianco potrebbe
essere reso in nero; oppure, salvando l’immagine, la palette potrebbe tornare quella default.
Memorizzazione informazioni di servizio
È possibile registrare tutto il contenuto di una bitmap come char o string, ma non è molto consigliabile. Per
raggruppare i dati in un formato più leggibile si possono dichiarare formati composti detti struct
(strutture): una struttura dati non è normalmente formata da un dato singolo, ma da più dati associati ad essa.
Ciascun componente di una variabile strutturata viene indicato con il formato degli oggetti:
<struct>.<component>
Anche con questi accorgimenti, tuttavia, sarà necessario rispettare il formato delle istruzioni, che spesso
ricorda una gestione “old-style” dei flussi di dati come sequenze di caratteri.
Per semplificare il sorgente vengono specificate alcune abbreviazioni dei tipi di dato presenti nelle strutture.
typedef unsigned char BYTE;
typedef unsigned short int WORD;
typedef unsigned long int DWORD;
struct fileHeader
{
WORD type;
DWORD size;
WORD reserved1;
WORD reserved2;
DWORD offset;
};
struct imageHeader
{
DWORD size;
DWORD width;
DWORD height;
WORD planes;
WORD bitCount;
DWORD compression;
DWORD imageSize;
DWORD xPixelsPerMeter;
DWORD yPixelsPerMeter;
DWORD clrUsed;
DWORD clrImportant;
};
struct color
{
BYTE blue;
BYTE green;
BYTE red;
BYTE reserved;
};
//
//
//
BYTE: 1 byte unsigned
WORD: 2 bytes unsigned
DWORD: 4 bytes unsigned
Strutture dati
Dichiarazione di variabili composte:
fileHeader h1;
imageHeader h2;
color black, white;
In questo modo le variabili h1, h2, white, black risultano
composte da più dati, corrispondenti alla definizione di struct
Ad esempio, le componenti della variabile h1 saranno:
h1.type
h1.size
h1.reserved1
h1.reserved2
h1.offset
All’interno del programma è possibile associare alle variabili
composte i valori distinti dei singoli dati atomici, evitando di
costruire stringhe scarsamente significative e ancor meno
gestibili:
white.blue = 255;
white.green = 255;
white.red = 255;
white.reserved = 0;
Essendoci nel formato BMP un header di 14 byte, bisogna
allineare la scrittura alla sua esatta dimensione (e non a 16); per
farlo occorre aggiungere la seguente direttiva (che può essere
associata alle include all’interno del programma).
#pragma pack( push, 1)
I colori sono descritti con le componenti fondamentali nel formato RGB (red, green, blue). I valori associati ai
colori sono in ordine inverso perché è così che sono memorizzate le DWORD.
Come si può immaginare, per un bitmap monocromatico basteranno le descrizioni del bianco (255,255,255)
e del nero (0,0,0). Per un’immagine a colori servirebbe una palette con tutte le descrizioni dei colori, ma, per
ovvi motivi di praticità, per più di 256 colori non viene memorizzata. Con 256 colori le informazioni di
servizio occupano uno spazio ancora gestibile di 1078 byte (54+1024 per i colori), mentre per un’immagine a
16M il carico necessario diventerebbe improponibile.
Una volta dichiarate le strutture, se ne definisce una per tipo (tranne per i colori, che possono essere due),
oltre ad una variabile di flusso per direzionare il tutto nel file desiderato:
fileHeader h1;
imageHeader h2;
color black, white;
fstream flow;
I dati dei due header sono per lo più fissi; quelli variabili, come già visto, sono i seguenti:
h1.size
UNRELIABLE (valore = 62+h2.imageSize)
h2.width
h2.height
h2.imageSize
SIGNIFICATIVO
SIGNIFICATIVO
UNRELIABLE
Per definire le palette è opportuno seguire lo standard (nero=assenza di colori; bianco=tutti i colori):
black.blue = 0;
black.green = 0;
black.red = 0;
black.reserved = 0;
white.blue = 255;
white.green = 255;
white.red = 255;
white.reserved = 0;
Per scrivere l’intera struttura è consigliabile usare il metodo write , che ha il seguente formato:
flow.write( (char*)&<struct>, sizeof(<struct>));
Per registrare le informazioni di servizio si può procedere in questo modo:
flow.open( z, ios::out | ios::binary);
result = flow.good();
if ( result )
{
flow.write ((char*)&h1,
sizeof
flow.write ((char*)&h2,
sizeof
flow.write ((char*)&black, sizeof
flow.write ((char*)&white, sizeof
flow.close();
}
// z = nome del file
(fileHeader));
(imageHeader));
(color));
(color));
sizeof( fileHeader) 14
sizeof( imageHeader) 40
sizeof( color)
4
La variabile z è la stringa contenente il nome del file.
Il metodo .good() permette di verificare se un’operazione (in questo caso l’apertura) è andata a buon fine.
Tra le cause di possibili errori, ci potrebbe essere il disco pieno (piuttosto improbabile), oppure l’accesso
protetto alla cartella; se l’intero file system fosse danneggiato, probabilmente il programma non riuscirebbe
nemmeno a funzionare.
A questo punto qualche modulo si dovrà occupare della scrittura dei byte dati, aprendo nuovamente il file
con una nuova clausola (ios::app), che consente di aggiungere in coda (operazione nota come append):
flow.open( z, ios::out | ios::binary | ios::app);
//
append mode
Nel prossimo paragrafo potremo vedere un esempio di come raggruppare le informazioni e i moduli in una
libreria (un header file con estensione .h)
Una libreria per la gestione delle immagini
Le dichiarazioni e le funzioni necessarie alla registrazione possono essere inserite in una libreria, che
potrebbe essere scritta come riportato nel seguito.
#include <fstream>
#pragma pack(push, 1)
using namespace std;
//
byte-wise setting
typedef unsigned char BYTE;
typedef unsigned short int WORD;
typedef unsigned long int DWORD;
//
//
//
1 byte unsigned
2 bytes unsigned
4 bytes unsigned
/*
The two headers (file/image) and the palettes
*/
struct fileHeader
{
WORD type;
DWORD size;
Tutte le dichiarazioni dei dati e degli header
WORD reserved1;
WORD reserved2;
sono stati esaminati in precedenza.
DWORD offset;
};
struct imageHeader
{
DWORD size;
DWORD width;
DWORD height;
WORD planes;
WORD bitCount;
DWORD compression;
DWORD imageSize;
DWORD xPixelsPerMeter;
DWORD yPixelsPerMeter;
DWORD clrUsed;
DWORD clrImportant;
};
struct color
{
BYTE
blue;
//
The colors are stored in reverse order
BYTE
green;
BYTE
red;
BYTE
reserved;
};
/*
This function computes the number of bytes
required for each row of the bitmap
*/
int bytesPerRow( int width)
{
int b, row;
b = ((width+7)/8);
//
bytes minumum
row = 4 * ( (b-1)/4 + 1 );
//
actual bytes per row
return row;
}
Il modulo “bytesPerRow” calcola quanti byte sono
occupati da una riga di pixel; non dipende dall’altezza
dell’immagine. I calcoli sono già stati esaminati in
/*
Stores the bitmap header and the palette:
Dimensions in bytes:
file info
14
image info
40
palette
8 (4 black + 4 white)
*/
bool monoHeaders( const char* z, int width, int height)
{
fileHeader h1;
Il modulo ha questo nome per i seguenti motivi:
imageHeader h2;
 mono
monocromatico
color white, black;
int b, i, j, row, size;
 Headers il plurale indica le 4 strutture di
char bitmap, b1, b2;
bool result;
fstream x;
servizio
row = bytesPerRow( width);
size = row * height;
//
//
bytes per row
total bytes
h1.type = 19778;
h1.size = 62 + size;
h1.reserved1 = 0;
h1.reserved2 = 0;
h1.offset = 62;
//
//
must be 19778 (signature: BM)
---SIZE (unreliable)
//
header size: 14 + 40 + (4+4) (black/white)
h2.size = 40;
h2.width = width;
h2.height = height;
h2.planes = 1;
h2.bitCount = 1;
h2.compression = 0;
h2.imageSize = size;
h2.xPixelsPerMeter = 0;
h2.yPixelsPerMeter = 0;
h2.clrUsed = 0;
h2.clrImportant = 0;
//
//
//
//
//
header length (fixed)
---SIZE
---SIZE
must be 1 (color planes)
1 = monochrome
//
//
//
---SIZE
unreliable
unreliable
black.blue = 0;
black.green = 0;
black.red = 0;
black.reserved = 0;
white.blue = 255;
white.green = 255;
white.red = 255;
white.reserved = 0;
Il modulo “monoHeaders” si occupa della registrazione
del dati di servizio: l’unico dato veramente necessario è il
nome del file, mentre le dimensioni sono inserite per
completezza (l’immagine risulterebbe valida anche con
questi dati posti a zero).
x.open( z, ios::out | ios::binary);
result = x.good();
if ( result )
{
x.write ((char*)&h1,
x.write ((char*)&h2,
x.write ((char*)&black,
x.write ((char*)&white,
x.close();
}
return result;
}
sizeof
sizeof
sizeof
sizeof
(
(
(
(
fileHeader));
imageHeader));
color));
color));
//
//
//
//
header 1
header 2
black = 0
white = 1
Un’immagine casuale
Quello che segue è un esempio di creazione di un bitmap monocromatico affidato quasi completamente al
caso (la prima e l’ultima riga sono state previste come “tutto bianco” e “tutto nero”).
Va tenuto presente che le righe sono memorizzate dal basso all’alto.
bool randomImage( const char* z, int width, int height)
{
int b, i, j, row, size;
char b1, b2, bitmap;
bool result;
fstream x;
result = monoHeaders( z, width, height);
//
headers/palette
if ( result )
{
row = bytesPerRow( width);
x.open( z, ios::out | ios::binary | ios::app); //
append mode
for( i=1; i<=height; i++)
{
for( j=1; j<=row; j++)
{
if (i==1)
bitmap = 255;
//
1st row: white
else
if (i==height)
bitmap = 0;
//
last row: black
else
bitmap = rand() % 256;
//
other: random
x.write (&bitmap, 1);
}
}
x.close();
}
Il risultato del nostro modesto esempio è quasi
completamente casuale; potrebbe essere come
return result;
nell’immagine seguente (23x7):
}
int main()
{
bool result;
int width, height;
srand(time(0));
width = rand() % 30 + 1;
height = rand() % 10 + 1;
result = randomImage( "test.bmp", width, height);
cout << result;
getchar();
}
N.B.: Scrivere un programma C++ per la gestione di immagini, o anche per la sola lettura, è completamente
fuori luogo, sia per le difficoltà pratiche, sia per la presenza di decine di programmi adeguati.
Scarica

bitmap - Atletica Carpenedolo