Cara menulis komponen parsing gambar di Delphi
Sebagai alat pengembangan RAD yang kuat, Delphi selalu memiliki keunggulan unik dalam pengembangan perangkat lunak aplikasi. Keunggulan ini juga tercermin dalam pengembangan perangkat lunak yang berhubungan dengan gambar. Jika Anda ingin menempatkan gambar di desktop, Anda hanya perlu menempatkan kontrol Gambar di desktop, lalu Anda dapat memuat gambar secara sewenang-wenang dalam format BMP, WMF, EMF, dan format lainnya melalui properti Gambar. Jika Anda ingin menambahkan dukungan untuk JPEG, Anda hanya perlu menambahkan unit JPEG. Bahkan setelah memuat JPEG ke dalam Gambar, Delphi akan secara otomatis menambahkan unit JPEG. Semuanya sangat sederhana untuk dilakukan. Format dasar telah dienkapsulasi dalam VCL, jadi bagaimana Delphi mengimplementasikan dukungan untuk format gambar seperti JPEG?
Sebenarnya proses implementasinya mudah dilihat dari TPicture yang bisa diartikan sebagai wadah untuk semua objek gambar.
Misalnya, ada dua baris kode berikut di JPEG.pas:
TPicture.RegisterFileFormat('jpeg', sJPEGImageFile, TJPEGImage);
TPicture.RegisterFileFormat('jpg', sJPEGImageFile, TJPEGImage);
(sJPEGImageFile = 'File Gambar JPEG', lihat JConsts.pas)
Apa maksudnya? Ini dapat dipahami sebagai kelas yang mendaftarkan TJPEGImage sebagai file gambar dengan dua akhiran: jpeg dan jpg.
Intinya adalah menyimpan akhiran, deskripsi gambar, kelas analisis gambar tertentu, dan informasi lainnya ke FileFormats.
Lihat kode berikut untuk detailnya:
var FileFormats: TFileFormatsList = nihil;
prosedur kelas TPicture.RegisterFileFormat(const AExtension,
ADeskripsi: string; AGraphicClass: TGraphicClass);
mulai
GetFileFormats.Add(AExtension, ADescription, 0, AGraphicClass);
akhir;
fungsi GetFileFormats: TFileFormatsList;
mulai
jika FileFormats = nil maka FileFormats := TFileFormatsList.Create;
Hasil := Format File;
akhir;
TPicture mendukung empat format gambar secara default karena telah ditambahkan di konstruktor TFileFormatsList.
konstruktor TFileFormatsList.Create;
mulai
warisan Buat;
Tambahkan('wmf', SVMetafiles, 0, TMetafile);
Tambahkan('emf', SVEnhMetafiles, 0, TMetafile);
Tambahkan('ico', SVIcons, 0, TIcon);
Tambahkan('bmp', SVBitmaps, 0, TBitmap);
akhir;
Melalui informasi yang disimpan dalam FileFormats kontrol OpenPictureDialog secara otomatis menghasilkan daftar jenis file yang didukung.
Jadi bagaimana cara menulis kelas parsing gambar ini?
TGraphic adalah kelas dasar objek TBitmap, TIcon, dan TMetafile. Demikian pula, kelas penguraian gambar di sini juga harus berasal dari TGraphic. Ini dapat menghemat banyak pekerjaan dengan menggunakan banyak kode yang telah dienkapsulasi dalam VCL.
Untuk mengimplementasikan fungsi dasar, biasanya Anda hanya perlu membebani tiga anggota secara berlebihan:
TXXXGambar = kelas(TGrafis)
terlindung
procedure Draw(ACanvas: TCanvas; const Rect: TRect);//Gambarlah gambar ke kanvas
publik
procedure LoadFromStream(Stream: TStream); //Dapatkan data gambar dari aliran
procedure SaveToStream(Stream: TStream); //Menulis data gambar ke dalam aliran
akhir;
Karena TGraphic.LoadFromFile/TGraphic.SaveToFile telah mengimplementasikan fungsi membaca data dari nama file ke aliran/menulis data dalam aliran ke file yang sesuai, tidak perlu membebani secara berlebihan tanpa kebutuhan khusus. Anggota Draw secara alami digunakan untuk menggambar gambar ke kanvas. Karena enkapsulasi GDI TCanvas yang sempurna, tidak perlu mempertimbangkan proses cara menggambar gambar ke formulir menggunakan GDI. Yang tersisa hanyalah menulis kode untuk bagian penguraian gambar.
Mari kita ambil format RAS sebagai contoh untuk pembahasan lebih lanjut.
TGraphic tidak digunakan sebagai kelas dasar di sini, tetapi TBitmap digunakan. Hal ini semakin menghemat proses implementasi Draw dan hanya perlu mengimplementasikan proses konversi ke bitmap di LoadFromStream.
jenis
TRASGrafis = kelas(TBitmap)
publik
prosedur LoadFromStream(Stream: TStream);
prosedur SaveToStream(Stream: TStream);
akhir;
//Tentukan tipe rekaman yang menjelaskan header file RAS
TRASHeader = catatan yang dikemas
Sihir, //tandai
Lebar, //lebar
Tinggi, //tinggi
Kedalaman, //kedalaman warna
Panjang, //panjang data gambar, mungkin sama dengan 0
RasType, //Jenis format
Tipe Peta, //tipe palet
MapLength: Cardinal; //Panjang data palet
akhir;
//Sangat penting untuk menentukan tipe record yang digunakan untuk mendeskripsikan header file RAS
konstanta
//Definisikan konstanta yang mewakili semua jenis RAS
RT_OLD = 0;
RT_STANDAR = 1;
RT_BYTE_ENCODED = 2;
RT_FORMAT_RGB = 3;
RT_FORMAT_TIFF = 4;
RT_FORMAT_IFF = 5;
RT_EXPERIMENTAL = $FFFF;
//Definisikan konstanta yang mewakili tipe palet
RMT_NONE = 0; //Tidak ada data palet
RMT_EQUAL_RGB = 1;
RMT_RAW = 2;
{Jika format RAS adalah RT_OLD, panjang datanya mungkin 0}
fungsi SwapLong(Nilai konstan: Kardinal): Kardinal;
asm
BSWAP EAX//Instruksi pertukaran byte panggilan
akhir;
//Lemparkan pengecualian, parameternya adalah informasi pengecualian spesifik
prosedur RasError(const ErrorString: String);
mulai
naikkan EInvalidGraphic.Create(ErrorString);
akhir;
{Berikut ini adalah kode bagian implementasinya. }
prosedur TRASGraphic.LoadFromStream(Stream: TStream);
var
Tajuk: Pemimpin SAMPAH;
Baris8: PByte;
Baris24: PRGBTriple;
Baris32: PRGBQuad;
Peta PM: PByte;
Y: Bilangan bulat;
Saya: Bilangan Bulat;
Dibaca Peta: Boolean;
Sobat: TMaxLogPalette;
R,G,B:array[0..255] Byte;
ColorByte: Byte;
mulai
dengan Stream lakukan
mulai
ReadBuffer(Header, SizeOf(Header)); //Membaca data header file ke dalam record Header
dengan Header lakukan
mulai
Lebar := SwapLong(Lebar);
Tinggi := SwapLong(Tinggi);
Kedalaman := SwapLong(Kedalaman);
Panjang := SwapLong(Panjang);
Tipe RAS := SwapLong(RASType);
Tipe Peta := SwapLong(Tipe Peta);
Panjang Peta := SwapLong(Panjang Peta);
akhir;
//Karena urutan pembacaan data, Anda perlu memanggil SwapLong di atas untuk mengubah urutannya.
jika (Header.Magic = $956AA659) dan
(Header.Lebar<>0) dan (Header.Height<>0) dan
(Header.Depth di [1,8,24,32]) dan (Header.RasType di [RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB]) lalu
mulai
Lebar := Header.Lebar;
Tinggi := Header.Tinggi;
Dibaca Peta := Salah;
case Header.Kedalaman
1:Format Piksel := pf1Bit;
8:
mulai
Format Piksel := pf8Bit;
case Header.MapType dari
RMT_TIDAK ADA:
mulai
Versi Pal.pal:=$300;
Pal.palNumEntries:=256;
untuk I := 0 hingga 255 lakukan
mulai
Pal.palPalEntry[I].peRed:=I;
Pal.palPalEntry[I].peGreen:=I;
Pal.palPalEntry[I].peBlue:=I;
Pal.palPalEntry[I].peFlags:=0;
akhir;
Palet := CreatePalette(PLogPalette(@Pal)^);
//Ketika kedalaman warna gambar adalah 8 bit dan tidak ada informasi palet, buat palet skala abu-abu 8-bit
akhir;
RMT_EQUAL_RGB:
mulai
jika (Header.MapLength = 3*256) maka
mulai
Versi Pal.pal:=$300;
Pal.palNumEntries:=256;
BacaBuffer(R,256);
BacaBuffer(G,256);
BacaBuffer(B,256);
untuk I := 0 hingga 255 lakukan
mulai
Pal.palPalEntry[I].peRed:=R[I];
Pal.palPalEntry[I].peGreen:=G[I];
Pal.palPalEntry[I].peBlue:=B[I];
Pal.palPalEntry[I].peFlags:=0;
akhir;
Palet := CreatePalette(PLogPalette(@Pal)^);
//Baca informasi palet dalam file
//Untuk operasi palet terkait API, silakan periksa MSDN
akhir
kalau tidak
RasError('Panjang palet salah!');
Dibaca Peta := Benar;
akhir;
RMT_RAW:
mulai
RasError('Format file tidak didukung!');
akhir;
akhir;
akhir;
24:Format Piksel := pf24Bit;
32:
mulai
Format Piksel := pf32Bit;
//
akhir;
akhir;
jika (bukan MapReaded) dan (Header.MapLength>0) lalu
mulai
Posisi := Posisi + Header.MapLength;
akhir;
//Jika panjang palet bukan 0 dan informasi relevan tidak terbaca dengan benar, lewati bagian data ini
case Header.Kedalaman
8:
mulai
jika Header.RasType = RT_BYTE_ENCODED maka
mulai
//MENYANDI
//Silakan periksa sendiri informasi tentang pengkodean dan decoding kompresi RLE.
RasError('Format kompresi tidak didukung!');
akhir
kalau tidak
mulai
untuk Y := 0 sampai Tinggi-1 do
mulai
Baris8:=Garis Pindai[Y];
ReadBuffer(Baris8^,Lebar);
jika (Lebar mod 2)=1 maka
mulai
Posisi := Posisi + 1;
akhir;
akhir;
akhir;
akhir;{akhir 8Bit}
dua puluh empat:
mulai
case Header.RasType dari
RT_OLD,
RT_STANDAR:
mulai
untuk Y := 0 sampai Tinggi-1 do
mulai
Baris24:=Garis Pindai[Y];
ReadBuffer(Baris24^,Lebar*3);
jika (Lebar mod 2)=1 maka
mulai
Posisi := Posisi + 1;
akhir;
akhir;
akhir;
RT_BYTE_ENCODED:
mulai
//MENYANDI
//Silakan periksa sendiri informasi tentang pengkodean dan decoding kompresi RLE.
RasError('Format kompresi tidak didukung!');
akhir;
RT_FORMAT_RGB:
mulai
untuk Y := 0 sampai Tinggi-1 do
mulai
Baris24:=Garis Pindai[Y];
ReadBuffer(Baris24^,Lebar*3);
untuk I := 0 hingga Lebar-1 lakukan
mulai
ColorByte := Baris24^.rgbtMerah;
Baris24^.rgbtMerah := Baris24^.rgbtBiru;
Baris24^.rgbtBiru := ColorByte;
Inc(Baris24);
akhir;
//Jika dalam format RT_FORMAT_RGB, ambil datanya dengan RGB, di sini Anda perlu menukar nilai R dan B
jika (Lebar mod 2)=1 maka
mulai
Posisi := Posisi + 1;
akhir;
akhir;
akhir;{akhir RT_FORMAT_RGB}
kalau tidak
RasError('Format file tidak didukung!');
akhir;
akhir;{akhir 24Bit}
32:
mulai
case Header.RasType dari
RT_OLD,
RT_STANDAR:
mulai
untuk Y := 0 sampai Tinggi-1 do
mulai
Baris32:=Garis Pindai[Y];
ReadBuffer(Baris32^,Lebar*4);
untuk I := 0 hingga Lebar-1 lakukan
mulai
ColorByte := Baris32^.rgbDicadangkan;
Row32^.rgbReserved := Row32^.rgbBlue;
Baris32^.rgbBiru := Baris32^.rgbHijau;
Baris32^.rgbHijau := Baris32^.rgbMerah;
Baris32^.rgbMerah := ColorByte;
Inc(Baris32);
akhir;
//Saat menggunakan warna 32-bit, Anda perlu menyesuaikan urutan data setelah membaca.
akhir;
akhir;
RT_BYTE_ENCODED:
mulai
//MENYANDI
//Silakan periksa sendiri informasi tentang pengkodean dan decoding kompresi RLE.
RasError('Format kompresi tidak didukung!');
akhir;
RT_FORMAT_RGB:
mulai
Untuk Y := 0 sampai Tinggi-1 lakukan
mulai
Baris32:=Garis Pindai[Y];
ReadBuffer(Baris32^,Lebar*4);
untuk I := 0 hingga Lebar-1 lakukan
mulai
ColorByte := Baris32^.rgbBiru;
Baris32^.rgbBiru := Baris32^.rgbDicadangkan;
Row32^.rgbReserved := ColorByte;
ColorByte := Baris32^.rgbHijau;
Baris32^.rgbHijau := Baris32^.rgbMerah;
Baris32^.rgbMerah := ColorByte;
Inc(Baris32);
akhir;
//Kode untuk penyesuaian pesanan dan pertukaran nilai R dan B digabungkan di sini.
akhir;
akhir;{akhir RT_FORMAT_RGB}
kalau tidak
RasError('Format file tidak didukung!');
akhir;{akhir 32Bit}
akhir;
kalau tidak
mulai
Gambar Gratis;
RasError('Format file tidak didukung!');
akhir;
akhir;
akhir
kalau tidak
RasError('Format file tidak didukung!');
akhir;{diakhiri dengan}
akhir;
{Kode berikut muncul beberapa kali pada kode di atas:
jika (Lebar mod 2)=1 maka
mulai
Posisi := Posisi + 1;
akhir;
Hal ini dikarenakan data pada setiap baris harus word-aligned, yaitu data pada setiap baris harus dicatat dengan jumlah byte yang genap. Jika informasi warna setiap piksel dicatat dalam 1 byte (8 bit) atau 3 byte (24 bit) dan jumlah piksel pada setiap baris ganjil, maka satu byte harus diisi. Jadi satu byte dilewati di sini.
dalam kode di belakang
jika (Lebar mod 2) = 1 maka
mulai
IsiByte:=0;
Aliran.Tulis(FillByte,1);
akhir;
Hal ini juga didasarkan pada prinsip yang sama. }
prosedur TRASGraphic.SaveToStream(Stream: TStream);
var
Tajuk: Pemimpin SAMPAH;
Baris8: PByte;
Baris24: PRGBTriple;
Baris32: PRGBQuad;
IsiByte: Byte;
Y: Bilangan bulat;
Saya: Bilangan Bulat;
Sobat: TMaxLogPalette;
R,G,B:array[0..255] Byte;
mulai
Header.Sihir := $956AA659;
Header.Lebar := SwapLong(Lebar);
Header.Tinggi := SwapLong(Tinggi);
Header.RasType := SwapLong(RT_STANDARD);
jika (PixelFormat = pf1bit) atau (PixelFormat = pf4bit) maka
Format Piksel:=pf8bit
else if (PixelFormat <> pf8bit) dan (PixelFormat <> pf24bit) dan (PixelFormat <> pf32bit) maka
Format Piksel:=pf24bit;
kasus PixelFormat dari
pf8bit:
mulai
Header.Panjang := SwapLong(Tinggi*(Lebar+(Lebar mod 2)));
Header.Kedalaman := SwapLong(8);
Header.MapType := SwapLong(RMT_EQUAL_RGB);
Header.MapLength := SwapLong(3*256);
Stream.WriteBuffer(Header,SizeOf(Header));
GetPaletteEntries(Palette, 0, 256, Pal.palPalEntry);
untuk I := 0 hingga 255 lakukan
mulai
R[I]:=Pal.palPalEntry[I].peRed;
G[I]:=Pal.palPalEntry[I].peGreen;
B[I]:=Pal.palPalEntry[I].peBlue;
akhir;
//Untuk operasi palet terkait API, silakan periksa MSDN
Aliran.WriteBuffer(R,256);
Aliran.WriteBuffer(G,256);
Aliran.WriteBuffer(B,256);
untuk Y := 0 sampai Tinggi-1 do
mulai
Baris8 := ScanLine[Y];
Stream.WriteBuffer(Baris8^,Lebar);
jika (Lebar mod 2) = 1 maka
mulai
IsiByte:=0;
Aliran.Tulis(FillByte,1);
akhir;
akhir;
akhir;
pf32bit:
mulai
Header.Panjang := SwapLong(Tinggi*Lebar*4);
Header.Kedalaman := SwapLong(32);
Header.MapType := SwapLong(RMT_NONE);
Header.Panjang Peta := 0;
Stream.WriteBuffer(Header,SizeOf(Header));
untuk Y := 0 sampai Tinggi-1 do
mulai
Baris32 := ScanLine[Y];
untuk I := 0 hingga Lebar-1 lakukan
mulai
Stream.WriteBuffer(Row32.rgbReserved,1);
Aliran.WriteBuffer(Baris32^,3);
Inc(Baris32);
akhir;
akhir;
akhir;
kalau tidak
mulai
Header.Panjang := SwapLong(Tinggi*Lebar*3);
Header.Kedalaman := SwapLong(24);
Header.MapType := SwapLong(RMT_NONE);
Header.Panjang Peta := 0;
Stream.WriteBuffer(Header,SizeOf(Header));
untuk Y := 0 sampai Tinggi-1 do
mulai
Baris24 := ScanLine[Y];
Stream.WriteBuffer(Baris24^,Lebar*3);
jika (Lebar mod 2) = 1 maka
mulai
IsiByte:=0;
Aliran.Tulis(FillByte,1);
akhir;
akhir;
akhir;
akhir;
//SaveToStream pada dasarnya adalah proses kebalikan dari LoadFromStream.
akhir;
inisialisasi
TPicture.RegisterFileFormat('RAS', 'Sun RAS', TRASGraphic);
finalisasi
TPicture.Batalkan PendaftaranGraphicClass(TRASGraphic);
Dengan beberapa baris kode ini, komponen penguraian gambar yang lengkap telah selesai.