Chuẩn bị cho công việc
Như mọi khi, trước tiên hãy mở cửa sổ terminal và chạy lệnh.update50
để đảm bảo ứng dụng của bạn đã được cập nhật. Trước khi bạn bắt đầu, hãy làm theo cách này cd ~ / workspace
wget http://cdn.cs50.net/2015/fall/psets/4/pset4/pset4.zip
để tải xuống bản lưu trữ ZIP của tác vụ này. Bây giờ nếu bạn chạy ls bạn sẽ thấy rằng bạn có một tệp có tên pset4.zip trong thư mục ~/workspace . Giải nén nó bằng lệnh: Nếu chạy unzip pset4.zip
lại lệnh ls , bạn sẽ thấy một thư mục khác đã xuất hiện. Bây giờ bạn có thể xóa tệp zip như hiển thị bên dưới: rm -f pset4.zip
Hãy mở thư mục pset4 cd pset4
, chạy ls và đảm bảo thư mục chứa bmp / jpg / questions.txt
whodunit hoặc "Ai đã làm điều này?"
Nếu bạn đã từng nhìn thấy màn hình mặc định của Windows XP (https://en.wikipedia.org/wiki/Bliss_(image)) (những ngọn đồi và bầu trời xanh), thì bạn đã thấy BMP. Trên các trang web, rất có thể bạn đã nhìn thấy ảnh GIF. Bạn đã xem ảnh kỹ thuật số chưa? Vì vậy, chúng tôi rất vui khi được nhìn thấy JPEG. Nếu bạn đã từng chụp ảnh màn hình trên máy Mac, rất có thể bạn đã nhìn thấy PNG. Đọc trên Internet về các định dạng BMP, GIF, JPEG, PNG và trả lời những câu hỏi sau:-
Mỗi định dạng hỗ trợ bao nhiêu màu?
-
Định dạng nào hỗ trợ hoạt ảnh?
-
Sự khác biệt giữa nén lossy và lossless là gì?
-
Định dạng nào sau đây sử dụng tính năng nén có tổn hao?
-
Điều gì xảy ra từ quan điểm kỹ thuật khi một tệp bị xóa trong hệ thống tệp FAT?
-
Có thể làm gì để đảm bảo (với khả năng cao) rằng không thể khôi phục được các tệp đã xóa?
xxd -c 24 -g 3 -s 54 smiley.bmp
Bạn sẽ thấy những gì được hiển thị bên dưới; Chúng tôi lại đánh dấu màu đỏ tất cả các trường hợp 0000ff. Trong hình ảnh ở cột ngoài cùng bên trái, bạn có thể thấy các địa chỉ trong tệp, tương đương với phần bù từ byte đầu tiên của tệp. Tất cả chúng đều được đưa ra trong hệ thống số thập lục phân. Nếu chúng ta chuyển đổi hệ thập lục phân 00000036 thành số thập phân, chúng ta sẽ nhận được 54. Vậy bạn đang xem byte thứ 54 từ Smiley.bmp . Hãy nhớ lại rằng trong các tệp BMP 24 bit, 14 + 40 = 54 byte đầu tiên chứa đầy siêu dữ liệu. Vì vậy, nếu bạn muốn xem siêu dữ liệu, hãy chạy lệnh sau: xxd -c 24 -g 3 smiley.bmp
Nếu Smiley.bmp chứa các ký tự ASCII , chúng ta sẽ thấy chúng ở cột ngoài cùng bên phải trong xxd thay vì tất cả các dấu chấm đó. Vì vậy, Smiley là BMP 24 bit (mỗi pixel được biểu thị bằng 24 8 = 3 byte) với kích thước (độ phân giải) là 8x8 pixel. Do đó, mỗi dòng (hoặc "Dòng quét" như được gọi) chiếm (8 pixel) x (3 byte mỗi pixel) = 24 byte. Số này là bội số của 4 và điều này rất quan trọng vì tệp BMP được lưu trữ hơi khác nếu số byte trong dòng không phải là bội số của 4. Vì vậy, trong small.bmp, một tệp BMP 24 bit khác trong thư mục của chúng tôi, bạn có thể thấy hộp 3x3 pixel màu xanh lục. Nếu mở nó trong trình xem ảnh, bạn sẽ thấy nó giống với hình ảnh hiển thị bên dưới, chỉ có kích thước nhỏ hơn. Do đó, mỗi dòng trong Small.bmp chiếm (3 pixel) × (3 byte mỗi pixel) = 9 byte, không phải là bội số của 4. Để có độ dài dòng là bội số của 4, nó được đệm thêm các số 0: trong khoảng từ 0 đến 3 byte, chúng tôi điền vào mỗi dòng ở định dạng BMP 24 bit (bạn có đoán được tại sao lại như vậy không?). Đối với small.bmp , cần có 3 byte số 0, vì (3 pixel) x (3 byte mỗi pixel) + (3 byte phần đệm) = 12 byte, thực sự là bội số của 4. Để "xem" phần đệm này, làm như sau xxd -c 12 -g 3 -s 54 small.bmp
Lưu ý rằng chúng tôi sử dụng một giá trị khác cho -c so với Smiley.bmp , vì vậy lần này xxd chỉ xuất ra 4 cột (3 cho hình vuông màu xanh lá cây và 1 cho phần đệm). Để rõ ràng, chúng tôi đã đánh dấu tất cả các trường hợp 00ff00 bằng màu xanh lục. Ngược lại, hãy sử dụng xxd cho tệp big.bmp . Nó trông giống hệt như Small.bmp, chỉ có độ phân giải của nó là 12x12 pixel, tức là lớn hơn bốn lần. Chạy lệnh dưới đây. Bạn có thể cần mở rộng cửa sổ để tránh chuyển. xxd -c 36 -g 3 -s 54 large.bmp
Bạn sẽ thấy một cái gì đó như thế này: Xin lưu ý, không có nội dung lạc đề nào trong BMP này! Xét cho cùng, (12 pixel) × (3 byte mỗi pixel) = 36 byte và đây là bội số của 4. Trình soạn thảo hex xxd đã hiển thị cho chúng tôi các byte trong tệp BMP của chúng tôi. Làm thế nào chúng ta có thể có được chúng theo chương trình? Trong copy.c có một chương trình có mục đích duy nhất trong đời là tạo ra một bản sao của BMP, từng mảnh một. Có, bạn có thể sử dụng cp cho việc này . Tuy nhiên, cp sẽ không thể giúp được gì cho anh Boddy. Hãy hy vọng copy.c thực hiện điều này, vì vậy chúng ta sẽ bắt đầu: ./copy smiley.bmp copy.bmp
Nếu bây giờ bạn chạy ls (với cờ thích hợp), bạn sẽ thấy Smiley.bmp và copy.bmp thực sự có cùng kích thước. Hãy kiểm tra lại xem điều này có thực sự đúng không? diff smiley.bmp copy.bmp
Nếu lệnh này không hiển thị bất kỳ thứ gì trên màn hình, điều đó có nghĩa là các tệp thực sự giống hệt nhau (quan trọng: một số chương trình, như Photoshop, bao gồm các số 0 ở cuối một số VMP. Phiên bản sao chép của chúng tôi sẽ loại bỏ chúng, vì vậy đừng lo lắng nếu trong trường hợp sao chép các BMP khác mà bạn đã tải xuống hoặc tạo để thử nghiệm thì bản sao sẽ nhỏ hơn bản gốc vài byte). Bạn có thể mở cả hai tệp trong trình xem ảnh của Ristretto (nhấp đúp) để xác nhận điều này một cách trực quan. Nhưng khác biệt thực hiện việc so sánh này theo từng byte, vì vậy tầm nhìn của cô ấy sắc nét hơn của bạn! Bản sao này được tạo ra như thế nào? Hóa ra copy.c có liên quan đến bmp.h . Hãy đảm bảo: mở bmp.h. Ở đó bạn sẽ thấy định nghĩa thực tế của những tiêu đề mà chúng tôi đã đề cập, được điều chỉnh từ cách triển khai của chính Microsoft. Ngoài ra, tệp này xác định các kiểu dữ liệu BYTE, DWORD, LONG và WORD, là những kiểu dữ liệu thường thấy trong thế giới lập trình Win32 (tức là Windows). Lưu ý rằng về cơ bản đây là các bí danh của các dạng nguyên thủy mà bạn (hy vọng) đã quen thuộc. Hóa ra BITMAPFILEHEADER và BITMAPINFOHEADER đang sử dụng những loại này. Tệp này cũng định nghĩa một cấu trúc có tên RGBTRIPLE. Nó “đóng gói” ba byte: một màu xanh lam, một màu xanh lá cây và một màu đỏ (đây là thứ tự mà chúng ta sẽ tìm kiếm bộ ba RGB trên đĩa). Những cấu trúc này hữu ích như thế nào? Tóm lại, một tệp chỉ đơn giản là một chuỗi byte (hoặc cuối cùng là bit) trên đĩa. Tuy nhiên, những byte này thường được sắp xếp sao cho một số byte đầu tiên đại diện cho một cái gì đó, sau đó một số byte tiếp theo đại diện cho một thứ khác, v.v. "Các định dạng" tệp tồn tại bởi vì chúng tôi có các tiêu chuẩn hoặc quy tắc xác định ý nghĩa của byte. Bây giờ, chúng ta có thể chỉ cần đọc tệp từ đĩa vào RAM dưới dạng một mảng byte lớn. Và chúng ta nhớ rằng byte ở vị trí [i] đại diện cho một thứ, trong khi byte ở vị trí [j] là một thứ khác. Nhưng tại sao không đặt tên cho một số byte này để chúng ta có thể truy xuất chúng từ bộ nhớ dễ dàng hơn? Đây chính xác là những gì cấu trúc trong bmp.h giúp chúng ta. Thay vì coi một tệp là một chuỗi byte dài, chúng ta thấy nó được chia thành các khối dễ hiểu hơn - các chuỗi cấu trúc. Hãy nhớ lại rằng Smiley.bmp có độ phân giải 8x8 pixel, do đó nó chiếm 14 + 40 + (8 × 8) × 3 = 246 byte trên đĩa (bạn có thể kiểm tra điều này bằng lệnh ls). Đây là giao diện trên đĩa theo Microsoft: Chúng ta có thể thấy thứ tự đó rất quan trọng khi nói đến các thành viên của cấu trúc. Byte 57 là rgbtBlue (không phải rgbtRed) vì rgbtBlue được xác định trong RGBTRIPLE trước tiên. Nhân tiện, việc chúng tôi sử dụng thuộc tính đóng gói đảm bảo rằng clang không cố gắng "căn chỉnh từ" các thành viên (với địa chỉ byte đầu tiên của mỗi thành viên là bội số của 4), để chúng tôi không gặp phải lỗ hổng trong cấu trúc của chúng tôi hoàn toàn không tồn tại trên đĩa. Tiếp tục nào. Tìm các URL khớp với BITMAPFILEHEADER và BITMAPINFOHEADER, theo nhận xét trong bmp.h. Chú ý, khoảnh khắc tuyệt vời: bạn đang bắt đầu sử dụng MSDN (Mạng nhà phát triển Microsoft)! Thay vì cuộn qua copy.c , hãy trả lời một số câu hỏi để hiểu cách hoạt động của mã. Như mọi khi, lệnh man là người bạn thực sự của bạn và bây giờ là MSDN. Nếu bạn không biết câu trả lời, hãy tra Google và suy nghĩ về nó. Bạn cũng có thể tham khảo file stdio.h tại https://reference.cs50.net/.
-
Đặt điểm dừng trong main (bằng cách nhấp vào bên trái thước có số dòng chính).
-
Trong tab thiết bị đầu cuối , đi tới ~/workspace/pset4/bmp và biên dịch copy.c vào chương trình sao chép bằng cách sử dụng make.
-
Chạy debug50 copy Smiley.bmp copy.bmp , thao tác này sẽ mở bảng gỡ lỗi ở bên phải.
-
Xem qua chương trình từng bước bằng cách sử dụng bảng bên phải. Lưu ý bf và bi . Trong ~/workspace/pset4/questions.txt , hãy trả lời các câu hỏi:
-
stdint.h là gì ?
-
Mục đích của việc sử dụng uint8_t , uint32_t , int32_t và uint16_t trong một chương trình là gì?
-
BYTE , DWORD , LONG và WORD chứa bao nhiêu byte tương ứng (Giả sử kiến trúc 32 bit)?
- Hai byte đầu tiên của tệp BMP phải là gì (ASCII, thập phân hoặc thập lục phân)? (các byte dẫn đầu, được sử dụng để xác định định dạng tệp (có xác suất cao) thường được gọi là "số ma thuật").
-
Sự khác biệt giữa bfSize và biSize là gì?
-
BiHeight âm có nghĩa là gì?
-
Trường nào trong BITMAPINFOHEADER xác định độ sâu màu trong BMP (tức là số bit trên mỗi pixel)?
-
Tại sao hàm fopen có thể trả về NULL trong copy.c 37?
-
Tại sao đối số thứ ba của fread trong mã của chúng ta lại bằng 1?
-
Giá trị nào trong copy.c 70 xác định phần đệm nếu bi.biWidth là 3?
-
Fseek làm gì?
-
SEEK_CUR là gì?
Trở lại với ông Body. Bài tập:
Viết chương trình có tên whodunit trong một tệp có tên whodunit.c hiển thị bức vẽ của ông Boddy. Hmmmm, cái gì cơ? Giống như copy, chương trình phải lấy chính xác hai đối số dòng lệnh, và nếu bạn chạy chương trình như hình dưới đây thì kết quả sẽ được lưu trong Judge.bmp, trong đó hình vẽ của Mr. Boddy sẽ không bị nhiễu../whodunit clue.bmp verdict.b
Hãy để chúng tôi gợi ý rằng bạn sẽ bắt đầu giải quyết bí ẩn này bằng cách chạy lệnh bên dưới. cp copy.c whodunit.c
Bạn có thể ngạc nhiên về số dòng mã bạn cần viết để giúp ông Boddy. Không có gì không cần thiết ẩn trong Smiley.bmp , vì vậy hãy thoải mái kiểm tra chương trình trên tệp này. Nó nhỏ và bạn có thể so sánh đầu ra của chương trình của mình với đầu ra của xxd trong quá trình phát triển (hoặc có thể có điều gì đó ẩn giấu trong Smiley.bmp ? Thực ra là không). Nhân tiện, vấn đề này có thể được giải quyết theo nhiều cách khác nhau. Một khi bạn xác định được bức vẽ của ông Body, ông ấy sẽ yên nghỉ. Vì whodunit có thể được triển khai theo nhiều cách nên bạn sẽ không thể kiểm tra tính chính xác của việc triển khai bằng check50 . Và hãy để nó làm hỏng cuộc vui của bạn, nhưng giải pháp của trợ lý cũng không có sẵn cho vấn đề whodunit . Cuối cùng, trong tệp Trong ~/workspace/pset4/questions.txt , hãy trả lời câu hỏi sau: Whodunit? //ктоэтосделал?
thay đổi kích thước
Chà, bây giờ - bài kiểm tra tiếp theo! Hãy viết một chương trình có tên là thay đổi kích thước trong thay đổi kích thước.c . Nó sẽ thay đổi kích thước hình ảnh BMP 24-bit không nén theo các bước n. Ứng dụng của bạn phải chấp nhận chính xác ba đối số dòng lệnh, với (n) đầu tiên là số nguyên không lớn hơn 100, đối số thứ hai là tên của tệp sẽ được sửa đổi và đối số thứ ba là tên của phiên bản đã lưu của tệp đã sửa đổi. tài liệu.Usage: ./resize n infile outfile
Với chương trình như vậy, chúng ta có thể tạo big.bmp từ Small.bmp bằng cách thay đổi kích thước của Small.bmp bằng 4 (tức là nhân cả chiều rộng và chiều cao với 4), như hiển thị bên dưới. ./resize 4 small.bmp large.bmp
Để đơn giản, bạn có thể bắt đầu tác vụ bằng cách sao chép lại copy.c và đặt tên cho bản sao thay đổi kích thước.c . Nhưng trước tiên, hãy tự hỏi bản thân và trả lời những câu hỏi sau: việc thay đổi kích thước BMP có ý nghĩa gì (bạn có thể giả định rằng n lần kích thước tệp trong sẽ không vượt quá 232 - 1) . Xác định trường nào trong BITMAPFILEHEADER và BITMAPINFOHEADER bạn cần thay đổi. Xem xét xem bạn có cần thêm hoặc xóa các trường dòng quét hay không . Và, vâng, hãy biết ơn vì chúng tôi không yêu cầu bạn xem xét tất cả các giá trị có thể có của n từ 0 đến 1! (mặc dù, nếu bạn quan tâm thì đây là một vấn đề trong sách của hacker ;)). Tuy nhiên, chúng tôi giả định rằng với n = 1, chương trình sẽ hoạt động chính xác và tệp outfile đầu ra sẽ có cùng kích thước với tệp infile gốc. Bạn có muốn kiểm tra chương trình bằng check50 không? Nhập lệnh sau: ~cs50/pset4/resize
Chà, nếu bạn muốn xem, ví dụ: các tiêu đề
big.bmp (ở dạng thân thiện với người dùng hơn xxd cho phép), bạn cần chạy lệnh sau:
~cs50/pset4/peek large.bmp
Thậm chí tốt hơn, nếu bạn muốn so sánh các tiêu đề có tiêu đề của tệp trợ lý CS50. Bạn có thể chạy các lệnh bên trong thư mục
~/workspace/pset4/bmp của mình (hãy nghĩ xem mỗi lệnh làm gì). Nếu bạn đã sử dụng
malloc , hãy nhớ sử dụng
free để tránh rò rỉ bộ nhớ. Hãy thử sử dụng
valgrind để kiểm tra rò rỉ.
./resize 4 small.bmp student.bmp
~cs50/pset4/resize 4 small.bmp staff.bmp
~cs50/pset4/peek student.bmp staff.bmp
Làm thế nào để quyết định?
-
Mở tệp mà chúng ta cần phóng to, đồng thời tạo và mở một tệp mới trong đó hình ảnh phóng to sẽ được ghi lại;
-
cập nhật thông tin tiêu đề cho tệp đầu ra. Vì hình ảnh của chúng tôi ở định dạng BMP và chúng tôi đang thay đổi kích thước của nó nên chúng tôi cần viết tiêu đề của tệp mới với kích thước mới. Điều gì sẽ thay đổi? Kích thước tệp cũng như kích thước hình ảnh - chiều rộng và chiều cao của nó.
-
Chúng tôi đọc tệp gửi đi, từng dòng, từng pixel. Để thực hiện việc này, chúng ta lại quay lại thư viện I/O tệp và hàm fread. Nó đưa một con trỏ tới một cấu trúc sẽ chứa các byte đã đọc, kích thước của phần tử đơn lẻ mà chúng ta sẽ đọc, số lượng các phần tử như vậy và một con trỏ tới tệp mà chúng ta sẽ đọc từ đó.
-
Chúng ta tăng từng dòng theo chiều ngang theo tỷ lệ đã chỉ định và ghi kết quả vào tệp đầu ra.
Chúng ta ghi tập tin như thế nào? Chúng ta có một hàm fwrite, trong đó chúng ta chuyển một chỉ báo tới cấu trúc nơi chứa dữ liệu được ghi vào tệp, kích thước của phần tử, số của chúng và một con trỏ tới tệp đầu ra. Để tổ chức vòng lặp, chúng ta có thể sử dụng vòng lặp for mà chúng ta đã quen thuộc .
-
Điền vào chỗ trống! Nếu số pixel trên một dòng không phải là bội số của bốn, chúng ta phải thêm "căn chỉnh" - 0 byte. Chúng ta sẽ cần một công thức để tính kích thước căn chỉnh. Để ghi byte rỗng vào tệp đầu ra, bạn có thể sử dụng hàm fputc, truyền cho nó ký tự bạn muốn ghi và một con trỏ tới tệp đầu ra.
Bây giờ chúng ta đã kéo dài chuỗi theo chiều ngang và thêm căn chỉnh vào tệp đầu ra, chúng ta cần di chuyển vị trí hiện tại trong tệp đầu ra vì chúng ta cần chuyển qua căn chỉnh.
-
Tăng kích thước dọc. Nó phức tạp hơn, nhưng chúng ta có thể sử dụng mã mẫu từ copy.c (copy.c mở tệp đầu ra, ghi tiêu đề vào tệp đầu ra, đọc hình ảnh từ tệp nguồn theo từng dòng, từng pixel và ghi chúng vào tập tin đầu ra). Dựa trên điều này, điều đầu tiên bạn có thể làm là chạy lệnh sau: cp copy.c thay đổi kích thước.c
Kéo giãn hình ảnh theo chiều dọc có nghĩa là sao chép từng dòng nhiều lần. Có một số cách khác nhau để làm điều này. Ví dụ: bằng cách sử dụng tính năng viết lại, khi chúng ta lưu tất cả pixel của một dòng vào bộ nhớ và ghi dòng này vào tệp đầu ra trong một vòng lặp nhiều lần nếu cần. Một phương pháp khác là sao chép lại: sau khi đọc một dòng từ tệp gửi đi, ghi nó vào tệp đầu ra và căn chỉnh nó, đưa hàm fseek trở lại đầu dòng trong tệp gửi đi và lặp lại mọi thứ một vài lần.
-
Mở tập tin có nội dung trong thẻ nhớ.
-
Tìm phần đầu của tệp JPEG. Tất cả các tập tin trên thẻ này đều là hình ảnh ở định dạng JPEG.
hồi phục
Để chuẩn bị cho bài tập của Tuần 4, tôi đã dành vài ngày qua để xem các bức ảnh được lưu ở định dạng JPEG bằng máy ảnh kỹ thuật số của tôi trên thẻ nhớ CompactFlash (CF) 1 GB. Làm ơn đừng nói với tôi rằng tôi thực sự đã dành vài ngày qua trên Facebook. Thật không may, kỹ năng máy tính của tôi còn nhiều điều chưa được mong đợi và tôi đã vô tình xóa hết ảnh mà không hề hay biết! May mắn thay, trong thế giới máy tính, “xóa” thường không có nghĩa là “bị giết”. Máy tính của tôi báo rằng thẻ nhớ hiện đã trống nhưng tôi biết nó đang nói dối. Bài tập: Viết chương trình trong ~/workspace/pset4/jpg/recover.c để khôi phục những bức ảnh này. Hừm. Được rồi, đây là một điều làm rõ hơn. Mặc dù định dạng JPEG phức tạp hơn BMP nhưng JPEG có "chữ ký", các mẫu byte giúp phân biệt nó với các định dạng tệp khác. Hầu hết các tệp JPEG đều bắt đầu bằng ba byte sau:0xff 0xd8 0xff
Từ byte đầu tiên đến byte thứ ba, từ trái sang phải. Byte thứ tư rất có thể sẽ là một trong các kết hợp sau: 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,0xe8, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef. Nói cách khác, bốn bit đầu tiên của byte thứ tư của tệp JPEG là 1110. Rất có thể, nếu bạn tìm thấy một trong những mẫu này trên ổ đĩa nơi lưu trữ ảnh (như thẻ nhớ của tôi), thì đây sẽ là phần khởi đầu của tệp JPEG. Tất nhiên, bạn có thể gặp phải trường hợp này trên đĩa nào đó hoàn toàn là tình cờ, phục hồi dữ liệu không thể gọi là một môn khoa học chính xác.
GO TO FULL VERSION