1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2011 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
5  * Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
23  */
24 
25 
26 #include <wx/image.h>
27 #include <wx/bitmap.h>
28 #include <wx/gdicmn.h>
29 #include <wx/mstream.h>
30 #include <wx/menu.h>
31 #include <wx/menuitem.h>
32 #include <wx/aui/auibar.h>
33 #include <wx/dcclient.h>
34 #include <wx/dcmemory.h>
35 
36 #include <cstdint>
37 #include <mutex>
38 #include <unordered_map>
39 
40 #include <asset_archive.h>
41 #include <bitmaps.h>
42 #include <bitmap_store.h>
43 #include <bitmaps/bitmap_opaque.h> // for pcb_calculator compatibility shim
44 #include <pgm_base.h>
45 #include <eda_base_frame.h>
46 #include <eda_draw_frame.h>
47 #include <paths.h>
48 
49 
50 static std::unique_ptr<BITMAP_STORE> s_BitmapStore;
51 
52 
53 struct SCALED_BITMAP_ID {
54     BITMAPS bitmap;
55     int scale;
56 
operator ==SCALED_BITMAP_ID57     bool operator==( SCALED_BITMAP_ID const& other ) const noexcept
58     {
59         return bitmap == other.bitmap && scale == other.scale;
60     }
61 };
62 
63 
64 namespace std {
65     template<> struct hash<SCALED_BITMAP_ID>
66     {
67         typedef SCALED_BITMAP_ID argument_type;
68         typedef std::size_t result_type;
69 
operator ()std::hash70         result_type operator()( argument_type const& id ) const noexcept
71         {
72             static const bool sz64 = sizeof( uintptr_t ) == 8;
73             static const size_t mask = sz64 ? 0xF000000000000000uLL : 0xF0000000uL;
74             static const size_t offset = sz64 ? 60 : 28;
75 
76             // The hash only needs to be fast and simple, not necessarily accurate - a collision
77             // only makes things slower, not broken. BITMAPS is a pointer, so the most
78             // significant several bits are generally going to be the same for all. Just convert
79             // it to an integer and stuff the scale factor into those bits.
80             return
81                 ( (uintptr_t)( id.bitmap ) & ~mask ) |
82                 ( ( (uintptr_t)( id.scale ) & 0xF ) << offset );
83         }
84     };
85 }
86 
87 
88 static std::unordered_map<SCALED_BITMAP_ID, wxBitmap> s_ScaledBitmapCache;
89 
90 static std::mutex s_BitmapCacheMutex;
91 
92 
GetBitmapStore()93 BITMAP_STORE* GetBitmapStore()
94 {
95     if( !s_BitmapStore )
96     {
97         wxFileName path( PATHS::GetStockDataPath() + wxT( "/resources" ), wxT( "images.zip" ) );
98         s_BitmapStore = std::make_unique<BITMAP_STORE>();
99     }
100 
101     return s_BitmapStore.get();
102 }
103 
104 
KiBitmap(BITMAPS aBitmap,int aHeightTag)105 wxBitmap KiBitmap( BITMAPS aBitmap, int aHeightTag )
106 {
107     return GetBitmapStore()->GetBitmap( aBitmap, aHeightTag );
108 }
109 
110 
111 // TODO: Remove this once pcb_calculator images are moved into the main bitmap system
KiBitmap(const BITMAP_OPAQUE * aBitmap)112 wxBitmap KiBitmap( const BITMAP_OPAQUE* aBitmap )
113 {
114     wxMemoryInputStream is( aBitmap->png, aBitmap->byteCount );
115     wxImage image( is, wxBITMAP_TYPE_PNG );
116     wxBitmap bitmap( image );
117 
118     return bitmap;
119 }
120 
121 
KiIconScale(wxWindow * aWindow)122 int KiIconScale( wxWindow* aWindow )
123 {
124     const int vert_size = aWindow->ConvertDialogToPixels( wxSize( 0, 8 ) ).y;
125 
126     // Autoscale won't exceed unity until the system has quite high resolution,
127     // because we don't want the icons to look obviously scaled on a system
128     // where it's easy to see it.
129 
130     if( vert_size > 34 )        return 8;
131     else if( vert_size > 29 )   return 7;
132     else if( vert_size > 24 )   return 6;
133     else                        return 4;
134 }
135 
136 
get_scale_factor(wxWindow * aWindow)137 static int get_scale_factor( wxWindow* aWindow )
138 {
139     int requested_scale = Pgm().GetCommonSettings()->m_Appearance.icon_scale;
140 
141     if( requested_scale > 0 )
142         return requested_scale;
143     else
144         return KiIconScale( aWindow );
145 }
146 
147 
KiScaledBitmap(BITMAPS aBitmap,wxWindow * aWindow,int aHeight,bool aQuantized)148 wxBitmap KiScaledBitmap( BITMAPS aBitmap, wxWindow* aWindow, int aHeight, bool aQuantized )
149 {
150     // Bitmap conversions are cached because they can be slow.
151     int scale = get_scale_factor( aWindow );
152 
153     if( aQuantized )
154         scale = KiROUND( (double) scale / 4.0 ) * 4;
155 
156     SCALED_BITMAP_ID id = { static_cast<BITMAPS>( aBitmap ), scale };
157 
158     std::lock_guard<std::mutex> guard( s_BitmapCacheMutex );
159     auto it = s_ScaledBitmapCache.find( id );
160 
161     if( it != s_ScaledBitmapCache.end() )
162     {
163         return it->second;
164     }
165     else
166     {
167         wxBitmap bitmap = GetBitmapStore()->GetBitmapScaled( aBitmap, scale, aHeight );
168         return s_ScaledBitmapCache.emplace( id, bitmap ).first->second;
169     }
170 }
171 
172 
ClearScaledBitmapCache()173 void ClearScaledBitmapCache()
174 {
175     std::lock_guard<std::mutex> guard( s_BitmapCacheMutex );
176     s_ScaledBitmapCache.clear();
177 }
178 
179 
KiScaledBitmap(const wxBitmap & aBitmap,wxWindow * aWindow)180 wxBitmap KiScaledBitmap( const wxBitmap& aBitmap, wxWindow* aWindow )
181 {
182     const int scale = get_scale_factor( aWindow );
183 
184     if( scale == 4 )
185     {
186         return wxBitmap( aBitmap );
187     }
188     else
189     {
190         wxImage image = aBitmap.ConvertToImage();
191         image.Rescale( scale * image.GetWidth() / 4, scale * image.GetHeight() / 4,
192             wxIMAGE_QUALITY_BILINEAR );
193 
194         return wxBitmap( image );
195     }
196 }
197 
198 
KiBitmapNew(BITMAPS aBitmap)199 wxBitmap* KiBitmapNew( BITMAPS aBitmap )
200 {
201     wxBitmap* bitmap = new wxBitmap( GetBitmapStore()->GetBitmap( aBitmap ) );
202 
203     return bitmap;
204 }
205 
206 
SaveCanvasImageToFile(EDA_DRAW_FRAME * aFrame,const wxString & aFileName,BITMAP_TYPE aBitmapType)207 bool SaveCanvasImageToFile( EDA_DRAW_FRAME* aFrame, const wxString& aFileName,
208                             BITMAP_TYPE aBitmapType )
209 {
210     wxCHECK( aFrame != nullptr, false );
211 
212     bool       retv = true;
213 
214     // Make a screen copy of the canvas:
215     wxSize image_size = aFrame->GetCanvas()->GetClientSize();
216 
217     wxClientDC dc( aFrame->GetCanvas() );
218     wxBitmap   bitmap( image_size.x, image_size.y );
219     wxMemoryDC memdc;
220 
221     memdc.SelectObject( bitmap );
222     memdc.Blit( 0, 0, image_size.x, image_size.y, &dc, 0, 0 );
223     memdc.SelectObject( wxNullBitmap );
224 
225     wxImage image = bitmap.ConvertToImage();
226 
227     wxBitmapType type = wxBITMAP_TYPE_PNG;
228     switch( aBitmapType )
229     {
230     case BITMAP_TYPE::PNG: type = wxBITMAP_TYPE_PNG; break;
231     case BITMAP_TYPE::BMP: type = wxBITMAP_TYPE_BMP; break;
232     case BITMAP_TYPE::JPG: type = wxBITMAP_TYPE_JPEG; break;
233     }
234 
235     if( !image.SaveFile( aFileName, type ) )
236         retv = false;
237 
238     image.Destroy();
239     return retv;
240 }
241 
242 
AddBitmapToMenuItem(wxMenuItem * aMenu,const wxBitmap & aImage)243 void AddBitmapToMenuItem( wxMenuItem* aMenu, const wxBitmap& aImage )
244 {
245     // Retrieve the global application show icon option:
246     bool useImagesInMenus = Pgm().GetCommonSettings()->m_Appearance.use_icons_in_menus;
247 
248     wxItemKind menu_type = aMenu->GetKind();
249 
250     if( useImagesInMenus && menu_type != wxITEM_CHECK && menu_type != wxITEM_RADIO  )
251     {
252         aMenu->SetBitmap( aImage );
253     }
254 }
255 
256 
AddMenuItem(wxMenu * aMenu,int aId,const wxString & aText,const wxBitmap & aImage,wxItemKind aType=wxITEM_NORMAL)257 wxMenuItem* AddMenuItem( wxMenu* aMenu, int aId, const wxString& aText,
258                          const wxBitmap& aImage, wxItemKind aType = wxITEM_NORMAL )
259 {
260     wxMenuItem* item = new wxMenuItem( aMenu, aId, aText, wxEmptyString, aType );
261     AddBitmapToMenuItem( item, aImage );
262 
263     aMenu->Append( item );
264 
265     return item;
266 }
267 
268 
AddMenuItem(wxMenu * aMenu,int aId,const wxString & aText,const wxString & aHelpText,const wxBitmap & aImage,wxItemKind aType=wxITEM_NORMAL)269 wxMenuItem* AddMenuItem( wxMenu* aMenu, int aId, const wxString& aText,
270                          const wxString& aHelpText, const wxBitmap& aImage,
271                          wxItemKind aType = wxITEM_NORMAL )
272 {
273     wxMenuItem* item = new wxMenuItem( aMenu, aId, aText, aHelpText, aType );
274     AddBitmapToMenuItem( item, aImage );
275 
276     aMenu->Append( item );
277 
278     return item;
279 }
280 
281 
AddMenuItem(wxMenu * aMenu,wxMenu * aSubMenu,int aId,const wxString & aText,const wxBitmap & aImage)282 wxMenuItem* AddMenuItem( wxMenu* aMenu, wxMenu* aSubMenu, int aId,
283                          const wxString& aText, const wxBitmap& aImage )
284 {
285     wxMenuItem* item = new wxMenuItem( aMenu, aId, aText );
286     item->SetSubMenu( aSubMenu );
287     AddBitmapToMenuItem( item, aImage );
288 
289     aMenu->Append( item );
290 
291     return item;
292 }
293 
294 
AddMenuItem(wxMenu * aMenu,wxMenu * aSubMenu,int aId,const wxString & aText,const wxString & aHelpText,const wxBitmap & aImage)295 wxMenuItem* AddMenuItem( wxMenu* aMenu, wxMenu* aSubMenu, int aId,
296                          const wxString& aText, const wxString& aHelpText,
297                          const wxBitmap& aImage )
298 {
299     wxMenuItem* item = new wxMenuItem( aMenu, aId, aText, aHelpText );
300     item->SetSubMenu( aSubMenu );
301     AddBitmapToMenuItem( item, aImage );
302 
303     aMenu->Append( item );
304 
305     return item;
306 }
307