added filter list page to layer properties

This commit is contained in:
Pavel Yosifovich
2025-01-25 15:34:31 +02:00
parent 1ef1aee120
commit 04fc34e723
14 changed files with 197 additions and 43 deletions
+85
View File
@@ -0,0 +1,85 @@
#include "pch.h"
#include "FiltersListPage.h"
#include <WFPEngine.h>
#include "StringHelper.h"
#include <SortHelper.h>
CFiltersListPage::CFiltersListPage(WFPEngine& engine, FWPM_LAYER* layer) : m_Engine(engine), m_Layer(layer) {
WFPFilterEnumerator enumerator(engine.Handle());
DWORD count = 0;
DWORD maxCount = 8000;
do {
m_Filters.Append(enumerator.NextFiltered<FilterInfo>([&](auto& filter) { return filter->layerKey == layer->layerKey; }, maxCount));
count += maxCount;
} while (m_Filters.size() == count);
}
size_t CFiltersListPage::GetFilterCount() const {
return m_Filters.size();
}
CString CFiltersListPage::GetColumnText(HWND, int row, int col) const {
auto& f = m_Filters[row];
switch (col) {
case 0: return std::to_wstring(f.Index).c_str();
case 1: return f.Data->displayData.name;
case 2: return std::to_wstring(f.Data->numFilterConditions).c_str();
case 3: return std::format(L"0x{:X}", *f.Data->effectiveWeight.uint64).c_str();
case 4: return StringHelper::WFPFilterActionTypeToString(f.Data->action.type);
}
return CString();
}
void CFiltersListPage::DoSort(SortInfo const* si) {
auto sort = [&](auto& f1, auto& f2) {
switch (si->SortColumn) {
case 0: return SortHelper::Sort(f1.Index, f2.Index, si->SortAscending);
case 1: return SortHelper::Sort(f1.Data->displayData.name, f2.Data->displayData.name, si->SortAscending);
case 2: return SortHelper::Sort(f1.Data->numFilterConditions, f2.Data->numFilterConditions, si->SortAscending);
case 3: return SortHelper::Sort(*f1.Data->effectiveWeight.uint64, *f2.Data->effectiveWeight.uint64, si->SortAscending);
case 4: return SortHelper::Sort(StringHelper::WFPFilterActionTypeToString(f1.Data->action.type), StringHelper::WFPFilterActionTypeToString(f2.Data->action.type), si->SortAscending);
}
return false;
};
std::ranges::sort(m_Filters, sort);
}
int CFiltersListPage::GetRowImage(HWND, int row, int) const {
switch (m_Filters[row].Data->action.type) {
case FWP_ACTION_BLOCK: return 2;
case FWP_ACTION_PERMIT: return 1;
case FWP_ACTION_CONTINUE: return 3;
}
return 4;
}
LRESULT CFiltersListPage::OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) {
InitDynamicLayout(false);
m_List.Attach(GetDlgItem(IDC_LIST));
m_List.SetExtendedListViewStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT);
CImageList images;
images.Create(16, 16, ILC_COLOR32 | ILC_MASK, 6, 4);
UINT icons[] = { IDI_FILTER, IDI_FILTER_PERMIT, IDI_FILTER_BLOCK, IDI_FILTER_REFRESH, IDI_CALLOUT };
for (auto icon : icons)
images.AddIcon(AtlLoadIconImage(icon, 0, 16, 16));
m_List.SetImageList(images, LVSIL_SMALL);
auto cm = GetColumnManager(m_List);
cm->AddColumn(L"", 0, 0);
cm->AddColumn(L"#", LVCFMT_RIGHT, 50);
cm->AddColumn(L"Name", 0, 240);
cm->AddColumn(L"Conditions", LVCFMT_RIGHT, 60);
cm->AddColumn(L"Weight", LVCFMT_RIGHT, 130);
cm->AddColumn(L"Action", 0, 110);
cm->DeleteColumn(0);
int i = 1;
for (auto& f : m_Filters)
f.Index = i++;
m_List.SetItemCount((int)m_Filters.size());
return 0;
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <DialogHelper.h>
#include "resource.h"
#include <VirtualListView.h>
#include <WFPEnumerators.h>
class WFPEngine;
class CFiltersListPage :
public CPropertyPageImpl<CFiltersListPage>,
public CVirtualListView<CFiltersListPage>,
public CDynamicDialogLayout<CFiltersListPage>,
public CDialogHelper<CFiltersListPage> {
public:
enum { IDD = IDD_LAYERFIELDS };
CFiltersListPage(WFPEngine& engine, FWPM_LAYER* layer);
size_t GetFilterCount() const;
CString GetColumnText(HWND, int row, int col) const;
void DoSort(SortInfo const* si);
int GetRowImage(HWND, int row, int) const;
BEGIN_MSG_MAP(CFiltersListPage)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
CHAIN_MSG_MAP(CDynamicDialogLayout<CFiltersListPage>)
CHAIN_MSG_MAP(CVirtualListView<CFiltersListPage>)
END_MSG_MAP()
private:
struct FilterInfo {
FWPM_FILTER* Data;
int Index;
};
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
private:
WFPEngine& m_Engine;
FWPM_LAYER* m_Layer;
WFPObjectVector<FWPM_FILTER, FilterInfo> m_Filters;
CListViewCtrl m_List;
};
+39 -36
View File
@@ -30,7 +30,7 @@ BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) {
if (CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))
return TRUE;
return m_view.PreTranslateMessage(pMsg);
return m_Tabs.PreTranslateMessage(pMsg);
}
BOOL CMainFrame::OnIdle() {
@@ -77,7 +77,7 @@ LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/
UIAddToolBar(tb);
//m_view.m_bTabCloseButton = false;
m_hWndClient = m_view.Create(m_hWnd, rcDefault, nullptr,
m_hWndClient = m_Tabs.Create(m_hWnd, rcDefault, nullptr,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
CImageList images;
images.Create(16, 16, ILC_COLOR32, 8, 4);
@@ -87,17 +87,17 @@ LRESULT CMainFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/
};
for (auto icon : icons)
images.AddIcon(AtlLoadIconImage(icon, 0, 16, 16));
m_view.SetImageList(images);
m_Tabs.SetImageList(images);
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
CMenuHandle menuMain = GetMenu();
m_view.SetWindowMenu(menuMain.GetSubMenu(WINDOW_MENU_POSITION));
m_Tabs.SetWindowMenu(menuMain.GetSubMenu(WINDOW_MENU_POSITION));
LOGFONT lf;
((CFontHandle)m_view.GetFont()).GetLogFont(lf);
((CFontHandle)m_Tabs.GetFont()).GetLogFont(lf);
wcscpy_s(lf.lfFaceName, L"Consolas");
m_MonoFont.CreateFontIndirect(&lf);
@@ -169,6 +169,9 @@ void CMainFrame::InitMenu() {
{ ID_FILE_SAVE, IDI_SAVE },
{ ID_EDIT_FIND, IDI_FIND },
{ ID_EDIT_FINDNEXT, IDI_FIND_NEXT },
{ ID_NEW_FILTER, IDI_FILTER },
{ ID_NEW_PROVIDER, IDI_PROVIDER },
{ ID_NEW_SUBLAYER, IDI_SUBLAYER },
};
for (auto& cmd : cmds) {
if (cmd.icon)
@@ -179,7 +182,7 @@ void CMainFrame::InitMenu() {
}
void CMainFrame::UpdateUI() {
bool anyPage = m_view.GetPageCount() > 0;
bool anyPage = m_Tabs.GetPageCount() > 0;
UIEnable(ID_FILE_SAVE, anyPage);
UIEnable(ID_EDIT_COPY, false);
UIEnable(ID_EDIT_CUT, false);
@@ -224,9 +227,9 @@ LRESULT CMainFrame::OnShowWindow(UINT, WPARAM, LPARAM, BOOL&) {
}
LRESULT CMainFrame::OnRebuildWindowMenu(UINT, WPARAM, LPARAM, BOOL& bHandled) {
auto il = CImageList(m_view.GetImageList());
for (int i = 0; i < m_view.GetPageCount(); i++) {
auto image = m_view.GetPageImage(i);
auto il = CImageList(m_Tabs.GetImageList());
for (int i = 0; i < m_Tabs.GetPageCount(); i++) {
auto image = m_Tabs.GetPageImage(i);
AddCommand(ID_WINDOW_TABFIRST + i, il.GetIcon(image));
}
AddSubMenu(GetSubMenu(GetMenu(), WINDOW_MENU_POSITION));
@@ -241,16 +244,16 @@ LRESULT CMainFrame::OnFileExit(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCt
LRESULT CMainFrame::OnViewSessions(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
auto view = new CSessionsView(this, m_Engine);
view->Create(m_view, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_view.AddPage(view->m_hWnd, L"Sessions", 0, view);
view->Create(m_Tabs, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_Tabs.AddPage(view->m_hWnd, L"Sessions", 0, view);
return 0;
}
LRESULT CMainFrame::OnViewFilters(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
auto view = new CFiltersView(this, m_Engine);
view->Create(m_view, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_view.AddPage(view->m_hWnd, L"Filters", 1, view);
view->Create(m_Tabs, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_Tabs.AddPage(view->m_hWnd, L"Filters", 1, view);
view->Refresh();
return 0;
@@ -258,56 +261,56 @@ LRESULT CMainFrame::OnViewFilters(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWn
LRESULT CMainFrame::OnViewProviders(WORD, WORD, HWND, BOOL&) {
auto view = new CProvidersView(this, m_Engine);
view->Create(m_view, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_view.AddPage(view->m_hWnd, L"Providers", 2, view);
view->Create(m_Tabs, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_Tabs.AddPage(view->m_hWnd, L"Providers", 2, view);
return 0;
}
LRESULT CMainFrame::OnViewProviderContexts(WORD, WORD, HWND, BOOL&) {
auto view = new CProviderContextView(this, m_Engine);
view->Create(m_view, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_view.AddPage(view->m_hWnd, L"Provider Contexts", 6, view);
view->Create(m_Tabs, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_Tabs.AddPage(view->m_hWnd, L"Provider Contexts", 6, view);
return 0;
}
LRESULT CMainFrame::OnViewLayers(WORD, WORD, HWND, BOOL&) {
auto view = new CLayersView(this, m_Engine);
view->Create(m_view, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_view.AddPage(view->m_hWnd, L"Layers", 3, view);
view->Create(m_Tabs, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_Tabs.AddPage(view->m_hWnd, L"Layers", 3, view);
return 0;
}
LRESULT CMainFrame::OnViewNetEvents(WORD, WORD, HWND, BOOL&) {
auto view = new CNetEventsView(this, m_Engine);
view->Create(m_view, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_view.AddPage(view->m_hWnd, L"Net Events", 8, view);
view->Create(m_Tabs, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_Tabs.AddPage(view->m_hWnd, L"Net Events", 8, view);
return 0;
}
LRESULT CMainFrame::OnViewHierarchy(WORD, WORD, HWND, BOOL&) {
auto view = new CHierarchyView(this, m_Engine);
view->Create(m_view, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_view.AddPage(view->m_hWnd, L"Hierarchy", 7, view);
view->Create(m_Tabs, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_Tabs.AddPage(view->m_hWnd, L"Hierarchy", 7, view);
return 0;
}
LRESULT CMainFrame::OnViewSublayers(WORD, WORD, HWND, BOOL&) {
auto view = new CSublayersView(this, m_Engine);
view->Create(m_view, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_view.AddPage(view->m_hWnd, L"Sublayers", 4, view);
view->Create(m_Tabs, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_Tabs.AddPage(view->m_hWnd, L"Sublayers", 4, view);
return 0;
}
LRESULT CMainFrame::OnViewCallouts(WORD, WORD, HWND, BOOL&) {
auto view = new CCalloutsView(this, m_Engine);
view->Create(m_view, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_view.AddPage(view->m_hWnd, L"Callouts", 5, view);
view->Create(m_Tabs, rcDefault, nullptr, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0);
m_Tabs.AddPage(view->m_hWnd, L"Callouts", 5, view);
view->Refresh();
return 0;
@@ -328,9 +331,9 @@ LRESULT CMainFrame::OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCt
}
LRESULT CMainFrame::OnWindowClose(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
int nActivePage = m_view.GetActivePage();
int nActivePage = m_Tabs.GetActivePage();
if (nActivePage != -1)
m_view.RemovePage(nActivePage);
m_Tabs.RemovePage(nActivePage);
else
::MessageBeep((UINT)-1);
@@ -338,23 +341,23 @@ LRESULT CMainFrame::OnWindowClose(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWn
}
LRESULT CMainFrame::OnWindowCloseAll(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
m_view.RemoveAllPages();
m_Tabs.RemoveAllPages();
return 0;
}
LRESULT CMainFrame::OnWindowActivate(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/) {
int nPage = wID - ID_WINDOW_TABFIRST;
m_view.SetActivePage(nPage);
m_Tabs.SetActivePage(nPage);
return 0;
}
LRESULT CMainFrame::OnPageActivated(int, LPNMHDR, BOOL&) {
int page = m_view.GetActivePage();
int page = m_Tabs.GetActivePage();
bool handled = false;
if (page >= 0) {
handled = ::SendMessage(m_view.GetPageHWND(page), WM_ACTIVATE, 1, 0);
handled = ::SendMessage(m_Tabs.GetPageHWND(page), WM_ACTIVATE, 1, 0);
}
if (!handled) {
UpdateUI();
@@ -370,7 +373,7 @@ LRESULT CMainFrame::OnAlwaysOnTop(WORD, WORD, HWND, BOOL&) {
}
LRESULT CMainFrame::OnEditFind(WORD, WORD, HWND, BOOL&) {
ATLASSERT(m_view.GetPageCount() > 0);
ATLASSERT(m_Tabs.GetPageCount() > 0);
if (m_pFindDlg == nullptr) {
m_pFindDlg = new CFindReplaceDialog;
@@ -388,9 +391,9 @@ LRESULT CMainFrame::OnFind(UINT msg, WPARAM wp, LPARAM lp, BOOL&) {
}
m_pFindDlg->SetFocus();
if (auto page = m_view.GetActivePage(); page >= 0) {
if (auto page = m_Tabs.GetActivePage(); page >= 0) {
m_SearchText = m_pFindDlg->GetFindString();
::SendMessage(m_view.GetPageHWND(page), msg, wp, lp);
::SendMessage(m_Tabs.GetPageHWND(page), msg, wp, lp);
}
return 0;
}
+2 -2
View File
@@ -20,7 +20,7 @@ public:
virtual BOOL OnIdle();
BEGIN_MSG_MAP(CMainFrame)
COMMAND_TABVIEW_HANDLER(m_view, 1)
COMMAND_TABVIEW_HANDLER(m_Tabs, 1)
COMMAND_ID_HANDLER(ID_VIEW_FILTERS, OnViewFilters)
COMMAND_ID_HANDLER(ID_VIEW_SESSIONS, OnViewSessions)
COMMAND_ID_HANDLER(ID_VIEW_PROVIDERS, OnViewProviders)
@@ -103,7 +103,7 @@ private:
WFPEngine m_Engine;
CFont m_MonoFont;
CCustomTabView m_view;
CCustomTabView m_Tabs;
CMultiPaneStatusBarCtrl m_StatusBar;
CFindReplaceDialog* m_pFindDlg{ nullptr };
CString m_SearchText;
+1 -1
View File
@@ -611,7 +611,7 @@ BEGIN
VALUE "FileDescription", "Windows Filtering Platform Explorer"
VALUE "FileVersion", "0.5.7.0"
VALUE "InternalName", "WFPExplorer"
VALUE "LegalCopyright", "©2022-2024 Pavel Yosifovich"
VALUE "LegalCopyright", "©2022-2025 Pavel Yosifovich"
VALUE "OriginalFilename", "WFPExplorer.exe"
VALUE "ProductName", "WFP Explorer"
VALUE "ProductVersion", "0.5.7.0"
+2
View File
@@ -364,6 +364,7 @@
<ClCompile Include="CalloutsView.cpp" />
<ClCompile Include="FilterConditionsPage.cpp" />
<ClCompile Include="FilterGeneralPage.cpp" />
<ClCompile Include="FiltersListPage.cpp" />
<ClCompile Include="FiltersView.cpp" />
<ClCompile Include="HierarchyView.cpp" />
<ClCompile Include="LayerFieldsPage.cpp" />
@@ -399,6 +400,7 @@
<ClInclude Include="CalloutsView.h" />
<ClInclude Include="FilterConditionsPage.h" />
<ClInclude Include="FilterGeneralPage.h" />
<ClInclude Include="FiltersListPage.h" />
<ClInclude Include="FiltersView.h" />
<ClInclude Include="GenericListViewBase.h" />
<ClInclude Include="HierarchyView.h" />
+6
View File
@@ -108,6 +108,9 @@
<ClCompile Include="NewSubLayerDlg.cpp">
<Filter>Dialogs</Filter>
</ClCompile>
<ClCompile Include="FiltersListPage.cpp">
<Filter>PropertyPages</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
@@ -194,6 +197,9 @@
<ClInclude Include="NewSubLayerDlg.h">
<Filter>Dialogs</Filter>
</ClInclude>
<ClInclude Include="FiltersListPage.h">
<Filter>PropertyPages</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="WFPExplorer.rc">
+13 -4
View File
@@ -11,6 +11,8 @@
#include "ProviderDlg.h"
#include "CalloutDlg.h"
#include "SubLayerDlg.h"
#include "WFPEnumerators.h"
#include "FiltersListPage.h"
CString WFPHelper::GetProviderName(WFPEngine const& engine, GUID const& key) {
auto provider = engine.GetProviderByKey(key);
@@ -55,19 +57,26 @@ CString WFPHelper::GetSublayerName(WFPEngine const& engine, GUID const& key) {
int WFPHelper::ShowLayerProperties(WFPEngine& engine, FWPM_LAYER* layer) {
auto name = L"Layer Properties (" + GetLayerName(engine, layer->layerKey) + L")";
CPropertySheet sheet((PCWSTR)name);
sheet.m_psh.dwFlags |= PSH_NOAPPLYNOW | PSH_USEICONID | PSH_NOCONTEXTHELP | PSH_RESIZABLE;
sheet.m_psh.dwFlags |= PSH_NOAPPLYNOW | PSH_USEICONID | PSH_NOCONTEXTHELP;
sheet.m_psh.pszIcon = MAKEINTRESOURCE(IDI_LAYERS);
CLayerGeneralPage general(engine, layer);
general.m_psp.dwFlags |= PSP_USEICONID;
general.m_psp.pszIcon = MAKEINTRESOURCE(IDI_CUBE);
general.m_psp.dwFlags |= PSP_USEHICON;
general.m_psp.hIcon = AtlLoadIconImage(IDI_CUBE, LR_LOADTRANSPARENT | LR_CREATEDIBSECTION, 16, 16);
sheet.AddPage(general);
CLayerFieldsPage fields(engine, layer);
if (layer->numFields > 0) {
fields.m_psp.dwFlags |= PSP_USEICONID;
fields.m_psp.pszIcon = MAKEINTRESOURCE(IDI_FIELD);
sheet.AddPage(fields);
}
CFiltersListPage filterPage(engine, layer);
if (filterPage.GetFilterCount()) {
filterPage.SetTitle(L"Filters");
filterPage.m_psp.dwFlags |= PSP_USEICONID;
filterPage.m_psp.pszIcon = MAKEINTRESOURCE(IDI_FILTER);
sheet.AddPage(filterPage);
}
return (int)sheet.DoModal();
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 19 KiB