Qt: menggambar berdasarkan grafik vektor

Qt menyediakan programmer dengan fitur yang sangat kaya, tetapi rangkaian widget terbatas. Jika tidak ada yang cocok, Anda harus menggambar sesuatu sendiri. Cara paling sederhana - untuk menggunakan gambar yang sudah jadi - memiliki kelemahan serius: kebutuhan untuk menyimpan gambar dalam file atau sumber daya, masalah dengan skalabilitas, dan portabilitas format gambar. Berikut ini menjelaskan penggunaan prinsip-prinsip grafik vektor tanpa menggunakan gambar vektor yang sebenarnya.


Pembukaan


Semuanya dimulai dengan fakta bahwa sekali indikasi tanda-tanda satu-bit diperlukan. Beberapa aplikasi menerima beberapa data pada beberapa port, paket harus dibongkar dan ditampilkan di layar. Akan menyenangkan pada saat yang sama untuk meniru dashboard yang sudah dikenalinya. Untuk menampilkan data digital, Qt menawarkan "out of the box" kelas QLCDN, mirip dengan indikator tujuh segmen yang sudah dikenal, tetapi ada sesuatu yang tidak terlihat dalam lampu tunggal.


Menggunakan bendera (itu kotak centang) dan sakelar (itu adalah tombol radio) untuk keperluan ini buruk, dan berikut adalah daftar alasannya:


  • Ini salah secara semantik. Tombol - itu adalah tombol, dan dimaksudkan untuk input pengguna, dan bukan untuk menunjukkan apa pun padanya.
  • Ini menyiratkan yang kedua: pengguna berusaha menyodok tombol-tombol tersebut. Jika pada saat yang sama memperbarui informasi tidak terlalu cepat, indikasinya akan berbohong, dan pengguna akan melaporkan kegagalan program, cekikikan dengan kejam.
  • Jika Anda mengunci tombol untuk menekan (setEnabled (false)), maka itu menjadi abu-abu jelek. Saya ingat bahwa di Delphi, di wilayah versi 6, ada tipuan dengan telinga: Anda bisa meletakkan bendera di panel dan menonaktifkan ketersediaan panel, bukan bendera, maka bendera tidak abu-abu atau aktif. Trik ini tidak berfungsi di sini.
  • Tombol-tombol memiliki fokus input. Dengan demikian, jika ada elemen input di jendela, dan pengguna berjalan di sepanjang mereka menggunakan tombol Tab, ia harus berjalan di sepanjang elemen output, yang tidak nyaman dan jelek.
  • Pada akhirnya, tombol-tombol ini hanya terlihat tidak estetis, terutama di sebelah tujuh segmen.

Kesimpulan: Anda perlu menggambar bola lampu sendiri.


Pilihan tepung


Pertama saya mencari solusi yang sudah jadi. Dalam waktu sejauh itu, ketika saya menggunakan Delphi, Anda dapat menemukan hanya sejumlah besar komponen jadi, baik dari perusahaan yang serius dan produsen amatir. Qt memiliki banyak masalah dengan ini. QWT memiliki beberapa elemen, tetapi bukan itu. Saya tidak melihat amatirisme sama sekali. Mungkin, jika Anda menggali dengan benar di Github, Anda dapat menemukan sesuatu, tetapi saya sendiri mungkin akan melakukannya lebih cepat.


Hal pertama yang disarankan sendiri dari buatan sendiri adalah menggunakan dua file gambar dengan gambar lampu menyala dan mati. Buruk:


  • Penting untuk menemukan gambar yang bagus (atau menggambar, tetapi saya bukan seorang seniman);
  • Pertanyaan prinsipnya adalah: mengikat itu tidak baik, bahkan gambar, bahkan berbaring di bawah kaki Anda;
  • Mereka harus disimpan di suatu tempat. File-file ini sangat buruk: tidak sengaja terhapus - dan tidak ada tombol. Sumber dayanya lebih baik, tetapi saya juga tidak merasa jika saya bisa bertahan;
  • Tidak ada skalabilitas;
  • Kemampuan penyesuaian (warna, misalnya) dicapai hanya dengan menambahkan file. Yaitu, sumber daya intensif dan tidak fleksibel.

Hal kedua yang mengikuti dari yang pertama adalah menggunakan gambar vektor, bukan gambar. Selain itu, Qt dapat membuat SVG. Di sini sudah sedikit lebih mudah dengan pencarian gambar itu sendiri: ada banyak pelajaran tentang grafik vektor dalam jaringan, Anda dapat menemukan sesuatu yang lebih atau kurang cocok dan menyesuaikannya dengan kebutuhan Anda. Tetapi pertanyaannya tetap penyimpanan dan kustomisasi, dan rendering tidak gratis untuk sumber daya. Uang, tentu saja, tetapi masih ...


Dan yang ketiga mengikuti dari yang kedua: Anda dapat menggunakan prinsip-prinsip grafik vektor untuk gambar gambar sendiri! File gambar vektor dalam bentuk teks menunjukkan apa dan bagaimana cara menggambar. Saya dapat menentukan kode yang sama menggunakan tutorial vektor. Untungnya, objek QPainter memiliki alat yang diperlukan: pena, kuas, gradien dan gambar primitif, bahkan isian tekstur. Ya, alat-alatnya jauh dari semua: tidak ada topeng, mode campuran, tapi sama sekali tidak diperlukan fotorealisme.


Saya mencari beberapa contoh di internet. Dia mengambil pelajaran pertama yang muncul: "Kami menggambar tombol di editor grafis Inkscape" dari situs "Sangat mudah untuk menggambar." Tombol dari pelajaran ini jauh lebih seperti bola lampu daripada tombol, yang sangat cocok untukku. Saya membuat konsep: alih-alih Inkscape, proyek di Qt.


Tes bulu


Saya membuat proyek baru. Saya memilih nama proyek rgbled (karena saya ingin melakukan sesuatu seperti LED RGB) dan jalur untuk itu. Saya memilih kelas dasar QWidget dan nama RgbLed, saya menolak untuk membuat file formulir. Proyek secara default setelah peluncuran membuat jendela kosong, itu masih tidak menarik.


Persiapan untuk menggambar


Ada yang kosong. Sekarang Anda perlu mendapatkan anggota pribadi dari kelas, yang akan menentukan geometri gambar. Keuntungan penting dari grafik vektor adalah skalabilitasnya, sehingga harus ada jumlah minimum yang konstan, dan mereka hanya mengatur proporsi. Dimensi akan dihitung ulang dalam acara resizeEvent (), yang perlu didefinisikan ulang.


Dalam tutorial menggambar yang digunakan, dimensi ditentukan dalam piksel saat Anda melanjutkan. Saya harus menentukan terlebih dahulu apa yang akan saya gunakan dan bagaimana cara menghitung ulang.


Gambar yang diambil terdiri dari elemen-elemen berikut:


  • cincin luar (condong ke luar, bagian tepi cembung)
  • cincin bagian dalam (miring ke dalam)
  • Rumah lampu LED, "kaca"
  • bayangan di tepi kaca
  • sorotan teratas
  • suar bawah

Lingkaran konsentris, yaitu segala sesuatu kecuali silau, ditentukan oleh posisi pusat dan jari-jari. Silau ditentukan oleh pusat, lebar dan tinggi, dan posisi pusat-pusat silau X bertepatan dengan posisi pusat X dari keseluruhan gambar.


Untuk menghitung elemen geometri, Anda perlu menentukan mana yang lebih besar - lebar atau tinggi, karena bohlam bulat dan harus masuk ke dalam persegi dengan sisi sama dengan yang lebih kecil dari dua dimensi. Jadi, saya menambahkan anggota pribadi yang sesuai ke file header.


kode
private: int height; int width; int minDim; int half; int centerX; int centerY; QRect drawingRect; int outerBorderWidth; int innerBorderWidth; int outerBorderRadius; int innerBorderRadius; int topReflexY; int bottomReflexY; int topReflexWidth; int topReflexHeight; int bottomReflexWidth; int bottomReflexHeight; 

Lalu saya mendefinisikan kembali fungsi yang dilindungi yang disebut ketika widget diubah ukurannya.


kode
 protected: void resizeEvent(QResizeEvent *event); void RgbLed::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); this->height = this->size().height(); this->width = this->size().width(); this->minDim = (height > width) ? width : height; this->half = minDim / 2; this->centerX = width / 2; this->centerY = height / 2; this->outerBorderWidth = minDim / 10; this->innerBorderWidth = minDim / 14; this->outerBorderRadius = half - outerBorderWidth; this->innerBorderRadius = half - (outerBorderWidth + innerBorderWidth); this->topReflexY = centerY - (half - outerBorderWidth - innerBorderWidth) / 2; this->bottomReflexY = centerY + (half - outerBorderWidth - innerBorderWidth) / 2; this->topReflexHeight = half / 5; this->topReflexWidth = half / 3; this->bottomReflexHeight = half / 5; this->bottomReflexWidth = half / 3; drawingRect.setTop((height - minDim) / 2); drawingRect.setLeft((width - minDim) / 2); drawingRect.setHeight(minDim); drawingRect.setWidth(minDim); } 

Di sini, sisi bujur sangkar yang menjadi tempat bohlam bertulis dihitung, bagian tengah bujur sangkar, jari-jari pelek yang menempati area semaksimal mungkin, lebar pelek, bagian luarnya harus 1/10 dari diameter, dan bagian dalam 1/14. Kemudian posisi silau, yang terletak di tengah jari-jari atas dan bawah, dihitung, lebar dan tinggi dipilih oleh mata.


Selain itu, di bidang yang dilindungi saya akan segera menambahkan satu set warna untuk digunakan.


kode
  QColor ledColor; QColor lightColor; QColor shadowColor; QColor ringShadowDarkColor; QColor ringShadowMedColor; QColor ringShadowLightColor; QColor topReflexUpColor; QColor topReflexDownColor; QColor bottomReflexCenterColor; QColor bottomReflexSideColor; 

Dengan namanya, kira-kira jelas bahwa ini adalah warna bola lampu, bagian terang bayangan, bagian gelap bayangan, tiga warna bayangan berbentuk lingkaran di sekitar bola lampu dan warna gradien silau.


Warna harus diinisialisasi, jadi saya akan melengkapi benda kerja desainer.


kode
 RgbLed::RgbLed(QWidget *parent) : QWidget(parent), ledColor(Qt::green), lightColor(QColor(0xE0, 0xE0, 0xE0)), shadowColor(QColor(0x70, 0x70, 0x70)), ringShadowDarkColor(QColor(0x50, 0x50, 0x50, 0xFF)), ringShadowMedColor(QColor(0x50, 0x50, 0x50, 0x20)), ringShadowLightColor(QColor(0xEE, 0xEE, 0xEE, 0x00)), topReflexUpColor(QColor(0xFF, 0xFF, 0xFF, 0xA0)), topReflexDownColor(QColor(0xFF, 0xFF, 0xFF, 0x00)), bottomReflexCenterColor(QColor(0xFF, 0xFF, 0xFF, 0x00)), bottomReflexSideColor(QColor(0xFF, 0xFF, 0xFF, 0x70)) { } 

Juga, jangan lupa untuk memasukkan dalam file header inklusi kelas yang akan dibutuhkan saat menggambar.


kode
 #include <QPainter> #include <QPen> #include <QBrush> #include <QColor> #include <QGradient> 

Kode ini berhasil dikompilasi, tetapi tidak ada yang berubah di jendela widget. Sudah waktunya untuk mulai menggambar.


Menggambar


Saya memasukkan fungsi tertutup


  void drawLed(const QColor &color); 

dan mendefinisikan kembali fungsi yang dilindungi


  void paintEvent(QPaintEvent *event); 

Peristiwa menggambar kembali akan menyebabkan gambar yang sebenarnya, yang warna "kaca" dilewatkan sebagai parameter.


kode
 void RgbLed::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); this->drawLed(ledColor); } 

Sejauh ini. Dan kita mulai secara bertahap mengisi fungsi menggambar.


kode
 void RgbLed::drawLed(const QColor &color) { QPainter p(this); QPen pen; pen.setStyle(Qt::NoPen); p.setPen(pen); } 

Pertama, objek artis dibuat, yang akan digunakan dalam menggambar. Kemudian pensil dibuat yang diperlukan sehingga tidak ada pensil: dalam gambar ini, garis besar stroke tidak hanya tidak diperlukan, tetapi tidak diperlukan sama sekali.


Kemudian lingkaran pertama digambar sesuai dengan pelajaran tentang grafik vektor: lingkaran besar, diisi dengan gradien radial. Gradien memiliki titik jangkar cahaya di bagian atas, tetapi tidak di bagian paling ujung, dan yang gelap di bagian bawah, tetapi juga tidak di bagian paling ujung. Kuas dibuat berdasarkan gradien, dengan pelukis kuas ini melukis lingkaran (yaitu, elips yang tertulis dalam kotak). Ternyata kode seperti itu


kode
  QRadialGradient outerRingGradient(QPoint(centerX, centerY - outerBorderRadius - (outerBorderWidth / 2)), minDim - (outerBorderWidth / 2)); outerRingGradient.setColorAt(0, lightColor); outerRingGradient.setColorAt(1, shadowColor); QBrush outerRingBrush(outerRingGradient); p.setBrush(outerRingBrush); p.drawEllipse(this->drawingRect); qDebug() << "draw"; 

Lingkungan menekankan parameter warna dari fungsi drawLed karena tidak digunakan. Biarkan dia bertoleransi, dia belum dibutuhkan, tetapi dia akan segera membutuhkannya. Proyek yang diluncurkan menghasilkan hasil sebagai berikut:


menggambar


Tambahkan kumpulan kode lain.


kode
  QRadialGradient innerRingGradient(QPoint(centerX, centerY + innerBorderRadius + (innerBorderWidth / 2)), minDim - (innerBorderWidth / 2)); innerRingGradient.setColorAt(0, lightColor); innerRingGradient.setColorAt(1, shadowColor); QBrush innerRingBrush(innerRingGradient); p.setBrush(innerRingBrush); p.drawEllipse(QPoint(centerX, centerY), outerBorderRadius, outerBorderRadius); 

Lingkarannya hampir sama, hanya berukuran lebih kecil dan terbalik. Kami mendapatkan gambar berikut:


menggambar


Maka akhirnya Anda membutuhkan warna kaca:


kode
  QColor dark(color.darker(120)); QRadialGradient glassGradient(QPoint(centerX, centerY), innerBorderRadius); glassGradient.setColorAt(0, color); glassGradient.setColorAt(1, dark); QBrush glassBrush(glassGradient); p.setBrush(glassBrush); p.drawEllipse(QPoint(centerX, centerY), innerBorderRadius, innerBorderRadius); 

Di sini, menggunakan fungsi yang lebih gelap dari warna yang ditransmisikan, warna yang sama diperoleh, tetapi lebih gelap, untuk mengatur gradien. Koefisien 120 dipilih oleh mata. Inilah hasilnya:


menggambar


Tambahkan bayangan annular di sekitar kaca. Ini dilakukan dalam pelajaran tentang grafik vektor, dan ini harus menambah volume dan realisme:


kode
  QRadialGradient shadowGradient(QPoint(centerX, centerY), innerBorderRadius); shadowGradient.setColorAt(0, ringShadowLightColor); shadowGradient.setColorAt(0.85, ringShadowMedColor); shadowGradient.setColorAt(1, ringShadowDarkColor); QBrush shadowBrush(shadowGradient); p.setBrush(shadowBrush); p.drawEllipse(QPoint(centerX, centerY), innerBorderRadius, innerBorderRadius); 

Ada gradien tiga langkah, sehingga bayangan lebih tebal ke tepi dan berubah pucat menuju pusat. Ternyata seperti ini:


menggambar


Tambahkan highlight, keduanya sekaligus. Sorotan atas, tidak seperti yang lebih rendah (dan semua elemen lainnya), dibuat gradien linier. Artis saya begitu-begitu, saya mengambil kata saya untuk penulis pelajaran. Mungkin ada kebenarannya, saya tidak akan bereksperimen dengan berbagai jenis gradien.


kode
  QLinearGradient topTeflexGradient(QPoint(centerX, (innerBorderWidth + outerBorderWidth)), QPoint(centerX, centerY)); topTeflexGradient.setColorAt(0, topReflexUpColor); topTeflexGradient.setColorAt(1, topReflexDownColor); QBrush topReflexbrush(topTeflexGradient); p.setBrush(topReflexbrush); p.drawEllipse(QPoint(centerX, topReflexY), topReflexWidth, topReflexHeight); QRadialGradient bottomReflexGradient(QPoint(centerX, bottomReflexY + (bottomReflexHeight / 2)), bottomReflexWidth); bottomReflexGradient.setColorAt(0, bottomReflexSideColor); bottomReflexGradient.setColorAt(1, bottomReflexCenterColor); QBrush bottomReflexBrush(bottomReflexGradient); p.setBrush(bottomReflexBrush); p.drawEllipse(QPoint(centerX, bottomReflexY), bottomReflexWidth, bottomReflexHeight); 

Faktanya, itu semua, bola lampu yang sudah jadi, seperti pada KDPV.


menggambar


Visibilitas silau dan tonjolan kaca dipengaruhi oleh warna, atau lebih tepatnya, seberapa gelap itu. Mungkin masuk akal untuk menambahkan penyesuaian untuk kecerahan silau dan koefisien peredupan dalam fungsi yang lebih gelap tergantung pada kegelapan, tapi ini perfeksionisme, saya pikir.


Di bawah ini adalah contoh penggunaan di jendela program.


menggambar


Memanjakan


Untuk bersenang-senang, Anda bisa bermain-main dengan bunga. Misalnya, mengganti acara klik mouse yang dilindungi


  void mousePressEvent(QMouseEvent *event); 

dengan cara ini:


kode
 void RgbLed::mousePressEvent(QMouseEvent *event) { static int count = 0; if (event->button() == Qt::LeftButton) { switch (count) { case 0: ledColor = Qt::red; count++; break; case 1: ledColor = Qt::green; count++; break; case 2: ledColor = Qt::blue; count++; break; case 3: ledColor = Qt::gray; count++; break; default: ledColor = QColor(220, 30, 200); count = 0; break; } this->repaint(); } QWidget::mousePressEvent(event); } 

tidak lupa menambahkan acara mouse ke header:


 #include <QMouseEvent> 

Sekarang klik mouse pada komponen akan mengubah warna bola lampu: merah, hijau, biru, abu-abu dan beberapa cahaya acak dari lampu.


Epilog


Sedangkan untuk menggambar, itu saja. Dan widget harus menambahkan fungsionalitas. Dalam kasus saya, bidang Boolean "status penggunaan" ditambahkan, bidang Boolean lain yang mendefinisikan status "Aktif" atau "Tidak Aktif" dan warna default untuk status ini, serta getter terbuka dan setter untuk semua ini. Kolom ini digunakan dalam fungsi paintEvent () untuk memilih warna yang diteruskan ke drawLed () sebagai parameter. Sebagai akibatnya, Anda dapat mematikan penggunaan status dan mengatur bohlam ke warna apa pun, atau menghidupkan status dan menghidupkan atau mematikan bohlam berdasarkan peristiwa. Terutama nyaman untuk membuat setter keadaan terbuka dan slot siam baik itu dengan sinyal yang harus dipantau.


Menggunakan mousePressEvent menunjukkan bahwa widget dapat dibuat tidak hanya sebagai indikator, tetapi juga sebuah tombol, yang membuatnya ditekan, dilepaskan, dibengkokkan, dipelintir, diwarnai dan apa pun yang Anda inginkan untuk acara menunjuk, mengklik, dan melepaskan.


Tetapi ini tidak lagi mendasar. Tujuannya adalah untuk menunjukkan di mana Anda dapat mengambil model peran ketika menggambar widget Anda sendiri dan bagaimana rendering ini mudah diterapkan tanpa menggunakan gambar raster atau vektor, dalam sumber daya atau file.

Source: https://habr.com/ru/post/id425547/


All Articles