วิธีเขียนองค์ประกอบการแยกวิเคราะห์รูปภาพใน Delphi
ในฐานะเครื่องมือพัฒนา RAD อันทรงพลัง Delphi มีข้อได้เปรียบที่เป็นเอกลักษณ์ในการพัฒนาซอฟต์แวร์แอพพลิเคชั่นมาโดยตลอด ข้อได้เปรียบนี้ยังสะท้อนให้เห็นในการพัฒนาซอฟต์แวร์ที่เกี่ยวข้องกับภาพอีกด้วย หากคุณต้องการวางรูปภาพบนเดสก์ท็อป คุณเพียงแค่วางตัวควบคุมรูปภาพบนเดสก์ท็อป จากนั้นคุณก็สามารถโหลดรูปภาพในรูปแบบ BMP, WMF, EMF และรูปแบบอื่น ๆ ได้ตามต้องการผ่านคุณสมบัติรูปภาพ หากคุณต้องการเพิ่มการรองรับ JPEG คุณจะต้องเพิ่มหน่วย JPEG เท่านั้น แม้หลังจากโหลด JPEG ในภาพแล้ว Delphi จะเพิ่มหน่วย JPEG โดยอัตโนมัติ ทุกอย่างมันง่ายมากที่จะทำ รูปแบบพื้นฐานได้รับการห่อหุ้มไว้ใน VCL แล้ว Delphi จะสนับสนุนรูปแบบภาพเช่น JPEG ได้อย่างไร
ในความเป็นจริงมันง่ายที่จะเห็นกระบวนการนำไปใช้งานจาก TPicture ซึ่งสามารถเข้าใจได้ว่าเป็นคอนเทนเนอร์สำหรับออบเจ็กต์รูปภาพทั้งหมด
ตัวอย่างเช่น มีโค้ดสองบรรทัดต่อไปนี้ใน JPEG.pas:
TPicture.RegisterFileFormat('jpeg', sJPEGImageFile, TJPEGImage);
TPicture.RegisterFileFormat('jpg', sJPEGImageFile, TJPEGImage);
(sJPEGImageFile = 'ไฟล์ภาพ JPEG' ดูที่ JConsts.pas)
มันหมายความว่าอะไร? สามารถเข้าใจได้ว่าเป็นคลาสที่ลงทะเบียน TJPEGImage เป็นไฟล์รูปภาพที่มีส่วนต่อท้ายสองรายการ: jpeg และ jpg
สิ่งสำคัญคือการบันทึกส่วนต่อท้าย คำอธิบายรูปภาพ คลาสการวิเคราะห์รูปภาพเฉพาะ และข้อมูลอื่น ๆ ลงใน FileFormats
ดูรหัสต่อไปนี้เพื่อดูรายละเอียด:
var FileFormats: TFileFormatsList = ไม่มี;
คลาส PROcedure TPicture.RegisterFileFormat (const AExtension,
คำอธิบาย: สตริง AGraphicClass: TGraphicClass);
เริ่ม
GetFileFormats.Add (AExtension, ADescription, 0, AGraphicClass);
จบ;
ฟังก์ชัน GetFileFormats: TFileFormatsList;
เริ่ม
ถ้า FileFormats = ไม่มี ดังนั้น FileFormats := TFileFormatsList.Create;
ผลลัพธ์ := รูปแบบไฟล์;
จบ;
TPicture รองรับรูปแบบภาพสี่รูปแบบตามค่าเริ่มต้น เนื่องจากถูกเพิ่มไว้ใน Constructor ของ TFileFormatsList
ตัวสร้าง TFileFormatsList.Create;
เริ่ม
สืบทอดสร้าง;
เพิ่ม('wmf', SVMetafiles, 0, TMetafile);
เพิ่ม('emf', SVENhMetafiles, 0, TMetafile);
เพิ่ม('ico', SVIcons, 0, TIcon);
เพิ่ม('bmp', SVBitmaps, 0, TBitmap);
จบ;
ด้วยข้อมูลที่บันทึกไว้ใน FileFormats ตัวควบคุม OpenPictureDialog จะสร้างรายการประเภทไฟล์ที่รองรับโดยอัตโนมัติ
แล้วจะเขียนคลาสแยกวิเคราะห์รูปภาพเหล่านี้ได้อย่างไร?
TGraphic เป็นคลาสพื้นฐานของอ็อบเจ็กต์ TBitmap, TIcon และ TMetafile ในทำนองเดียวกัน คลาสการแยกวิเคราะห์รูปภาพควรได้มาจาก TGraphic เช่นกัน ซึ่งสามารถบันทึกงานได้มากโดยใช้โค้ดจำนวนมากที่ห่อหุ้มไว้ใน VCL
หากต้องการใช้ฟังก์ชันพื้นฐาน โดยทั่วไปคุณจะต้องมีสมาชิกเกินสามคนเท่านั้น:
TXXXImage = คลาส (TGraphic)
ได้รับการคุ้มครอง
ขั้นตอนการวาด (ACanvas: TCanvas; const Rect: TRect); แทนที่; // วาดภาพลงบนผืนผ้าใบ
สาธารณะ
ขั้นตอน LoadFromStream (สตรีม: TStream); // รับข้อมูลรูปภาพจากสตรีม
ขั้นตอน SaveToStream (สตรีม: TStream); // เขียนข้อมูลรูปภาพลงในสตรีม
จบ;
เนื่องจาก TGraphic.LoadFromFile/TGraphic.SaveToFile ได้ใช้ฟังก์ชันการอ่านข้อมูลจากชื่อไฟล์ไปยังสตรีม/การเขียนข้อมูลในสตรีมไปยังไฟล์ที่เกี่ยวข้องแล้ว จึงไม่จำเป็นต้องโอเวอร์โหลดโดยไม่จำเป็นพิเศษ Draw ของสมาชิกมักใช้ในการวาดภาพลงบนผืนผ้าใบ เนื่องจากการห่อหุ้ม GDI ที่สมบูรณ์แบบของ TCanvas จึงไม่จำเป็นต้องพิจารณาขั้นตอนการวาดภาพลงในแบบฟอร์มโดยใช้ GDI สิ่งที่เหลืออยู่คือการเขียนโค้ดสำหรับส่วนแยกวิเคราะห์รูปภาพ
ลองใช้รูปแบบ RAS เป็นตัวอย่างเพื่อการอภิปรายต่อไป
TGraphic ไม่ได้ใช้เป็นคลาสพื้นฐานที่นี่ แต่ใช้ TBitmap ซึ่งจะช่วยบันทึกกระบวนการใช้งาน Draw เพิ่มเติม และจำเป็นต้องใช้กระบวนการแปลงเป็นบิตแมปใน LoadFromStream เท่านั้น
พิมพ์
TRASGraphic = คลาส (TBitmap)
สาธารณะ
ขั้นตอน LoadFromStream (สตรีม: TStream);
ขั้นตอน SaveToStream (สตรีม: TStream แทนที่;
จบ;
//กำหนดประเภทบันทึกที่อธิบายส่วนหัวของไฟล์ RAS
TRASHeader = บันทึกที่อัดแน่น
เมจิก //มาร์ค
ความกว้าง //ความกว้าง
ส่วนสูง//สูง
ความลึก // ความลึกของสี
ความยาว //ความยาวของข้อมูลภาพอาจเท่ากับ 0
RasType, //ประเภทรูปแบบ
MapType, //ประเภทจานสี
MapLength: Cardinal; // ความยาวข้อมูลจานสี
จบ;
//จำเป็นอย่างยิ่งที่จะต้องกำหนดประเภทบันทึกที่ใช้อธิบายส่วนหัวของไฟล์ RAS
ค่าคงที่
//กำหนดค่าคงที่ที่แสดงถึง RAS ทุกประเภท
RT_OLD = 0;
RT_มาตรฐาน = 1;
RT_BYTE_ENCODED = 2;
RT_FORMAT_RGB = 3;
RT_FORMAT_TIFF = 4;
RT_FORMAT_IFF = 5;
RT_ทดลอง = $FFFF;
//กำหนดค่าคงที่ที่แสดงประเภทจานสี
RMT_NONE = 0; //ไม่มีข้อมูลจานสี
RMT_EQUAL_RGB = 1;
RMT_RAW = 2;
{หากรูปแบบของ RAS เป็น RT_OLD ความยาวของข้อมูลอาจเป็น 0}
ฟังก์ชั่น SwapLong (ค่า const: พระคาร์ดินัล): พระคาร์ดินัล;
asm
BSWAP EAX//คำสั่งการแลกเปลี่ยนไบต์การโทร
จบ;
//โยนข้อยกเว้น พารามิเตอร์คือข้อมูลข้อยกเว้นเฉพาะ
ขั้นตอน RasError (const ErrorString: String);
เริ่ม
เพิ่ม EInvalidGraphic.Create (ErrorString);
จบ;
{ต่อไปนี้เป็นรหัสสำหรับส่วนการใช้งาน -
ขั้นตอน TRASGraphic.LoadFromStream (สตรีม: TStream);
var
ส่วนหัว: TRASHeader;
แถว8: Pไบต์;
แถวที่ 24: PRGBTriple;
แถว32: PRGBQuad;
แผนที่: PByte;
Y: จำนวนเต็ม;
ฉัน: จำนวนเต็ม;
MapReaded: บูลีน;
เพื่อน: TMaxLogPalette;
R,G,B:อาร์เรย์[0..255] ของไบต์;
ColorByte: ไบต์;
เริ่ม
ด้วยการสตรีมทำ
เริ่ม
ReadBuffer(Header, SizeOf(Header)); //อ่านข้อมูลส่วนหัวของไฟล์ลงในส่วนหัวของบันทึก
ด้วยส่วนหัวทำ
เริ่ม
ความกว้าง := SwapLong(กว้าง);
ความสูง := SwapLong(ความสูง);
ความลึก := SwapLong(ความลึก);
ความยาว := SwapLong(ความยาว);
RASType := SwapLong(RASType);
MapType := SwapLong(MapType);
MapLength := SwapLong(MapLength);
จบ;
//เนื่องจากลำดับการอ่านข้อมูล คุณต้องเรียก SwapLong ด้านบนเพื่อเปลี่ยนลำดับ
ถ้า (Header.Magic = $956AA659) และ
(Header.Width<>0) และ (Header.Height<>0) และ
(Header.Depth ใน [1,8,24,32]) และ (Header.RasType ใน [RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB]) จากนั้น
เริ่ม
ความกว้าง := ส่วนหัวความกว้าง;
ความสูง := ส่วนหัวความสูง;
MapReaded := เท็จ;
ส่วนหัวของกรณีความลึกของ
1:รูปแบบพิกเซล := pf1Bit;
8:
เริ่ม
รูปแบบพิกเซล := pf8Bit;
กรณี Header.MapType ของ
RMT_NONE:
เริ่ม
Pal.palVersion:=$300;
Pal.palNumรายการ:=256;
สำหรับฉัน := 0 ถึง 255 ทำ
เริ่ม
Pal.palPalEntry[I].peRed:=I;
Pal.palPalEntry[I].peGreen:=I;
Pal.palPalEntry[I].peBlue:=I;
Pal.palPalEntry[I].peFlags:=0;
จบ;
จานสี := CreatePalette(PLogPalette(@Pal)^);
//เมื่อความลึกของสีของภาพคือ 8 บิตและไม่มีข้อมูลจานสี ให้สร้างจานสีระดับสีเทา 8 บิต
จบ;
RMT_EQUAL_RGB:
เริ่ม
ถ้า (Header.MapLength = 3*256) แล้ว
เริ่ม
Pal.palVersion:=$300;
Pal.palNumรายการ:=256;
อ่านบัฟเฟอร์(R,256);
อ่านบัฟเฟอร์(G,256);
อ่านบัฟเฟอร์(B,256);
สำหรับฉัน := 0 ถึง 255 ทำ
เริ่ม
Pal.palPalEntry[I].peRed:=R[I];
Pal.palPalEntry[I].peGreen:=G[I];
Pal.palPalEntry[I].peBlue:=B[I];
Pal.palPalEntry[I].peFlags:=0;
จบ;
จานสี := CreatePalette(PLogPalette(@Pal)^);
//อ่านข้อมูลพาเล็ตในไฟล์
//สำหรับการดำเนินการจานสีที่เกี่ยวข้องกับ API โปรดตรวจสอบ MSDN
จบ
อื่น
RasError('ความยาวของจานสีไม่ถูกต้อง!');
MapReaded := จริง;
จบ;
RMT_RAW:
เริ่ม
RasError('รูปแบบไฟล์ที่ไม่รองรับ!');
จบ;
จบ;
จบ;
24:รูปแบบพิกเซล := pf24Bit;
32:
เริ่ม
รูปแบบพิกเซล := pf32Bit;
-
จบ;
จบ;
ถ้า (ไม่ใช่ MapReaded) และ (Header.MapLength>0) แล้ว
เริ่ม
ตำแหน่ง := ตำแหน่ง + ส่วนหัวMapLength;
จบ;
//หากความยาวของจานสีไม่ใช่ 0 และข้อมูลที่เกี่ยวข้องอ่านไม่ถูกต้อง ให้ข้ามข้อมูลส่วนนี้
ส่วนหัวของกรณีความลึกของ
8:
เริ่ม
ถ้า Header.RasType = RT_BYTE_ENCODED แล้ว
เริ่ม
//เข้ารหัส
//โปรดตรวจสอบข้อมูลเกี่ยวกับการเข้ารหัสและการถอดรหัสการบีบอัด RLE ด้วยตนเอง
RasError('ไม่รองรับรูปแบบการบีบอัด!');
จบ
อื่น
เริ่ม
สำหรับ Y := 0 ถึงความสูง -1 ทำ
เริ่ม
Row8:=สแกนไลน์[Y];
ReadBuffer(แถว8^,ความกว้าง);
ถ้า (ความกว้าง mod 2)=1 แล้ว
เริ่ม
ตำแหน่ง := ตำแหน่ง + 1;
จบ;
จบ;
จบ;
สิ้นสุด; {สิ้นสุดของ 8Bit}
ยี่สิบสี่:
เริ่ม
กรณี Header.RasType ของ
RT_OLD,
RT_มาตรฐาน:
เริ่ม
สำหรับ Y := 0 ถึงความสูง -1 ทำ
เริ่ม
แถว24:=สแกนไลน์[Y];
ReadBuffer(แถว24^,กว้าง*3);
ถ้า (ความกว้าง mod 2)=1 แล้ว
เริ่ม
ตำแหน่ง := ตำแหน่ง + 1;
จบ;
จบ;
จบ;
RT_BYTE_ENCODED:
เริ่ม
//เข้ารหัส
//โปรดตรวจสอบข้อมูลเกี่ยวกับการเข้ารหัสและการถอดรหัสการบีบอัด RLE ด้วยตนเอง
RasError('ไม่รองรับรูปแบบการบีบอัด!');
จบ;
RT_FORMAT_RGB:
เริ่ม
สำหรับ Y := 0 ถึงความสูง-1 ทำ
เริ่ม
แถว24:=สแกนไลน์[Y];
ReadBuffer(แถว24^,กว้าง*3);
สำหรับฉัน := 0 ถึงความกว้าง -1 ทำ
เริ่ม
ColorByte := Row24^.rgbtRed;
Row24^.rgbtRed := แถว24^.rgbtBlue;
Row24^.rgbtBlue := ColorByte;
Inc(แถว24);
จบ;
//เมื่ออยู่ในรูปแบบ RT_FORMAT_RGB รับข้อมูลเป็น RGB ที่นี่คุณต้องแลกเปลี่ยนค่าของ R และ B
ถ้า (ความกว้าง mod 2)=1 แล้ว
เริ่ม
ตำแหน่ง := ตำแหน่ง + 1;
จบ;
จบ;
สิ้นสุด;{สิ้นสุด RT_FORMAT_RGB}
อื่น
RasError('รูปแบบไฟล์ที่ไม่รองรับ!');
จบ;
สิ้นสุด; {สิ้นสุดของ 24Bit}
32:
เริ่ม
กรณี Header.RasType ของ
RT_OLD,
RT_มาตรฐาน:
เริ่ม
สำหรับ Y := 0 ถึงความสูง -1 ทำ
เริ่ม
Row32:=สแกนไลน์[Y];
ReadBuffer(แถว32^,กว้าง*4);
สำหรับฉัน := 0 ถึงความกว้าง -1 ทำ
เริ่ม
ColorByte := Row32^.rgbReserved;
Row32^.rgbReserved := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc(แถว32);
จบ;
//เมื่อใช้สีแบบ 32 บิต คุณจะต้องปรับลำดับของข้อมูลหลังจากอ่าน
จบ;
จบ;
RT_BYTE_ENCODED:
เริ่ม
//เข้ารหัส
//โปรดตรวจสอบข้อมูลเกี่ยวกับการเข้ารหัสและการถอดรหัสการบีบอัด RLE ด้วยตนเอง
RasError('ไม่รองรับรูปแบบการบีบอัด!');
จบ;
RT_FORMAT_RGB:
เริ่ม
สำหรับ Y := 0 ถึงความสูง -1 ทำ
เริ่ม
Row32:=สแกนไลน์[Y];
ReadBuffer(แถว32^,กว้าง*4);
สำหรับฉัน := 0 ถึงความกว้าง -1 ทำ
เริ่ม
ColorByte := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbReserved;
Row32^.rgbReserved := ColorByte;
ColorByte := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc(แถว32);
จบ;
//รหัสสำหรับการปรับคำสั่งซื้อและการแลกเปลี่ยนค่า R และ B ถูกรวมเข้าด้วยกันที่นี่
จบ;
สิ้นสุด;{สิ้นสุด RT_FORMAT_RGB}
อื่น
RasError('รูปแบบไฟล์ที่ไม่รองรับ!');
สิ้นสุด; {สิ้นสุดของ 32Bit}
จบ;
อื่น
เริ่ม
ฟรีอิมเมจ;
RasError('รูปแบบไฟล์ที่ไม่รองรับ!');
จบ;
จบ;
จบ
อื่น
RasError('รูปแบบไฟล์ที่ไม่รองรับ!');
สิ้นสุด; {ลงท้ายด้วย}
จบ;
{รหัสต่อไปนี้ปรากฏหลายครั้งในรหัสด้านบน:
ถ้า (ความกว้าง mod 2)=1 แล้ว
เริ่ม
ตำแหน่ง := ตำแหน่ง + 1;
จบ;
เนื่องจากข้อมูลในแต่ละแถวจะต้องจัดแนวคำ กล่าวคือ ข้อมูลในแต่ละแถวจะต้องบันทึกด้วยจำนวนไบต์คู่ เมื่อข้อมูลสีของแต่ละพิกเซลถูกบันทึกเป็น 1 ไบต์ (8 บิต) หรือ 3 ไบต์ (24 บิต) และจำนวนพิกเซลในแต่ละแถวเป็นเลขคี่ จะต้องเติมหนึ่งไบต์ ดังนั้นหนึ่งไบต์จึงถูกข้ามไปที่นี่
ในโค้ดด้านหลัง
ถ้า (ความกว้าง mod 2) = 1 แล้ว
เริ่ม
เติมไบต์:=0;
สตรีม.เขียน(FillByte,1);
จบ;
มันก็อยู่บนหลักการเดียวกันเช่นกัน -
ขั้นตอน TRASGraphic.SaveToStream (สตรีม: TStream);
var
ส่วนหัว: TRASHeader;
แถว8: Pไบต์;
แถวที่ 24: PRGBTriple;
แถว32: PRGBQuad;
FillByte: ไบต์;
Y: จำนวนเต็ม;
ฉัน: จำนวนเต็ม;
เพื่อน: TMaxLogPalette;
R,G,B:อาร์เรย์[0..255] ของไบต์;
เริ่ม
Header.Magic := $956AA659;
Header.Width := SwapLong(กว้าง);
Header.Height := SwapLong(ความสูง);
Header.RasType := SwapLong(RT_STANDARD);
ถ้า (PixelFormat = pf1bit) หรือ (PixelFormat = pf4bit) แล้ว
รูปแบบพิกเซล:=pf8bit
อย่างอื่นถ้า (PixelFormat <> pf8bit) และ (PixelFormat <> pf24bit) และ (PixelFormat <> pf32bit) แล้ว
รูปแบบพิกเซล:=pf24bit;
กรณี PixelFormat ของ
pf8bit:
เริ่ม
Header.Length := SwapLong(ความสูง*(ความกว้าง+(ความกว้าง mod 2)));
Header.Depth := SwapLong(8);
Header.MapType := SwapLong(RMT_EQUAL_RGB);
Header.MapLength := SwapLong(3*256);
Stream.WriteBuffer (ส่วนหัว, SizeOf (ส่วนหัว));
GetPaletteEntries (จานสี, 0, 256, Pal.palPalEntry);
สำหรับฉัน := 0 ถึง 255 ทำ
เริ่ม
R[I]:=Pal.palPalEntry[I].peRed;
G[I]:=Pal.palPalEntry[I].peGreen;
B[I]:=Pal.palPalEntry[I].peBlue;
จบ;
//สำหรับการดำเนินการจานสีที่เกี่ยวข้องกับ API โปรดตรวจสอบ MSDN
Stream.WriteBuffer(R,256);
สตรีม WriteBuffer (G,256);
Stream.WriteBuffer(B,256);
สำหรับ Y := 0 ถึงความสูง-1 ทำ
เริ่ม
แถว8 := ScanLine[Y];
Stream.WriteBuffer(Row8^,ความกว้าง);
ถ้า (ความกว้าง mod 2) = 1 แล้ว
เริ่ม
เติมไบต์:=0;
สตรีม.เขียน(FillByte,1);
จบ;
จบ;
จบ;
pf32 บิต:
เริ่ม
Header.Length := SwapLong(ความสูง*กว้าง*4);
Header.Depth := SwapLong(32);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer (ส่วนหัว, SizeOf (ส่วนหัว));
สำหรับ Y := 0 ถึงความสูง-1 ทำ
เริ่ม
แถว32 := ScanLine[Y];
สำหรับฉัน := 0 ถึงความกว้าง -1 ทำ
เริ่ม
Stream.WriteBuffer (Row32.rgbReserved,1);
Stream.WriteBuffer(Row32^,3);
Inc(แถว32);
จบ;
จบ;
จบ;
อื่น
เริ่ม
Header.Length := SwapLong(ความสูง*กว้าง*3);
Header.Depth := SwapLong(24);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer (ส่วนหัว, SizeOf (ส่วนหัว));
สำหรับ Y := 0 ถึงความสูง -1 ทำ
เริ่ม
แถว24 := ScanLine[Y];
Stream.WriteBuffer(แถว24^,ความกว้าง*3);
ถ้า (ความกว้าง mod 2) = 1 แล้ว
เริ่ม
เติมไบต์:=0;
สตรีม.เขียน(FillByte,1);
จบ;
จบ;
จบ;
จบ;
//SaveToStream โดยพื้นฐานแล้วเป็นกระบวนการย้อนกลับของ LoadFromStream
จบ;
การเริ่มต้น
TPicture.RegisterFileFormat ('RAS', 'อาทิตย์ RAS', TRASGraphic);
การสรุป
TPicture.UnregisterGraphicClass(TRASGraphic);
ด้วยโค้ดไม่กี่บรรทัดนี้ องค์ประกอบการแยกวิเคราะห์รูปภาพที่สมบูรณ์จะเสร็จสมบูรณ์