Przygotowanie do pracy
Jak zawsze najpierw otwórz okno terminala i uruchom polecenie.update50
aby upewnić się, że Twoja aplikacja jest już aktualna. Zanim zaczniesz, wykonaj poniższe czynności, cd ~ / workspace
wget http://cdn.cs50.net/2015/fall/psets/4/pset4/pset4.zip
aby pobrać archiwum ZIP tego zadania. Teraz, jeśli uruchomisz ls, zobaczysz, że masz plik o nazwie pset4.zip w swoim katalogu ~/workspace . Wyodrębnij go za pomocą polecenia: Jeśli unzip pset4.zip
ponownie uruchomisz polecenie ls , zobaczysz, że pojawił się inny katalog. Teraz możesz usunąć plik zip, jak pokazano poniżej: rm -f pset4.zip
Otwórzmy katalog pset4 cd pset4
, uruchom ls i upewnij się, że katalog zawiera bmp / jpg / questions.txt
whodunit lub „Kto to zrobił?”
Jeśli kiedykolwiek widziałeś domyślny pulpit systemu Windows XP (https://en.wikipedia.org/wiki/Bliss_(image)) (wzgórza i błękitne niebo), to widziałeś BMP. Na stronach internetowych najprawdopodobniej widziałeś pliki GIF. Czy oglądałeś zdjęcia cyfrowe? Mieliśmy więc radość zobaczyć JPEG. Jeśli kiedykolwiek zrobiłeś zrzut ekranu na komputerze Mac, najprawdopodobniej widziałeś plik PNG. Przeczytaj w Internecie o formatach BMP, GIF, JPEG, PNG i odpowiedz na następujące pytania:-
Ile kolorów obsługuje każdy format?
-
Który format obsługuje animację?
-
Jaka jest różnica między kompresją stratną i bezstratną?
-
Który z tych formatów wykorzystuje kompresję stratną?
-
Co się dzieje z technicznego punktu widzenia, gdy plik zostanie usunięty w systemie plików FAT?
-
Co można zrobić, aby mieć pewność (z dużym prawdopodobieństwem), że usuniętych plików nie będzie można odzyskać?
xxd -c 24 -g 3 -s 54 smiley.bmp
Powinieneś zobaczyć to, co pokazano poniżej; Ponownie podkreśliliśmy na czerwono wszystkie wystąpienia 0000ff. Na obrazku w lewej kolumnie widać adresy w pliku, które odpowiadają przesunięciu od pierwszego bajtu pliku. Wszystkie podane są w systemie szesnastkowym. Jeśli przekonwertujemy liczbę szesnastkową 00000036 na dziesiętną, otrzymamy 54. Zatem patrzysz na 54. bajt z pliku Smiley.bmp . Przypomnijmy, że w 24-bitowych plikach BMP pierwsze 14 + 40 = 54 bajty są wypełnione metadanymi. Jeśli więc chcesz zobaczyć metadane, uruchom następujące polecenie: xxd -c 24 -g 3 smiley.bmp
Jeśli plik Smiley.bmp zawiera znaki ASCII , zobaczymy je w skrajnej prawej kolumnie w xxd zamiast tych wszystkich kropek. Zatem buźka jest 24-bitowym BMP (każdy piksel jest reprezentowany przez 24 ÷ 8 = 3 bajty) o rozmiarze (rozdzielczości) 8x8 pikseli. Każda linia (lub, jak się ją nazywa, „Scanline”) zajmuje (8 pikseli) x (3 bajty na piksel) = 24 bajty. Liczba ta jest wielokrotnością czterech i jest to ważne, ponieważ plik BMP jest przechowywany nieco inaczej, jeśli liczba bajtów w linii nie jest wielokrotnością czterech. Zatem w small.bmp, kolejnym 24-bitowym pliku BMP w naszym folderze, widać zielone pole o wymiarach 3x3 piksele. Jeśli otworzysz go w przeglądarce obrazów, zobaczysz, że przypomina obraz pokazany poniżej, tylko jest mniejszy. Każda linia w small.bmp zajmuje zatem (3 piksele) × (3 bajty na piksel) = 9 bajtów, co nie jest wielokrotnością 4. Aby uzyskać długość linii będącą wielokrotnością 4, jest ona dopełniana dodatkowymi zerami: pomiędzy 0 a 3 bajtami wypełniamy każdą linię w 24-bitowym formacie BMP (czy domyślasz się dlaczego?). W przypadku small.bmp potrzebne są 3 bajty zer, ponieważ (3 piksele) x (3 bajty na piksel) + (3 bajty dopełnienia) = 12 bajtów, co w rzeczywistości jest wielokrotnością 4. Aby „zobaczyć” to dopełnienie, wykonaj następujące czynności. xxd -c 12 -g 3 -s 54 small.bmp
Zauważ, że używamy innej wartości dla -c niż dla Smiley.bmp , więc xxd wyświetla tym razem tylko 4 kolumny (3 dla zielonego kwadratu i 1 dla wypełnienia). Dla przejrzystości podświetliliśmy wszystkie wystąpienia 00ff00 na zielono. Dla kontrastu, użyjmy xxd dla dużego pliku.bmp . Wygląda dokładnie tak samo jak small.bmp, tylko jego rozdzielczość wynosi 12x12 pikseli, czyli czterokrotnie większa. Uruchom poniższe polecenie. Może być konieczne rozwinięcie okna, aby uniknąć przeniesienia. xxd -c 36 -g 3 -s 54 large.bmp
Zobaczysz coś takiego: Uwaga, w tym BMP nie ma żadnych dygresji! W końcu (12 pikseli) × (3 bajty na piksel) = 36 bajtów, a to jest wielokrotność 4. Edytor szesnastkowy xxd pokazał nam bajty w naszych plikach BMP. Jak możemy je uzyskać programowo? W copy.c istnieje jeden program, którego jedynym celem w życiu jest utworzenie kopii BMP, kawałek po kawałku. Tak, możesz do tego użyć cp . Jednak cp nie będzie w stanie pomóc panu Boddy. Miejmy nadzieję, że copy.c to zrobi, więc zaczynamy: ./copy smiley.bmp copy.bmp
jeśli teraz uruchomisz ls (z odpowiednią flagą), zobaczysz, że pliki Smiley.bmp i copy.bmp rzeczywiście mają ten sam rozmiar. Sprawdźmy jeszcze raz, czy to rzeczywiście prawda? diff smiley.bmp copy.bmp
Jeśli to polecenie nie wyświetla niczego na ekranie, oznacza to, że pliki rzeczywiście są identyczne (ważne: niektóre programy, np. Photoshop, dołączają zera na końcach niektórych VMP. Nasza wersja kopii je odrzuca, więc nie rób tego) martw się, jeśli w przypadku kopiowania innych plików BMP, które pobrałeś lub utworzyłeś do testów, kopia będzie o kilka bajtów mniejsza niż oryginał). Możesz otworzyć oba pliki w przeglądarce obrazów Ristretto (kliknij dwukrotnie), aby potwierdzić to wizualnie. Ale diff porównuje bajt po bajcie, więc jej wzrok jest ostrzejszy niż twój! Jak powstała ta kopia? Okazuje się, że copy.c jest powiązany z bmp.h. Upewnijmy się: otwórz bmp.h. Zobaczysz tam rzeczywiste definicje nagłówków, o których już wspominaliśmy, zaadaptowane z własnych implementacji Microsoftu. Dodatkowo plik ten definiuje typy danych BYTE, DWORD, LONG i WORD, które są typami danych zwykle spotykanymi w świecie programowania Win32 (tj. Windows). Zauważ, że są to zasadniczo aliasy dla prymitywów, które (miejmy nadzieję) już znasz. Okazuje się, że BITMAPFILEHEADER i BITMAPINFOHEADER używały tych typów. Ten plik definiuje również strukturę o nazwie RGBTRIPLE. „Hermetyzuje” trzy bajty: jeden niebieski, jeden zielony i jeden czerwony (w tej kolejności będziemy szukać trójek RGB na dysku). W jaki sposób te struktury są przydatne? Podsumowując, plik to po prostu sekwencja bajtów (lub ostatecznie bitów) na dysku. Jednak te bajty są zazwyczaj uporządkowane w taki sposób, że kilka pierwszych reprezentuje coś, następnie kilka następnych reprezentuje coś innego i tak dalej. „Formaty” plików istnieją, ponieważ mamy standardy lub reguły definiujące, co oznaczają bajty. Teraz możemy po prostu wczytać plik z dysku do pamięci RAM jako jedną dużą tablicę bajtów. I pamiętamy, że bajt na pozycji [i] reprezentuje jedno, natomiast bajt na pozycji [j] oznacza coś innego. Ale dlaczego nie nadać niektórym z tych bajtów nazw, abyśmy mogli łatwiej odzyskać je z pamięci? Właśnie w tym pomagają nam struktury w bmp.h. Zamiast myśleć o pliku jako o jednej długiej sekwencji bajtów, widzimy go podzielonego na bardziej zrozumiałe bloki – sekwencje struktur. Przypomnijmy, że plik Smiley.bmp ma rozdzielczość 8x8 pikseli, więc zajmuje na dysku 14 + 40 + (8 × 8) × 3 = 246 bajtów (możesz to sprawdzić za pomocą polecenia ls). Według Microsoftu tak to wygląda na dysku: Widzimy, że kolejność ma znaczenie, jeśli chodzi o elementy struktur. Bajt 57 to rgbtBlue (nie, powiedzmy, rgbtRed), ponieważ rgbtBlue jest najpierw zdefiniowany w RGBTRIPLE. Nawiasem mówiąc, użycie przez nas atrybutu spakowanego zapewnia, że clang nie będzie próbował „wyrównać słów” elementów (adres pierwszego bajtu każdego elementu będzie wielokrotnością 4), dzięki czemu nie skończy się to dziurami w naszych struktur, które w ogóle nie istnieją na dysku. Przejdźmy dalej. Znajdź adresy URL pasujące do BITMAPFILEHEADER i BITMAPINFOHEADER, zgodnie z komentarzami w bmp.h. Uwaga, świetny moment: zaczynasz korzystać z MSDN (Microsoft Developer Network)! Zamiast przewijać dalej copy.c , odpowiedz na kilka pytań, aby zrozumieć, jak działa kod. Jak zawsze, twoim prawdziwym przyjacielem jest polecenie man, a teraz także MSDN. Jeśli nie znasz odpowiedzi, wpisz w Google i pomyśl. Możesz także zapoznać się z plikiem stdio.h pod adresem https://reference.cs50.net/.
-
Ustaw punkt przerwania w głównym (klikając po lewej stronie linijki z numerami linii głównych).
-
Na karcie terminala przejdź do ~/workspace/pset4/bmp i skompiluj plik copy.c do programu kopiującego za pomocą make.
-
Uruchom debug50 copy Smiley.bmp copy.bmp , otworzy to panel debugera po prawej stronie.
-
Przejdź przez program krok po kroku korzystając z panelu po prawej stronie. Uwaga bf i bi . W pliku ~/workspace/pset4/questions.txt odpowiedz na pytania:
-
Co to jest stdint.h ?
-
Jaki jest sens używania uint8_t , uint32_t , int32_t i uint16_t w programie?
-
Ile bajtów zawierają odpowiednio BYTE , DWORD , LONG i WORD (zakładając architekturę 32-bitową)?
- Jakie powinny być pierwsze dwa bajty pliku BMP (ASCII, dziesiętny czy szesnastkowy)? (bajty początkowe, które służą do identyfikacji formatu pliku (z dużym prawdopodobieństwem) nazywane są często „liczbami magicznymi”).
-
Jaka jest różnica między bfSize i biSize?
-
Co oznacza ujemna wartość biHeight?
-
Które pole w BITMAPINFOHEADER definiuje głębię kolorów w BMP (tj. bity na piksel)?
-
Dlaczego funkcja fopen może zwrócić NULL w copy.c 37?
-
Dlaczego trzeci argument fread w naszym kodzie jest równy 1?
-
Jaka wartość w copy.c 70 definiuje dopełnienie, jeśli bi.biWidth wynosi 3?
-
Co robi fseek?
-
Co to jest SEEK_CUR?
Wracając do pana Boddy'ego. Ćwiczenia:
Napisz program o nazwie whodunit w pliku o nazwie whodunit.c , który będzie wyświetlał rysunek pana Boddy'ego. Hmmmm, co? Podobnie jak kopia, program musi przyjmować dokładnie dwa argumenty wiersza poleceń i jeśli uruchomisz program w sposób pokazany poniżej, wynik zostanie zapisany w pliku verdict.bmp, w którym rysunek pana Boddy'ego nie będzie zaszumiony../whodunit clue.bmp verdict.b
Sugerujemy, abyś zaczął rozwiązywać tę zagadkę, uruchamiając poniższe polecenie. cp copy.c whodunit.c
Możesz być zaskoczony, ile linii kodu musisz napisać, aby pomóc panu Boddy. W Smiley.bmp nie ma nic niepotrzebnego , więc możesz przetestować program na tym pliku. Jest mały i możesz porównać wyniki swojego programu z wynikami xxd podczas programowania (a może jest coś ukrytego w pliku Smiley.bmp ? Właściwie nie). Nawiasem mówiąc, problem ten można rozwiązać na różne sposoby. Kiedy już zidentyfikujesz rysunek pana Boddy'ego, będzie on spokojny. Ponieważ whodunit można zaimplementować na wiele sposobów, nie będziesz w stanie sprawdzić poprawności implementacji za pomocą check50 . I niech to zepsuje ci zabawę, ale rozwiązanie asystentów również nie jest dostępne w przypadku problemu kryminału . Na koniec w pliku In ~/workspace/pset4/questions.txt odpowiedz na następujące pytanie: Whodunit? //ктоэтосделал?
Zmień rozmiar
Cóż, teraz - następny test! Napiszmy program o nazwie resize w pliku resize.c . Zmieni rozmiar nieskompresowanego 24-bitowego obrazu BMP w krokach co n. Twoja aplikacja musi akceptować dokładnie trzy argumenty wiersza poleceń, przy czym pierwszy (n) jest liczbą całkowitą nie większą niż 100, drugi to nazwa pliku, który będzie modyfikowany, a trzeci to nazwa zapisanej wersji zmodyfikowanego plik.Usage: ./resize n infile outfile
Za pomocą takiego programu moglibyśmy utworzyć duży.bmp z małego.bmp, zmieniając jego rozmiar o 4 (tj. mnożąc szerokość i wysokość przez 4), jak pokazano poniżej. ./resize 4 small.bmp large.bmp
Dla uproszczenia możesz rozpocząć zadanie, kopiując ponownie plik copy.c i nadając mu nazwę resize.c . Ale najpierw zadaj sobie pytanie i odpowiedz na te pytania: co oznacza zmiana rozmiaru BMP (możesz założyć, że n-krotność rozmiaru pliku nie przekroczy 232 - 1) . Określ, które pola w BITMAPFILEHEADER i BITMAPINFOHEADER należy zmienić. Zastanów się, czy chcesz dodać, czy usunąć pola linii skanowania . I tak, bądź wdzięczny, że nie prosimy Cię o rozważenie wszystkich możliwych wartości n od 0 do 1! (chociaż, jeśli jesteś zainteresowany, jest to problem z książki hakerskiej ;)). Zakładamy jednak, że dla n = 1 program będzie działał poprawnie, a plik wyjściowy będzie miał taki sam rozmiar jak oryginalny plik wejściowy. Chcesz sprawdzić program za pomocą check50? Wpisz następujące polecenie: ~cs50/pset4/resize
Cóż, jeśli chcesz zobaczyć na przykład nagłówki
Large.bmp (w bardziej przyjaznej dla użytkownika formie niż pozwala na to xxd), musisz uruchomić następujące polecenie:
~cs50/pset4/peek large.bmp
Jeszcze lepiej, jeśli chcesz porównać swoje nagłówki z nagłówkami plików asystenta CS50. Możesz uruchamiać polecenia w katalogu
~/workspace/pset4/bmp (zastanów się, co robi każde polecenie). Jeśli użyłeś
malloc , pamiętaj, aby użyć
darmowego , aby zapobiec wyciekom pamięci. Spróbuj użyć
valgrind , aby sprawdzić, czy nie ma wycieków.
./resize 4 small.bmp student.bmp
~cs50/pset4/resize 4 small.bmp staff.bmp
~cs50/pset4/peek student.bmp staff.bmp
Jak zdecydować?
-
Otwórz plik, który musimy powiększyć, a także utwórz i otwórz nowy plik, w którym zostanie zapisany powiększony obraz;
-
zaktualizować informacje nagłówka pliku wyjściowego. Ponieważ nasz obraz jest w formacie BMP i zmieniamy jego rozmiar, musimy zapisać nagłówek nowego pliku z nowymi wymiarami. Co się zmieni? Rozmiar pliku, a także rozmiar obrazu - jego szerokość i wysokość.
-
Czytamy wychodzący plik linia po linii, piksel po pikselu. Aby to zrobić, ponownie skorzystamy z naszej biblioteki plików I/O i funkcji fread. Pobiera wskaźnik do struktury, która będzie zawierać odczytane bajty, rozmiar pojedynczego elementu, który będziemy czytać, liczbę takich elementów oraz wskaźnik do pliku, z którego będziemy czytać.
-
Zwiększamy każdą linię poziomo zgodnie z określoną skalą i zapisujemy wynik do pliku wyjściowego.
Jak piszemy pliki? Mamy funkcję fwrite, do której przekazujemy wskaźnik do struktury, w której znajdują się dane do zapisania do pliku, wielkość elementu, ich liczbę oraz wskaźnik do pliku wyjściowego. Do uporządkowania pętli możemy użyć znanej nam już pętli for .
-
Wypełnić luki! Jeśli liczba pikseli w linii nie jest wielokrotnością czterech, musimy dodać „wyrównanie” - zero bajtów. Będziemy potrzebować wzoru do obliczenia rozmiaru wyrównania. Aby zapisać bajty zerowe w pliku wyjściowym, możesz użyć funkcji fputc, przekazując jej znak, który chcesz zapisać, oraz wskaźnik do pliku wyjściowego.
Teraz, gdy rozciągnęliśmy ciąg w poziomie i dodaliśmy wyrównanie do pliku wyjściowego, musimy przesunąć bieżącą pozycję w pliku wyjściowym, ponieważ musimy przeskoczyć wyrównanie.
-
Zwiększ rozmiar w pionie. To bardziej skomplikowane, ale możemy użyć przykładowego kodu z copy.c (copy.c otwiera plik wyjściowy, zapisuje nagłówek do pliku wyjściowego, odczytuje obraz z pliku źródłowego linia po linii, piksel po pikselu i zapisuje je do pliku wyjściowego). Na tej podstawie pierwszą rzeczą, którą możesz zrobić, to uruchomić następujące polecenie: cp copy.c resize.c
Rozciągnięcie obrazu w pionie oznacza kilkukrotne skopiowanie każdej linii. Można to zrobić na kilka różnych sposobów. Na przykład przy użyciu przepisywania, gdy zapisujemy wszystkie piksele jednej linii w pamięci i zapisujemy tę linię do pliku wyjściowego w pętli tyle razy, ile potrzeba. Inną metodą jest ponowne kopiowanie: po wczytaniu linii z pliku wychodzącego, zapisaniu jej do pliku wyjściowego i wyrównaniu, funkcja fseek wraca na początek linii w pliku wychodzącym i powtarza wszystko jeszcze kilka razy.
-
Otwórz plik z zawartością karty pamięci.
-
Znajdź początek pliku JPEG. Wszystkie pliki na tej karcie są obrazami w formacie JPEG.
odzyskiwać
W oczekiwaniu na publikację problemową z tygodnia 4. ostatnie kilka dni spędziłem na przeglądaniu zdjęć zapisanych moim aparatem cyfrowym w formacie JPEG na karcie pamięci CompactFlash (CF) o pojemności 1 GB. Proszę, nie mów mi, że zamiast tego spędziłem ostatnie kilka dni na Facebooku. Niestety moje umiejętności obsługi komputera pozostawiają wiele do życzenia i nieświadomie przez przypadek usunęłam wszystkie zdjęcia! Na szczęście w świecie komputerów „usunięty” zwykle nie oznacza „zabity”. Mój komputer twierdzi, że karta pamięci jest teraz pusta, ale wiem, że kłamie. Zadanie: Napisz program w ~/workspace/pset4/jpg/recover.c, który odzyska te zdjęcia. Hmmm. OK, oto jeszcze jedno wyjaśnienie. Chociaż format JPEG jest bardziej złożony niż BMP, JPEG zawiera „podpisy”, czyli wzorce bajtów, które pomagają odróżnić go od innych formatów plików. Większość plików JPEG zaczyna się od następujących trzech bajtów:0xff 0xd8 0xff
Od pierwszego do trzeciego bajtu, od lewej do prawej. Czwarty bajt będzie najprawdopodobniej jedną z następujących kombinacji: 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,0xe8, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef. Innymi słowy, pierwsze cztery bity czwartego bajtu pliku JPEG to 1110. Jest prawdopodobne, że jeśli znajdziesz jeden z tych wzorów na dysku, na którym przechowywane były zdjęcia (np. na mojej karcie pamięci), będzie to początek pliku JPEG. Oczywiście możesz natknąć się na to, na którym dysku, wyłącznie przez przypadek; odzyskiwania danych nie można nazwać nauką ścisłą.
GO TO FULL VERSION