Preparazione per il lavoro
Come sempre, prima apri una finestra di terminale ed esegui il comando.update50
per assicurarti che la tua applicazione sia già aggiornata. Prima di iniziare, segui questo cd ~ / workspace
wget http://cdn.cs50.net/2015/fall/psets/4/pset4/pset4.zip
per scaricare un archivio ZIP di questa attività. Ora se esegui ls vedrai che hai un file chiamato pset4.zip nella tua directory ~/workspace . Estrailo utilizzando il comando: Se esegui unzip pset4.zip
nuovamente il comando ls , vedrai che è apparsa un'altra directory. Ora puoi eliminare il file zip come mostrato di seguito: rm -f pset4.zip
Apriamo la directory pset4 cd pset4
, eseguiamo ls e assicuriamoci che la directory contenga bmp / jpg / questions.txt
whodunit o "Chi ha fatto questo?"
Se hai mai visto il desktop predefinito di Windows XP (https://en.wikipedia.org/wiki/Bliss_(image)) (colline e cielo blu), allora hai visto BMP. Nelle pagine web, molto probabilmente hai visto le GIF. Hai guardato le foto digitali? Quindi, abbiamo avuto la gioia di vedere JPEG. Se hai mai fatto uno screenshot su un Mac, molto probabilmente hai visto un PNG. Leggi su Internet i formati BMP, GIF, JPEG, PNG e rispondi a queste domande:-
Quanti colori supporta ciascun formato?
-
Quale formato supporta l'animazione?
-
Qual è la differenza tra compressione con perdita e senza perdita?
-
Quale di questi formati utilizza la compressione con perdita?
-
Cosa succede dal punto di vista tecnico quando un file viene eliminato in un file system FAT?
-
Cosa si può fare per garantire (con un'alta probabilità) che i file cancellati non possano essere recuperati?
xxd -c 24 -g 3 -s 54 smiley.bmp
Dovresti vedere quanto mostrato di seguito; Abbiamo nuovamente evidenziato in rosso tutte le istanze di 0000ff. Nell'immagine nella colonna più a sinistra puoi vedere gli indirizzi nel file, che equivalgono all'offset dal primo byte del file. Tutti sono indicati nel sistema numerico esadecimale. Se convertiamo l'esadecimale 00000036 in decimale, otteniamo 54. Quindi stai guardando il 54esimo byte di smiley.bmp . Ricordiamo che nei file BMP a 24 bit, i primi 14 + 40 = 54 byte sono pieni di metadati. Quindi, se vuoi vedere i metadati, esegui il seguente comando: xxd -c 24 -g 3 smiley.bmp
Se smiley.bmp contiene caratteri ASCII , li vedremo nella colonna più a destra in xxd invece di tutti quei punti. Quindi, lo smiley è un BMP a 24 bit (ogni pixel è rappresentato da 24 ÷ 8 = 3 byte) con una dimensione (risoluzione) di 8x8 pixel. Ogni riga (o "Scanline" come viene chiamata) occupa quindi (8 pixel) x (3 byte per pixel) = 24 byte. Questo numero è un multiplo di quattro e questo è importante perché il file BMP viene memorizzato in modo leggermente diverso se il numero di byte nella riga non è un multiplo di quattro. Quindi, in small.bmp, un altro file BMP a 24 bit nella nostra cartella, puoi vedere un riquadro verde di 3x3 pixel. Se lo apri in un visualizzatore di immagini, vedrai che assomiglia all'immagine mostrata sotto, solo di dimensioni più piccole. Ogni riga in small.bmp occupa quindi (3 pixel) × (3 byte per pixel) = 9 byte, che non è un multiplo di 4. Per ottenere una lunghezza di riga che sia multiplo di 4, viene riempita con zeri aggiuntivi: tra 0 e 3 byte riempiamo ogni riga in formato BMP a 24 bit (puoi indovinare perché?). Per small.bmp , sono necessari 3 byte di zeri, poiché (3 pixel) x (3 byte per pixel) + (3 byte di riempimento) = 12 byte, che in realtà è un multiplo di 4. Per "vedere" questo riempimento, Fai quanto segue. xxd -c 12 -g 3 -s 54 small.bmp
Tieni presente che utilizziamo un valore diverso per -c rispetto a smiley.bmp , quindi xxd questa volta restituisce solo 4 colonne (3 per il quadrato verde e 1 per il riempimento). Per chiarezza, abbiamo evidenziato tutte le istanze di 00ff00 in verde. Per contrasto, utilizziamo xxd per il file large.bmp . Sembra esattamente uguale a small.bmp, solo la sua risoluzione è 12x12 pixel, cioè quattro volte più grande. Esegui il comando seguente. Potrebbe essere necessario espandere la finestra per evitare il trasferimento. xxd -c 36 -g 3 -s 54 large.bmp
Vedrai qualcosa del genere: Nota: non ci sono digressioni in questo BMP! Dopotutto, (12 pixel) × (3 byte per pixel) = 36 byte, e questo è un multiplo di 4. L'editor esadecimale xxd ci ha mostrato i byte nei nostri file BMP. Come possiamo ottenerli a livello di programmazione? In copy.c c'è un programma il cui unico scopo nella vita è creare una copia del BMP, pezzo per pezzo. Sì, puoi usare cp per questo . Tuttavia, CP non sarà in grado di aiutare il signor Boddy. Speriamo che copy.c faccia questo, quindi eccoci qui: ./copy smiley.bmp copy.bmp
se ora esegui ls (con il flag appropriato), vedrai che smiley.bmp e copy.bmp hanno effettivamente la stessa dimensione. Controlliamo ancora se questo è effettivamente vero? diff smiley.bmp copy.bmp
Se questo comando non visualizza nulla sullo schermo, significa che i file sono effettivamente identici (importante: alcuni programmi, come Photoshop, includono zeri finali alla fine di alcuni VMP. La nostra versione di copia li scarta, quindi non farlo preoccupatevi se, copiando altri BMP che avete scaricato o creato per testare, la copia sarà di qualche byte più piccola dell'originale). È possibile aprire entrambi i file nel visualizzatore di immagini di Ristretto (fare doppio clic) per confermarlo visivamente. Ma diff fa questo confronto byte per byte, quindi la sua visione è più nitida della tua! Come è stata creata questa copia? Si scopre che copy.c è correlato a bmp.h . Assicuriamoci: apri bmp.h. Lì vedrai le definizioni effettive delle intestazioni che abbiamo già menzionato, adattate dalle implementazioni di Microsoft. Inoltre, questo file definisce i tipi di dati BYTE, DWORD, LONG e WORD, che sono i tipi di dati tipicamente presenti nel mondo della programmazione Win32 (ovvero Windows). Nota che questi sono essenzialmente alias per primitive con cui (si spera) hai già familiarità. Risulta che BITMAPFILEHEADER e BITMAPINFOHEADER utilizzavano questi tipi. Questo file definisce anche una struttura chiamata RGBTRIPLE. Esso “incapsula” tre byte: uno blu, uno verde e uno rosso (questo è l'ordine in cui cercheremo le triplette RGB sul disco). In che modo sono utili queste strutture? Per ricapitolare, un file è semplicemente una sequenza di byte (o, in ultima analisi, bit) sul disco. Tuttavia, questi byte vengono generalmente ordinati in modo che i primi rappresentino qualcosa, i successivi rappresentino qualcos'altro e così via. I "formati" dei file esistono perché abbiamo standard, o regole, che definiscono cosa significano i byte. Ora possiamo semplicemente leggere il file dal disco alla RAM come un grande array di byte. E ricordiamo che il byte in posizione [i] rappresenta una cosa, mentre il byte in posizione [j] è un'altra cosa. Ma perché non dare un nome ad alcuni di questi byte in modo da poterli recuperare più facilmente dalla memoria? Questo è esattamente ciò in cui ci aiutano le strutture in bmp.h. Invece di pensare a un file come a una lunga sequenza di byte, lo vediamo suddiviso in blocchi più comprensibili: sequenze di strutture. Ricordiamo che smiley.bmp ha una risoluzione di 8x8 pixel, quindi occupa 14 + 40 + (8 × 8) × 3 = 246 byte su disco (puoi verificarlo utilizzando il comando ls). Ecco come appare sul disco secondo Microsoft: Possiamo vedere che l'ordine è importante quando si tratta di membri di strutture. Il byte 57 è rgbtBlue (non, ad esempio, rgbtRed) perché rgbtBlue è definito prima in RGBTRIPLE. A proposito, il nostro utilizzo dell'attributo imballato garantisce che clang non tenti di "allineare a parola" i membri (con l'indirizzo del primo byte di ciascun membro che è un multiplo di 4), in modo da non ritrovarci con buchi le nostre strutture che non esistono affatto sul disco. Andiamo avanti. Trova gli URL che corrispondono a BITMAPFILEHEADER e BITMAPINFOHEADER, come indicato nei commenti in bmp.h. Attenzione, ottimo momento: stai iniziando a utilizzare MSDN (Microsoft Developer Network)! Invece di scorrere ulteriormente copy.c , rispondi ad alcune domande per capire come funziona il codice. Come sempre, il comando man è il tuo vero amico, e ora anche MSDN. Se non conosci le risposte, cerca su Google e pensaci. È anche possibile fare riferimento al file stdio.h su https://reference.cs50.net/.
-
Imposta un punto di interruzione nella riga principale (facendo clic a sinistra del righello con i numeri della riga principale).
-
In una scheda del terminale , vai su ~/workspace/pset4/bmp e compila copy.c nel programma di copia utilizzando make.
-
Esegui debug50 copy smiley.bmp copy.bmp , questo aprirà il pannello del debugger sulla destra.
-
Segui il programma passo dopo passo utilizzando il pannello a destra. Nota bf e bi . In ~/workspace/pset4/questions.txt , rispondi alle domande:
-
Cos'è stdint.h ?
-
Qual è lo scopo di utilizzare uint8_t , uint32_t , int32_t e uint16_t in un programma?
-
Quanti byte contengono rispettivamente BYTE , DWORD , LONG e WORD (presupponendo un'architettura a 32 bit)?
- Quali (ASCII, decimale o esadecimale) dovrebbero essere i primi due byte di un file BMP? (i byte iniziali, utilizzati per identificare il formato del file (con alta probabilità) sono spesso chiamati "numeri magici").
-
Qual è la differenza tra bfSize e biSize?
-
Cosa significa una biHeight negativa?
-
Quale campo in BITMAPINFOHEADER definisce la profondità del colore in BMP (ovvero bit per pixel)?
-
Perché la funzione fopen può restituire NULL in copy.c 37?
-
Perché il terzo argomento di fread nel nostro codice è uguale a 1?
-
Quale valore in copy.c 70 definisce il riempimento se bi.biWidth è 3?
-
Cosa fa fseek?
-
Cos'è SEEK_CUR?
Torniamo al signor Boddy. Esercizio:
Scrivi un programma chiamato whodunit in un file chiamato whodunit.c che mostri il disegno di Mr. Boddy. Hmmmm, cosa? Come per la copia, il programma deve accettare esattamente due argomenti della riga di comando e, se esegui il programma come mostrato di seguito, il risultato verrà memorizzato in verdict.bmp, in cui il disegno di Mr. Boddy non sarà rumoroso../whodunit clue.bmp verdict.b
Ti suggeriamo di iniziare a risolvere questo mistero eseguendo il comando seguente. cp copy.c whodunit.c
Potresti rimanere stupito da quante righe di codice devi scrivere per aiutare il signor Boddy. Non c'è nulla di inutile nascosto in smiley.bmp , quindi sentiti libero di testare il programma su questo file. È piccolo e puoi confrontare l'output del tuo programma e l'output di xxd durante lo sviluppo (o forse c'è qualcosa nascosto in smiley.bmp ? In realtà no). A proposito, questo problema può essere risolto in diversi modi. Una volta identificato il disegno del signor Boddy, riposerà in pace. Poiché whodunit può essere implementato in più modi, non sarai in grado di verificare la correttezza delle implementazioni con check50 . E lascia che ti rovini il divertimento, ma anche per il problema del giallo non è disponibile la soluzione degli assistenti . Infine, nel file In ~/workspace/pset4/questions.txt , rispondi alla seguente domanda: Whodunit? //ктоэтосделал?
ridimensionare
Bene, ora - il prossimo test! Scriviamo un programma chiamato resize in resize.c . Ridimensionerà un'immagine BMP a 24 bit non compressa in passaggi di n. L'applicazione deve accettare esattamente tre argomenti della riga di comando, di cui il primo (n) è un numero intero non maggiore di 100, il secondo è il nome del file che verrà modificato e il terzo è il nome della versione salvata del file modificato. file.Usage: ./resize n infile outfile
Con un programma del genere, potremmo creare large.bmp da small.bmp ridimensionando quest'ultimo di 4 (ovvero moltiplicando sia la larghezza che l'altezza per 4), come mostrato di seguito. ./resize 4 small.bmp large.bmp
Per semplicità, puoi avviare l'attività copiando nuovamente copy.c e nominando la copia resize.c . Ma prima chiediti e rispondi a queste domande: cosa significa modificare la dimensione BMP (puoi supporre che n volte la dimensione del file non superi 232 - 1) . Determina quali campi in BITMAPFILEHEADER e BITMAPINFOHEADER devi modificare. Valuta se è necessario aggiungere o rimuovere i campi delle linee di scansione . E sì, sii grato che non ti stiamo chiedendo di considerare tutti i possibili valori di n da 0 a 1! (anche se, se sei interessato, questo è un problema tratto dal libro di un hacker ;)). Tuttavia, assumiamo che per n = 1 il programma funzionerà correttamente e il file di output outfile avrà le stesse dimensioni dell'infile originale. Vuoi controllare il programma utilizzando check50? Digita il seguente comando: ~cs50/pset4/resize
Bene, se vuoi vedere, ad esempio, le intestazioni
large.bmp (in una forma più user-friendly rispetto a quella consentita da xxd), devi eseguire il seguente comando:
~cs50/pset4/peek large.bmp
Ancora meglio, se vuoi confrontare i tuoi headers con le intestazioni dei file dell'assistente CS50. Puoi eseguire comandi nella directory
~/workspace/pset4/bmp (pensa a cosa fa ciascun comando). Se hai utilizzato
malloc , assicurati di utilizzare
free per evitare perdite di memoria. Prova a utilizzare
valgrind per verificare la presenza di perdite.
./resize 4 small.bmp student.bmp
~cs50/pset4/resize 4 small.bmp staff.bmp
~cs50/pset4/peek student.bmp staff.bmp
Come decidere?
-
Apriamo il file che dobbiamo ingrandire e creiamo e apriamo anche un nuovo file in cui verrà registrata l'immagine ingrandita;
-
aggiornare le informazioni sull'intestazione per il file di output. Dato che la nostra immagine è in formato BMP e ne stiamo modificando le dimensioni, dobbiamo scrivere l'intestazione del nuovo file con le nuove dimensioni. Cosa cambierà? La dimensione del file, così come la dimensione dell'immagine: larghezza e altezza.
-
Leggiamo il file in uscita, riga per riga, pixel per pixel. Per fare ciò, ci rivolgiamo nuovamente alla nostra libreria I/O di file e alla funzione fread. Ci vuole un puntatore ad una struttura che conterrà i byte letti, la dimensione del singolo elemento che andremo a leggere, il numero di tali elementi, ed un puntatore al file da cui leggeremo.
-
Aumentiamo ogni riga orizzontalmente in base alla scala specificata e scriviamo il risultato nel file di output.
Come scriviamo i file? Abbiamo una funzione fwrite, alla quale passiamo un indicatore alla struttura in cui si trovano i dati da scrivere nel file, la dimensione degli elementi, il loro numero e un puntatore al file di output. Per organizzare il ciclo possiamo utilizzare il ciclo for che già conosciamo .
-
Riempi gli spazi vuoti! Se il numero di pixel in una riga non è multiplo di quattro, dobbiamo aggiungere "allineamento" - zero byte. Avremo bisogno di una formula per calcolare la dimensione dell'allineamento. Per scrivere byte nulli su un file di output, puoi utilizzare la funzione fputc, passandole il carattere che vuoi scrivere e un puntatore al file di output.
Ora che abbiamo allungato la corda orizzontalmente e aggiunto un allineamento al file di output, dobbiamo spostare la posizione corrente nel file di output perché dobbiamo saltare oltre l'allineamento.
-
Aumenta la dimensione verticale. È più complicato, ma possiamo usare il codice di esempio da copy.c (copy.c apre il file di output, scrive un'intestazione nel file di output, legge l'immagine dal file sorgente riga per riga, pixel per pixel, e la scrive al file di output). Sulla base di ciò, la prima cosa che puoi fare è eseguire il seguente comando: cp copy.c resize.c
Allungare verticalmente un'immagine significa copiare ogni riga più volte. Esistono diversi modi per farlo. Ad esempio, utilizzando la riscrittura, quando salviamo tutti i pixel di una riga in memoria e scriviamo questa riga nel file di output in un ciclo tutte le volte necessarie. Un altro metodo è la ricopia: dopo aver letto una riga dal file in uscita, averla scritta nel file di output e averla allineata, riportare la funzione fseek all'inizio della riga nel file in uscita e ripetere tutto più volte.
-
Apri il file con il contenuto della memory card.
-
Trova l'inizio del file JPEG. Tutti i file su questa scheda sono immagini in formato JPEG.
recuperare
In previsione del documento problematico della settimana 4, ho passato gli ultimi giorni a guardare le foto salvate in formato JPEG dalla mia fotocamera digitale su una scheda di memoria CompactFlash (CF) da 1 GB. Per favore, non dirmi che in realtà ho passato gli ultimi giorni invece su Facebook. Purtroppo le mie competenze informatiche lasciano molto a desiderare e, senza saperlo, ho cancellato per sbaglio tutte le foto! Fortunatamente, nel mondo dei computer, "eliminato" di solito non equivale a "ucciso". Il mio computer insiste sul fatto che la scheda di memoria ora è vuota, ma so che sta mentendo. Compito: scrivere un programma in ~/workspace/pset4/jpg/recover.c che recupererà queste foto. Hmm. Ok, ecco un altro chiarimento. Sebbene il formato JPEG sia più complesso del BMP, il formato JPEG è dotato di "firme", ovvero schemi di byte che aiutano a distinguerlo da altri formati di file. La maggior parte dei file JPEG iniziano con i seguenti tre byte:0xff 0xd8 0xff
dal primo byte al terzo, da sinistra a destra. Il quarto byte sarà molto probabilmente una delle seguenti combinazioni: 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,0xe8, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef. In altre parole, i primi quattro bit del quarto byte di un file JPEG sono 1110. È probabile che, se trovi uno di questi modelli sull'unità in cui sono state archiviate le foto (come la mia scheda di memoria), questo sarà l'inizio del file JPEG. Naturalmente, potresti riscontrare questo su quale disco per puro caso; il recupero dei dati non può essere definito una scienza esatta.
GO TO FULL VERSION