基於OpenCV的視頻組態 (1) :時鐘
寫在前面
本系列博客URL:
http://www.cnblogs.com/drgraph
http://blog.csdn.net/arwen
配套軟件下載地址:
http://www.czwenwu.com/YeeVingSetup.exe
配套軟件含三個可執行文件:YeeVingDriver.exe,YeeVingPlayer.exe,WatchDog.exe
其中,YeeVingDriver.exe是雙目觸控屏的驅動程序,內含鍵盤鼠標鉤子,安裝或運行的時候有可能會當成病毒。
WatchDog.exe是無人值守軟件
YeeVingPlayer.exe是廣告播放軟件客戶端。
本系列博客是在上述三個軟件研發過程中的片面記錄,基本上是屬於想到哪寫到哪的,不系統。主要目的是自己整理歸納一下,並期望與更多朋友交流。
QQ/微信:282397369
EMail: [email protected]
需求
時鐘的需求來自於廣告播放軟件客戶端。
櫥窗用戶反饋:在播放屏幕上,不僅僅是需要顯示視頻、圖片、動畫、文字、PPT等多媒體素材,還希望能看到一些小插件,看到比如時鐘、天氣預報等內容。當然,在界面定制的時候,指定一個小塊顯示相應內容就OK。
初步解決
先來處理時鐘。
既然先說了小插件,就先參考一下WINDOWS中的小工具。
當然,時鐘會有很多風格,先出一版這種效果的。
其實就是一個畫圖操作,不過想要改這個UI效果,如果用代碼來實現,好象還得要比較繁瑣的支持,在網上看到一種用圖片疊加起來的時鐘。
把幾個圖片素材下載下來,再重新命名一下。
最終就是采用Gdiplus技術,把這幾張圖片按順序畫出:
FGraphics->DrawImage(&image, x, y, w, h);
該轉的就轉一下。
Gdiplus::Bitmap * bitmap = RotateImage(&image, locked_theta, w, h);
float dx = x + w / 2 - bitmap->GetWidth() / 2;
float dy = y + h / 2 - bitmap->GetHeight() / 2;
FGraphics->DrawImage(bitmap, dx, dy, bitmap->GetWidth(), bitmap->GetHeight());
delete bitmap;
好象也沒有什麽好說的。可以拼成這種效果
模塊化
在實現的過程中,突然發現,這種由多張圖片疊加組成的場合還有一些,幹脆提煉成一個模塊,後續可以直接調用。
這裏面最主要的是配置,每張圖片都可以指定。最容易想到的解決方案就是采用XML方式來定義。
比如上面的時鐘,可以用XML定義如下:
<GdiMeta> <item angle="0" centerx="true" centery="true" height="1" picfilename="res\clock\background.png" width="1"/> <item angle="284" centerx="true" centery="true" height="1" name="hour" picfilename="res\clock\Hour.png" width="0"/> <item angle="176" centerx="true" centery="true" height="1" name="minute" picfilename="res\clock\Minute.png" width="0"/> <item angle="150" centerx="true" centery="true" height="1" name="second" picfilename="res\clock\Second.png" width="0"/> <item centerx="true" centery="true" height="1" picfilename="res\clock\Highlight.png" width="1"/> </GdiMeta>
既然是自己用,那就先定好格式。幾張圖片就用幾個節點。每個節點含以下屬性:
picfilename為圖片文件名,采用相對路徑,也可以是絕對路徑
angle為旋轉角度,指繞本圖像中心的旋轉角度
centerx、centery為橫向、縱向中心對齊標誌
height、width為高寬比例,指該圖形在最終圖形中所占的高寬比例。
name為本圖片對應名稱,後續可以根據該名稱進行相應值的修改。
思路理清了,模塊化也就容易了,頭文件
c
lass TCbwGdiMeta : public TRectangle { typedef TRectangle inherited; Gdiplus::Graphics * FGraphics; CbwXmlNode * FXmlContent; void __fastcall DrawXmlNode(CbwXmlNode * xmlNode, Gdiplus::RectF rf, Gdiplus::Graphics * FGraphics, Gdiplus::Brush * brush, Gdiplus::Pen * pen); UnicodeString __fastcall GetContent(); void __fastcall SetContent(UnicodeString value); void __fastcall SetXmlContent(CbwXmlNode * node); public: CBW_META_OBJECT(TCbwGdiMeta, TRectangle); virtual void __fastcall DoDraw(); // 畫出對象 virtual bool __fastcall PreDraw(); void __fastcall SetProperty(UnicodeString name, UnicodeString value); // 設置XML節點屬性,如 hour.angle = 100 virtual void __fastcall DoAddToXmlNode(CbwXmlNode * node); virtual void __fastcall DoGetFromXmlNode(CbwXmlNode * node, int& index); __published: __property UnicodeString Content = { read = GetContent, write = SetContent }; __property CbwXmlNode * XmlContent = { read = FXmlContent, write = SetXmlContent }; };
源文件
__fastcall TCbwGdiMeta::~TCbwGdiMeta() { delete FXmlContent; } void __fastcall TCbwGdiMeta::Initial() { ObjectClassType = cctGdiMeta; Name = THelper::FormatString("GdiMeta%d", MetaIndex[int(ObjectClassType)]); ++MetaIndex[int(ObjectClassType)]; FXmlContent = new CbwXmlNode("GdiMeta"); } bool __fastcall TCbwGdiMeta::PreDraw() { if (!inherited::PreDraw()) return false; return true; } void __fastcall TCbwGdiMeta::DoDraw() { FGraphics = GetGraphicsForRotate(Canvas); FGraphics->SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);//SmoothingModeHighQuality); TPoint p0 = TPoint(Points[0].x * Ratio, Points[0].y * Ratio); TPoint p1 = TPoint(Points[1].x * Ratio, Points[1].y * Ratio); TRect r = TTypeConvert::Points2Rect(p0, p1); Gdiplus::Brush * brush = BrushByData(BrushData, r); Gdiplus::RectF rf(r.left, r.top, r.Width(), r.Height()); Gdiplus::Pen gpen(Color2GPColor(PenData->Color), PenData->Width * Ratio); for(int i = 0; i < FXmlContent->ElementNumber; ++i) { CbwXmlNode * node = FXmlContent->Elements(i); DrawXmlNode(node, rf, FGraphics, brush, &gpen); } if(brush) delete brush; delete FGraphics; } void __fastcall TCbwGdiMeta::DrawXmlNode(CbwXmlNode * xmlNode, Gdiplus::RectF rf, Gdiplus::Graphics * FGraphics, Gdiplus::Brush * brush, Gdiplus::Pen * pen) { UnicodeString picName = xmlNode->AttributeValueByName("picFileName"); if(picName.Length()) { if(picName.Pos(L":") == 0) picName = THelper::File::GetApplicationPath() + picName; if(FileExists(picName)) { Gdiplus::Image image(picName.w_str(), false); float x = xmlNode->AttributeValueByName("x", "0").ToDouble(); float y = xmlNode->AttributeValueByName("y", "0").ToDouble(); float w = xmlNode->AttributeValueByName("width", "0").ToDouble(); float h = xmlNode->AttributeValueByName("height", "0").ToDouble(); double locked_theta = xmlNode->AttributeValueByName("angle", "0").ToDouble(); while(locked_theta >= 360) locked_theta -= 360; while(locked_theta < 0) locked_theta += 360; double width = rf.Width, height = rf.Height; x *= width; w *= width; y *= height; h *= height; if(w == 0) // 未定義寬度,則由原始寬度計算 w = image.GetWidth() * Ratio; if(h == 0) // 未定義高度,則由原始高度計算 h = image.GetHeight() * Ratio; if(xmlNode->BoolAttributeValueByName("centerx", "true")) x = (width - w) / 2; if(xmlNode->BoolAttributeValueByName("centery", "true")) y = (height - h) / 2; if(locked_theta) { Gdiplus::Bitmap * bitmap = RotateImage(&image, locked_theta, w, h); float dx = x + w / 2 - bitmap->GetWidth() / 2; float dy = y + h / 2 - bitmap->GetHeight() / 2; FGraphics->DrawImage(bitmap, dx, dy, bitmap->GetWidth(), bitmap->GetHeight()); delete bitmap; } else FGraphics->DrawImage(&image, x, y, w, h); } } } void __fastcall TCbwGdiMeta::SetProperty(UnicodeString pname, UnicodeString value) { UnicodeString name = THelper::String::GetStringAt(pname, ".", 0); UnicodeString attr = THelper::String::GetStringAt(pname, ".", 1); CbwXmlNode * destNode = FXmlContent->NodeByAttribute("name", name); if(destNode) destNode->AddAttribute(attr, value); } void __fastcall TCbwGdiMeta::DoAddToXmlNode(CbwXmlNode * node) { inherited::DoAddToXmlNode(node); CbwXmlNode * thisNode = node->LastNode; thisNode->AddElement(FXmlContent->Clone()); } void __fastcall TCbwGdiMeta::DoGetFromXmlNode(CbwXmlNode * node, int& index) { inherited::DoGetFromXmlNode(node, index); CbwXmlNode * thisNode = node->LastNode; FXmlContent->Assign(thisNode); } UnicodeString __fastcall TCbwGdiMeta::GetContent() { UnicodeString result = FXmlContent->GetHint("\n"); return result; } void __fastcall TCbwGdiMeta::SetContent(UnicodeString value) { FXmlContent->ReadFromString(value); } void __fastcall TCbwGdiMeta::SetXmlContent(CbwXmlNode * node) { if(node != FXmlContent) { UnicodeString content = node->GetHint("\n"); SetContent(content); } Draw(); }
剩下的就是實時更新時鐘,直接更新即可。
void __fastcall TCbwGraphForm_Player::UpdateTimeControls() { CBW_ITERATOR(CbwObjects, Objects) { TCbwGdiMeta * gdiMeta = dynamic_cast<TCbwGdiMeta *>(*it); if(gdiMeta && CanObjectBeVisible(gdiMeta)) { unsigned short Year, Month, Day, Hour, Minute, sec, msec; DecodeTime(Now(), Hour, Minute, sec, msec); double minute = Minute + sec / 60.0; double hour = (Hour + minute / 60.0) * 30; minute = minute * 6; sec *= 6; gdiMeta->SetProperty("Hour.angle", hour); gdiMeta->SetProperty("Minute.angle", minute); gdiMeta->SetProperty("Second.angle", sec); gdiMeta->Draw(); } } }
如此這般,在運行時就可以看到實時時鐘效果
屬性處理
為了更通用地處理,再實現一個兼容xml內容的屬性瀏覽器,主要需要增加兩個屬性類型:
頭文件:
class CbwXmlNodeAttributePropertyItem : public OrdinalPropertyItem { typedef OrdinalPropertyItem inherited; CbwXmlNode * FDestNode; virtual void __fastcall GetPropertyValue(); virtual void __fastcall SetValue(Variant var); public: __fastcall CbwXmlNodeAttributePropertyItem(TObject * object, UnicodeString propertyName, UnicodeString displayName = "", bool v = true); }; class CbwXmlNodePropertyItem : public ObjectPropertyItem { typedef ObjectPropertyItem inherited; CbwXmlNode * FDestNode; virtual void __fastcall AddItems(TList * item); virtual void __fastcall GetPropertyValue(); virtual void __fastcall SetStringList(TStringList * list); public: __fastcall CbwXmlNodePropertyItem(TObject * object, UnicodeString propertyName, UnicodeString displayName = "", bool v = true); };
源代碼:
__fastcall CbwXmlNodeAttributePropertyItem::CbwXmlNodeAttributePropertyItem (TObject * object, UnicodeString propertyName, UnicodeString displayName, bool v) : OrdinalPropertyItem(object, propertyName, displayName, v) { } void __fastcall CbwXmlNodeAttributePropertyItem::GetPropertyValue() { FDestNode = dynamic_cast<CbwXmlNode *>(Object); if(FDestNode) PropertyValue = FDestNode->AttributeValueByName(PropertyName); } void __fastcall CbwXmlNodeAttributePropertyItem::SetValue(Variant var) { if(FDestNode) { FDestNode->AddAttribute(PropertyName, var); CbwXmlNodePropertyItem * parent = dynamic_cast<CbwXmlNodePropertyItem *>(ParentItem); CbwXmlNode * node = FDestNode;//->ParentNode; while(parent && node) { if(IsPublishedProp(parent->Object, parent->PropertyName)) { SetObjectProp(parent->Object, parent->PropertyName, node); break; } parent = dynamic_cast<CbwXmlNodePropertyItem *>(parent->ParentItem); node = node->ParentNode; } } } void __fastcall CbwXmlNodePropertyItem::SetStringList(TStringList * list) { } void __fastcall CbwXmlNodePropertyItem::GetPropertyValue() { PropertyValue = PropertyName; } void __fastcall CbwXmlNodePropertyItem::AddItems(TList * item) { int count = item->Count; for (int i = 0; i < count; i++) { CbwPropertyItem * cItem = (CbwPropertyItem*)(item->Items[i]); delete cItem; } delete item; } __fastcall CbwXmlNodePropertyItem::CbwXmlNodePropertyItem (TObject * object, UnicodeString propertyName, UnicodeString displayName, bool v) : ObjectPropertyItem(object, propertyName, displayName, v) { FreeAllItem(); FDestNode = dynamic_cast<CbwXmlNode *>(object); if(!FDestNode) FDestNode = dynamic_cast<CbwXmlNode *>(GetObjectProp(object, propertyName)); if(FDestNode) { StringList->Clear(); int count = FDestNode->ElementNumber + FDestNode->Attributes.size(); if(count) { cItemList = new PPropertyItem[count]; int index = FDestNode->ElementNumber; CBW_ITERATOR_MAP(UnicodeString, UnicodeString, FDestNode->Attributes) cItemList[index++] = new CbwXmlNodeAttributePropertyItem(FDestNode, it->first); for(int i = 0; i < FDestNode->ElementNumber; ++i) { CbwXmlNode * node = FDestNode->Elements(i); UnicodeString name = node->Name; if(node->ContainAttribute("Name")) name = node->AttributeValueByName("Name"); cItemList[i] = new CbwXmlNodePropertyItem(node, name); } for(int i = 0; i < count; ++i) { StringList->Add("empty"); cItemList[i]->ParentItem = this; } } } PropertyValue = "XmlNode"; }
現在可以完全支持模擬時鐘。對於數字時鐘、日歷、天氣預報等,稍後再整理。
基於OpenCV的視頻組態 (1) :時鐘