Qt中已經有一些封裝好的對話框,比如QMessageBox、QColorDialog等,使用起來快捷方便,但缺點是我們無法為它們自定義樣式,所以可能難以“融入”我們的項目。既然如此,那就自己做一個把。抱着這樣的想法,我設計了一個顏色編輯選取對話框。
設計界面時,我參考了photoshop的拾色器、windows的畫圖軟件以及一個手繪控件軟件mockup。
最終完成的界面如下:
它包括以下一些功能:
- 選取預設的基本顏色
- 添加自定義顏色方便下次選取
- 從顏色拾取區域選擇顏色
- 預覽當前顏色和新選擇的顏色
- 查看和編輯調整顏色的hsv、rgb和16進制值
下面對一些重點難點作相關介紹:
一、色調、飽和度、亮度
從上圖可以看到,右邊的矩形是調整色調的區域,這部分亮度和飽和度都是100%,色調值由下往上遞增,范圍是0-360。
左邊的正方形區域是在某個色調值下,調整飽和度和亮度。亮度由下往上遞增,飽和度由左往右遞增。
色調(Hue)、飽和度(Saturation)、亮度(Brightness或Value)三者決定一個顏色(注:這里不考慮透明度),也就是我們所說的hsv。
同樣地,一個顏色可以用hsv表示,也可以用RGB來表示。
根據上面的原理,設計相關算法也就不難了。
1.色調
m_huePixmap = QPixmap(34, 270); m_huePixmap.fill(Qt::transparent); QPainter painter(&m_huePixmap); painter.setRenderHint(QPainter::Antialiasing); QLinearGradient hueGradient(0, m_iColorHeight, 0, 0); for (double i = 0; i < 1.0; i += 1.0 / 16) { hueGradient.setColorAt(i, QColor::fromHsvF(i, 1, 1, 1)); } hueGradient.setColorAt(1, QColor::fromHsvF(0, 1, 1, 1)); painter.setPen(Qt::NoPen); painter.setBrush(QBrush(hueGradient)); painter.drawRect(0, topMargin, m_iColorWidth, m_iColorHeight);
請原諒代碼中一些費解的變量和值。
在這里,我們使用的是QLinearGeadient漸變來畫一個pixmap,由下往上,飽和度百分值由0增加到1.0。
2.飽和度、亮度
一開始我的想法是這個問題能不能像色調一樣,也用漸變來解決,但是並沒有想到合適的辦法。
於是只能遍歷每個像素並設置顏色,一共遍歷了256 * 256次!
for (int i = 0; i <= 255; ++i) { uchar *colorUnit = m_svImg.scanLine(i); for (int j = 0; j <= 255; ++j) { color.setHsv(m_iHue, j, 255 - i); QRgb curRgb = color.rgb(); colorUnit[j * 4] = qBlue(curRgb); colorUnit[j * 4 + 1] = qGreen(curRgb); colorUnit[j * 4 + 2] = qRed(curRgb); colorUnit[j * 4 + 3] = 255; } }
QImage有一個函數setPixel,設置每一個像素點的Rgb值,但是該函數效率很低。根據qt給出的建議,使用scanLine()這個函數
一開始我用的是QPixmap,但發現並沒有設置像素顏色的函數,於是換成了QImage。
當時還找了別人寫的一些顏色編輯器demo,發現他們居然都用的setPixel,效率之低難以置信。於是硬着頭皮在Qt文檔中找答案,最終找到了scanLine,效率提高了不少。
但是遍歷256*256次總讓人感覺不那么自在!
~
~
~
直到有一天,靈光一現。在這個正方形區域中,同一垂直線上的色調值和飽和度值是一樣的,同一水平線上的色調值和亮度值是一樣的。那么從左往右是從白色#ffffff到當前色調區域所選顏色的漸變,由上往下可以是一個透明度從0到255的漸變。將二者結合起來,正好得到了我想要的。或許,這就是緣分吧:
//1 QLinearGradient svGradient(0, 0, m_iAreaWidth, 0); svGradient.setColorAt(1, newColor); svGradient.setColorAt(0, QColor("#ffffff")); //2 QLinearGradient vGradient(0, 0, 0, m_iAreaWidth); vGradient.setColorAt(0, QColor(0, 0, 0, 0)); vGradient.setColorAt(1, QColor(0, 0, 0, 255)); //最后將兩個Pixmap重合
通過“肉眼”的觀察,能明顯感覺到效率提高了很多很多!
二、邏輯問題
另一大難點就是邏輯問題,界面中各個區域有一個改變,就可能引起其它所有區域的改變。
比如:色調改變 - 飽和度亮度區域需要重繪 - 顏色預覽區域新的顏色改變 - 16進制文本編輯框和RGB、HSV的spinbox都會隨之改變
正所謂“牽一發而動全身”,更復雜的是,這些控件是相互影響的!
如果思考不周,將導致程序邏輯混亂、程序出現死循環或者多次循環影響效率等問題。
我在寫這個程序時主要用了3種方法避免混亂:
1.通過信號槽連接傳遞變化
2.調用其它類的公有函數
3.設置“flag”,通過判斷它的布爾值來決定是否調用函數。
第2和第3都是為了防止信號槽傳遞時可能導致程序的混亂。
三、關於QLineEdit的思考
左邊的lineEdit中展示的是顏色RGB值的16進制形式,范圍000000-ffffff,可以通過正則表達式對使用者的輸入進行限制。也可以加上inputMask,對格式進行更加嚴格的限制。然而限制太多反而會影響用戶體驗,需慎重考慮。
我的做法如下:
1.使用正則表達式控制輸入范圍。
2.手動編輯lineEdit中的內容時,光標位於lineEdit中,捕獲textEdited信號,改變顏色值。
3.鼠標點擊界面其它部分,使lineEdit失去焦點,然后捕獲它的編輯結束editingFinished信號,使其中的文本恢復6位的格式。
為了使鼠標點擊界面其它部分時輸入框失去焦點,需要設置主窗口的focus屬性:
setFocusPolicy(Qt::ClickFocus);
在設計這部分時,我也參考了photoshop的方法,可以打開ps感受一下哦。
源碼放在了github上,請原諒其中一些蹩腳的代碼,因為最初使用Qt4編寫的,后來在Qt5上運行也就沒怎么修改。