forked from dolphin-emu/dolphin
		
	Setting a single icon at a single resolution doesn't scale well, Windows requires a 16x16 icon for the window and a 32x32/48x48 for the taskbar. Providing all icons produces less pixellated results at HiDPI.
		
			
				
	
	
		
			534 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			534 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// Copyright 2009 Dolphin Emulator Project
 | 
						|
// Licensed under GPLv2+
 | 
						|
// Refer to the license.txt file included.
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
#include <array>
 | 
						|
#include <cmath>
 | 
						|
#include <string>
 | 
						|
#include <wx/app.h>
 | 
						|
#include <wx/bitmap.h>
 | 
						|
#include <wx/choice.h>
 | 
						|
#include <wx/combo.h>
 | 
						|
#include <wx/combobox.h>
 | 
						|
#include <wx/display.h>
 | 
						|
#include <wx/gdicmn.h>
 | 
						|
#include <wx/image.h>
 | 
						|
#include <wx/msgdlg.h>
 | 
						|
#include <wx/sizer.h>
 | 
						|
#include <wx/spinctrl.h>
 | 
						|
#include <wx/toolbar.h>
 | 
						|
#include <wx/toplevel.h>
 | 
						|
#include <wx/utils.h>
 | 
						|
 | 
						|
#include "Common/CommonPaths.h"
 | 
						|
#include "Common/FileUtil.h"
 | 
						|
#include "Common/StringUtil.h"
 | 
						|
#include "Core/ConfigManager.h"
 | 
						|
 | 
						|
#include "DolphinWX/WxUtils.h"
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
#include <Windows.h>
 | 
						|
#endif
 | 
						|
 | 
						|
namespace WxUtils
 | 
						|
{
 | 
						|
// Launch a file according to its mime type
 | 
						|
void Launch(const std::string& filename)
 | 
						|
{
 | 
						|
  if (!::wxLaunchDefaultBrowser(StrToWxStr(filename)))
 | 
						|
  {
 | 
						|
    // WARN_LOG
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// Launch an file explorer window on a certain path
 | 
						|
void Explore(const std::string& path)
 | 
						|
{
 | 
						|
  wxString wxPath = StrToWxStr(path);
 | 
						|
#ifndef _WIN32
 | 
						|
  // Default to file
 | 
						|
  if (!wxPath.Contains("://"))
 | 
						|
  {
 | 
						|
    wxPath = "file://" + wxPath;
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef __WXGTK__
 | 
						|
  wxPath.Replace(" ", "\\ ");
 | 
						|
#endif
 | 
						|
 | 
						|
  if (!::wxLaunchDefaultBrowser(wxPath))
 | 
						|
  {
 | 
						|
    // WARN_LOG
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ShowErrorDialog(const wxString& error_msg)
 | 
						|
{
 | 
						|
  wxMessageBox(error_msg, _("Error"), wxOK | wxICON_ERROR);
 | 
						|
}
 | 
						|
 | 
						|
wxBitmap CreateDisabledButtonBitmap(const wxBitmap& original)
 | 
						|
{
 | 
						|
  wxImage image = original.ConvertToImage();
 | 
						|
  return wxBitmap(image.ConvertToDisabled(240), wxBITMAP_SCREEN_DEPTH, original.GetScaleFactor());
 | 
						|
}
 | 
						|
 | 
						|
void AddToolbarButton(wxToolBar* toolbar, int toolID, const wxString& label, const wxBitmap& bitmap,
 | 
						|
                      const wxString& shortHelp)
 | 
						|
{
 | 
						|
  // Must explicitly set the disabled button bitmap because wxWidgets
 | 
						|
  // incorrectly desaturates it instead of lightening it.
 | 
						|
  toolbar->AddTool(toolID, label, bitmap, WxUtils::CreateDisabledButtonBitmap(bitmap),
 | 
						|
                   wxITEM_NORMAL, shortHelp);
 | 
						|
}
 | 
						|
 | 
						|
wxIconBundle GetDolphinIconBundle()
 | 
						|
{
 | 
						|
  static wxIconBundle s_bundle;
 | 
						|
  if (!s_bundle.IsEmpty())
 | 
						|
    return s_bundle;
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
 | 
						|
  // Convert the Windows ICO file into a wxIconBundle by tearing it apart into each individual
 | 
						|
  // sub-icon using the Win32 API. This is necessary because WX uses its own wxIcons internally
 | 
						|
  // which (unlike QIcon in Qt) only contain 1 image per icon, hence why wxIconBundle exists.
 | 
						|
  HINSTANCE dolphin = GetModuleHandleW(nullptr);
 | 
						|
  for (int size : {16, 32, 48, 256})
 | 
						|
  {
 | 
						|
    // Extract resource from embedded DolphinWX.rc
 | 
						|
    HANDLE win32_icon =
 | 
						|
        LoadImageW(dolphin, L"\"DOLPHIN\"", IMAGE_ICON, size, size, LR_CREATEDIBSECTION);
 | 
						|
    if (win32_icon && win32_icon != INVALID_HANDLE_VALUE)
 | 
						|
    {
 | 
						|
      wxIcon icon;
 | 
						|
      icon.CreateFromHICON(reinterpret_cast<HICON>(win32_icon));
 | 
						|
      s_bundle.AddIcon(icon);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
#else
 | 
						|
 | 
						|
  for (const char* fname : {"Dolphin.png", "dolphin_logo.png", "dolphin_logo@2x.png"})
 | 
						|
  {
 | 
						|
    wxImage image{StrToWxStr(File::GetSysDirectory() + RESOURCES_DIR DIR_SEP + fname),
 | 
						|
                  wxBITMAP_TYPE_PNG};
 | 
						|
    if (image.IsOk())
 | 
						|
    {
 | 
						|
      wxIcon icon;
 | 
						|
      icon.CopyFromBitmap(image);
 | 
						|
      s_bundle.AddIcon(icon);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
#endif
 | 
						|
 | 
						|
  return s_bundle;
 | 
						|
}
 | 
						|
 | 
						|
wxRect GetVirtualScreenGeometry()
 | 
						|
{
 | 
						|
  wxRect geometry;
 | 
						|
  for (unsigned int i = 0, end = wxDisplay::GetCount(); i < end; ++i)
 | 
						|
    geometry.Union(wxDisplay(i).GetGeometry());
 | 
						|
  return geometry;
 | 
						|
}
 | 
						|
 | 
						|
void SetWindowSizeAndFitToScreen(wxTopLevelWindow* tlw, wxPoint pos, wxSize size,
 | 
						|
                                 wxSize default_size)
 | 
						|
{
 | 
						|
  if (tlw->IsMaximized())
 | 
						|
    return;
 | 
						|
 | 
						|
  // NOTE: Positions can be negative and still be valid. Coordinates are relative to the
 | 
						|
  //   primary monitor so if the primary monitor is in the middle then (-1000, 10) is a
 | 
						|
  //   valid position on the monitor to the left of the primary. (This does not apply to
 | 
						|
  //   sizes obviously)
 | 
						|
  wxRect screen_geometry;
 | 
						|
  wxRect window_geometry{pos, size};
 | 
						|
 | 
						|
  if (wxDisplay::GetCount() > 1)
 | 
						|
    screen_geometry = GetVirtualScreenGeometry();
 | 
						|
  else
 | 
						|
    screen_geometry = wxDisplay(0).GetClientArea();
 | 
						|
 | 
						|
  // Initialize the default size if it is wxDefaultSize or otherwise negative.
 | 
						|
  default_size.DecTo(screen_geometry.GetSize());
 | 
						|
  default_size.IncTo(tlw->GetMinSize());
 | 
						|
  if (!default_size.IsFullySpecified())
 | 
						|
    default_size.SetDefaults(wxDisplay(0).GetClientArea().GetSize() / 2);
 | 
						|
 | 
						|
  // If the position we're given doesn't make sense then go with the current position.
 | 
						|
  // (Assuming the window was created with wxDefaultPosition then this should be reasonable)
 | 
						|
  if (pos.x - screen_geometry.GetLeft() < -1000 || pos.y - screen_geometry.GetTop() < -1000 ||
 | 
						|
      pos.x - screen_geometry.GetRight() > 1000 || pos.y - screen_geometry.GetBottom() > 1000)
 | 
						|
  {
 | 
						|
    window_geometry.SetPosition(tlw->GetPosition());
 | 
						|
  }
 | 
						|
 | 
						|
  // If the window is bigger than all monitors combined, or negative (uninitialized) then reset it.
 | 
						|
  if (window_geometry.IsEmpty() || window_geometry.GetWidth() > screen_geometry.GetWidth() ||
 | 
						|
      window_geometry.GetHeight() > screen_geometry.GetHeight())
 | 
						|
  {
 | 
						|
    window_geometry.SetSize(default_size);
 | 
						|
  }
 | 
						|
 | 
						|
  // Check if the window entirely lives on a single monitor without spanning.
 | 
						|
  // If the window does not span multiple screens then we should constrain it within that
 | 
						|
  // single monitor instead of the entire virtual desktop space.
 | 
						|
  // The benefit to doing this is that we can account for the OS X menu bar and Windows task
 | 
						|
  // bar which are treated as invisible when only looking at the virtual desktop instead of
 | 
						|
  // an individual screen.
 | 
						|
  if (wxDisplay::GetCount() > 1)
 | 
						|
  {
 | 
						|
    // SPECIAL CASE: If the window is entirely outside the visible area of the desktop then we
 | 
						|
    //   put it back on the primary (zero) monitor.
 | 
						|
    wxRect monitor_intersection{window_geometry};
 | 
						|
    int the_monitor = 0;
 | 
						|
    if (!monitor_intersection.Intersect(screen_geometry).IsEmpty())
 | 
						|
    {
 | 
						|
      std::array<int, 4> monitors{{wxDisplay::GetFromPoint(monitor_intersection.GetTopLeft()),
 | 
						|
                                   wxDisplay::GetFromPoint(monitor_intersection.GetTopRight()),
 | 
						|
                                   wxDisplay::GetFromPoint(monitor_intersection.GetBottomLeft()),
 | 
						|
                                   wxDisplay::GetFromPoint(monitor_intersection.GetBottomRight())}};
 | 
						|
      the_monitor = wxNOT_FOUND;
 | 
						|
      bool intersected = false;
 | 
						|
      for (int one_monitor : monitors)
 | 
						|
      {
 | 
						|
        if (one_monitor == the_monitor || one_monitor == wxNOT_FOUND)
 | 
						|
          continue;
 | 
						|
        if (the_monitor != wxNOT_FOUND)
 | 
						|
        {
 | 
						|
          // The window is spanning multiple screens.
 | 
						|
          the_monitor = wxNOT_FOUND;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        the_monitor = one_monitor;
 | 
						|
        intersected = true;
 | 
						|
      }
 | 
						|
      // If we get wxNOT_FOUND for all corners then there are holes in the virtual desktop and the
 | 
						|
      // entire window is lost in one. (e.g. 3 monitors in an 'L', window in top-right)
 | 
						|
      if (!intersected)
 | 
						|
        the_monitor = 0;
 | 
						|
    }
 | 
						|
    if (the_monitor != wxNOT_FOUND)
 | 
						|
    {
 | 
						|
      // We'll only use the client area of this monitor if the window will actually fit.
 | 
						|
      // (It may not fit if the window is spilling off the edge so it isn't entirely visible)
 | 
						|
      wxRect client_area{wxDisplay(the_monitor).GetClientArea()};
 | 
						|
      if (client_area.GetWidth() >= window_geometry.GetWidth() &&
 | 
						|
          client_area.GetHeight() >= window_geometry.GetHeight())
 | 
						|
      {
 | 
						|
        screen_geometry = client_area;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // The window SHOULD be small enough to fit on the screen, but it might be spilling off an edge
 | 
						|
  // so we'll snap it to the nearest edge as necessary.
 | 
						|
  if (!screen_geometry.Contains(window_geometry))
 | 
						|
  {
 | 
						|
    // NOTE: The order is important here, if the window *is* too big to fit then it will snap to
 | 
						|
    //   the top-left corner.
 | 
						|
    int spill_x = std::max(0, window_geometry.GetRight() - screen_geometry.GetRight());
 | 
						|
    int spill_y = std::max(0, window_geometry.GetBottom() - screen_geometry.GetBottom());
 | 
						|
    window_geometry.Offset(-spill_x, -spill_y);
 | 
						|
    if (window_geometry.GetTop() < screen_geometry.GetTop())
 | 
						|
      window_geometry.SetTop(screen_geometry.GetTop());
 | 
						|
    if (window_geometry.GetLeft() < screen_geometry.GetLeft())
 | 
						|
      window_geometry.SetLeft(screen_geometry.GetLeft());
 | 
						|
  }
 | 
						|
 | 
						|
  tlw->SetSize(window_geometry, wxSIZE_ALLOW_MINUS_ONE);
 | 
						|
}
 | 
						|
 | 
						|
wxSizer* GiveMinSize(wxWindow* window, const wxSize& min_size)
 | 
						|
{
 | 
						|
  wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
						|
  int flags = wxEXPAND;
 | 
						|
  // On Windows comboboxes will misrender when stretched vertically.
 | 
						|
  if (wxDynamicCast(window, wxChoice) || wxDynamicCast(window, wxComboBox) ||
 | 
						|
      wxDynamicCast(window, wxComboCtrl))
 | 
						|
    flags = wxALIGN_CENTER_VERTICAL;
 | 
						|
  sizer->Add(window, 1, flags);
 | 
						|
  sizer->SetMinSize(min_size);
 | 
						|
  return sizer;
 | 
						|
}
 | 
						|
 | 
						|
wxSizer* GiveMinSizeDIP(wxWindow* window, const wxSize& min_size)
 | 
						|
{
 | 
						|
  return GiveMinSize(window, window->FromDIP(min_size));
 | 
						|
}
 | 
						|
 | 
						|
wxSize GetTextWidgetMinSize(const wxControl* control, const wxString& value)
 | 
						|
{
 | 
						|
  return control->GetSizeFromTextSize(control->GetTextExtent(value));
 | 
						|
}
 | 
						|
 | 
						|
wxSize GetTextWidgetMinSize(const wxControl* control, unsigned int value)
 | 
						|
{
 | 
						|
  return GetTextWidgetMinSize(control, wxString::Format("%u", value));
 | 
						|
}
 | 
						|
 | 
						|
wxSize GetTextWidgetMinSize(const wxControl* control, int value)
 | 
						|
{
 | 
						|
  return GetTextWidgetMinSize(control, wxString::Format("%d", value));
 | 
						|
}
 | 
						|
 | 
						|
wxSize GetTextWidgetMinSize(const wxSpinCtrl* spinner)
 | 
						|
{
 | 
						|
  wxSize size = GetTextWidgetMinSize(spinner, spinner->GetMin());
 | 
						|
  size.IncTo(GetTextWidgetMinSize(spinner, spinner->GetMax()));
 | 
						|
  return size;
 | 
						|
}
 | 
						|
 | 
						|
static wxImage LoadScaledImage(const std::string& file_path, const wxWindow* context,
 | 
						|
                               const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags,
 | 
						|
                               const wxColour& fill_color)
 | 
						|
{
 | 
						|
  std::string fpath, fname, fext;
 | 
						|
  SplitPath(file_path, &fpath, &fname, &fext);
 | 
						|
 | 
						|
  const double window_scale_factor = context->GetContentScaleFactor();
 | 
						|
  // Compute the total scale factor from the ratio of DIPs to window pixels (FromDIP) and
 | 
						|
  // window pixels to framebuffer pixels (GetContentScaleFactor).
 | 
						|
  // NOTE: Usually only one of these is meaningful:
 | 
						|
  //   - On Windows/GTK2: content_scale = 1.0, FromDIP = 96DPI -> Screen DPI
 | 
						|
  //   - On Mac OS X: content_scale = screen_dpi / 96, FromDIP = 96DPI -> 96DPI (no-op)
 | 
						|
  // [The 1024 is arbitrarily large to minimise rounding error, it has no significance]
 | 
						|
  const double scale_factor = (context->FromDIP(1024) / 1024.0) * window_scale_factor;
 | 
						|
 | 
						|
  // We search for files on quarter ratios of DIPs to framebuffer pixels.
 | 
						|
  // By default, the algorithm prefers to find an exact or bigger size then downscale if
 | 
						|
  // needed but will resort to upscaling if a bigger image cannot be found.
 | 
						|
  // E.g. A basic retina screen on Mac OS X has a scale_factor of 2.0, so we would look for
 | 
						|
  // @2x, @2.25x, @2.5x, @2.75x, @3x, @1.75x, @1.5x, @1.25x, @1x, then give up.
 | 
						|
  // (At 125% on Windows the search is @1.25, @1.5, @1.75, @2, @2.25, @1)
 | 
						|
  // If flags does not include LSI_SCALE_DOWN (i.e. we would be forced to crop big
 | 
						|
  // images instead of scaling them) then we will only accept smaller sizes, i.e.
 | 
						|
  // @2x, @1.75, @1.5, @1.25, @1, then give up.
 | 
						|
  // NOTE: We do a lot of exact comparisons against floating point here but it's fine
 | 
						|
  //   because the numbers involved are all powers of 2 so can be represented exactly.
 | 
						|
  wxImage image;
 | 
						|
  double selected_image_scale = 1;
 | 
						|
  {
 | 
						|
    auto image_check = [&](double scale) -> bool {
 | 
						|
      std::string path = fpath + fname + StringFromFormat("@%gx", scale) + fext;
 | 
						|
      if (!File::Exists(path))
 | 
						|
      {
 | 
						|
        // Special Case: @1x may not have a suffix at all.
 | 
						|
        if (scale != 1.0 || !File::Exists(file_path))
 | 
						|
          return false;
 | 
						|
        path = file_path;
 | 
						|
      }
 | 
						|
      if (!image.LoadFile(StrToWxStr(path), wxBITMAP_TYPE_ANY))
 | 
						|
        return false;
 | 
						|
      selected_image_scale = scale;
 | 
						|
      return true;
 | 
						|
    };
 | 
						|
    const bool prefer_smaller = !(flags & LSI_SCALE_DOWN);
 | 
						|
    const double scale_factor_quarter =
 | 
						|
        prefer_smaller ? std::floor(scale_factor * 4) / 4 : std::ceil(scale_factor * 4) / 4;
 | 
						|
    // Search for bigger sizes first (preferred)
 | 
						|
    if (!prefer_smaller)
 | 
						|
    {
 | 
						|
      // We search within a 'circle' of the exact match limited by scale=1.0.
 | 
						|
      // i.e. scale_factor = 1.5, radius = 0.5; scale = 2.5, radius = 1.5.
 | 
						|
      // The minimum radius is 1.0.
 | 
						|
      double limit = std::max(scale_factor_quarter * 2 - 1, scale_factor_quarter + 1);
 | 
						|
      for (double quarter = scale_factor_quarter; quarter <= limit; quarter += 0.25)
 | 
						|
      {
 | 
						|
        if (image_check(quarter))
 | 
						|
          break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    // If we didn't hit a bigger size then we'll fallback to looking for smaller ones
 | 
						|
    if (!image.IsOk())
 | 
						|
    {
 | 
						|
      double quarter = scale_factor_quarter;
 | 
						|
      if (!prefer_smaller)  // So we don't recheck the exact match
 | 
						|
        quarter -= 0.25;
 | 
						|
      for (; quarter >= 1.0; quarter -= 0.25)
 | 
						|
      {
 | 
						|
        if (image_check(quarter))
 | 
						|
          break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // The file apparently does not exist so we give up. Create a white square placeholder instead.
 | 
						|
  if (!image.IsOk())
 | 
						|
  {
 | 
						|
    wxLogError("Could not find resource: %s", StrToWxStr(file_path));
 | 
						|
    image.Create(1, 1, false);
 | 
						|
    image.Clear(0xFF);
 | 
						|
  }
 | 
						|
 | 
						|
  return ScaleImage(image, selected_image_scale, window_scale_factor, output_size, usable_rect,
 | 
						|
                    flags, fill_color);
 | 
						|
}
 | 
						|
 | 
						|
wxBitmap LoadScaledBitmap(const std::string& file_path, const wxWindow* context,
 | 
						|
                          const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags,
 | 
						|
                          const wxColour& fill_color)
 | 
						|
{
 | 
						|
  return wxBitmap(LoadScaledImage(file_path, context, output_size, usable_rect, flags, fill_color),
 | 
						|
                  wxBITMAP_SCREEN_DEPTH, context->GetContentScaleFactor());
 | 
						|
}
 | 
						|
 | 
						|
wxBitmap LoadScaledResourceBitmap(const std::string& name, const wxWindow* context,
 | 
						|
                                  const wxSize& output_size, const wxRect& usable_rect,
 | 
						|
                                  LSIFlags flags, const wxColour& fill_color)
 | 
						|
{
 | 
						|
  std::string path = File::GetSysDirectory() + RESOURCES_DIR DIR_SEP + name + ".png";
 | 
						|
  return LoadScaledBitmap(path, context, output_size, usable_rect, flags, fill_color);
 | 
						|
}
 | 
						|
 | 
						|
wxBitmap LoadScaledThemeBitmap(const std::string& name, const wxWindow* context,
 | 
						|
                               const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags,
 | 
						|
                               const wxColour& fill_color)
 | 
						|
{
 | 
						|
  std::string path = File::GetThemeDir(SConfig::GetInstance().theme_name) + name + ".png";
 | 
						|
  return LoadScaledBitmap(path, context, output_size, usable_rect, flags, fill_color);
 | 
						|
}
 | 
						|
 | 
						|
wxBitmap ScaleImageToBitmap(const wxImage& image, const wxWindow* context,
 | 
						|
                            const wxSize& output_size, const wxRect& usable_rect, LSIFlags flags,
 | 
						|
                            const wxColour& fill_color)
 | 
						|
{
 | 
						|
  double scale_factor = context->GetContentScaleFactor();
 | 
						|
  return wxBitmap(ScaleImage(image, 1.0, scale_factor, output_size, usable_rect, flags, fill_color),
 | 
						|
                  wxBITMAP_SCREEN_DEPTH, scale_factor);
 | 
						|
}
 | 
						|
 | 
						|
wxBitmap ScaleImageToBitmap(const wxImage& image, const wxWindow* context, double source_scale,
 | 
						|
                            LSIFlags flags, const wxColour& fill_color)
 | 
						|
{
 | 
						|
  double scale_factor = context->GetContentScaleFactor();
 | 
						|
  return wxBitmap(ScaleImage(image, source_scale, scale_factor, wxDefaultSize, wxDefaultSize, flags,
 | 
						|
                             fill_color),
 | 
						|
                  wxBITMAP_SCREEN_DEPTH, scale_factor);
 | 
						|
}
 | 
						|
 | 
						|
wxImage ScaleImage(wxImage image, double source_scale_factor, double content_scale_factor,
 | 
						|
                   wxSize output_size, wxRect usable_rect, LSIFlags flags,
 | 
						|
                   const wxColour& fill_color)
 | 
						|
{
 | 
						|
  if (!image.IsOk())
 | 
						|
  {
 | 
						|
    wxFAIL_MSG("WxUtils::ScaleImage expects a valid image.");
 | 
						|
    return image;
 | 
						|
  }
 | 
						|
 | 
						|
  if (content_scale_factor != 1.0)
 | 
						|
  {
 | 
						|
    output_size *= content_scale_factor;
 | 
						|
    usable_rect.SetPosition(usable_rect.GetPosition() * content_scale_factor);
 | 
						|
    usable_rect.SetSize(usable_rect.GetSize() * content_scale_factor);
 | 
						|
  }
 | 
						|
 | 
						|
  // Fix the output size if it's unset.
 | 
						|
  wxSize img_size = image.GetSize();
 | 
						|
  if (output_size.GetWidth() < 1)
 | 
						|
    output_size.SetWidth(
 | 
						|
        static_cast<int>(img_size.GetWidth() * (content_scale_factor / source_scale_factor)));
 | 
						|
  if (output_size.GetHeight() < 1)
 | 
						|
    output_size.SetHeight(
 | 
						|
        static_cast<int>(img_size.GetHeight() * (content_scale_factor / source_scale_factor)));
 | 
						|
 | 
						|
  // Fix the usable rect. If it's empty then the whole canvas is usable.
 | 
						|
  if (usable_rect.IsEmpty())
 | 
						|
  {
 | 
						|
    // Constructs a temp wxRect 0,0->output_size then move assigns it.
 | 
						|
    usable_rect = output_size;
 | 
						|
  }
 | 
						|
  else if (!usable_rect.Intersects(output_size))
 | 
						|
  {
 | 
						|
    wxFAIL_MSG("Usable Zone Rectangle is not inside the canvas. Check the output size is correct.");
 | 
						|
    image.Create(1, 1, false);
 | 
						|
    image.SetRGB(0, 0, fill_color.Red(), fill_color.Green(), fill_color.Blue());
 | 
						|
    if (fill_color.Alpha() == wxALPHA_TRANSPARENT)
 | 
						|
      image.SetMaskColour(fill_color.Red(), fill_color.Green(), fill_color.Blue());
 | 
						|
    usable_rect = output_size;
 | 
						|
  }
 | 
						|
 | 
						|
  // Step 1: Scale the image
 | 
						|
  if ((flags & LSI_SCALE) != LSI_SCALE_NONE)
 | 
						|
  {
 | 
						|
    if (flags & LSI_SCALE_NO_ASPECT)
 | 
						|
    {
 | 
						|
      // Stretch scale without preserving the aspect ratio.
 | 
						|
      bool scale_width = (img_size.GetWidth() > usable_rect.GetWidth() && flags & LSI_SCALE_DOWN) ||
 | 
						|
                         (img_size.GetWidth() < usable_rect.GetWidth() && flags & LSI_SCALE_UP);
 | 
						|
      bool scale_height =
 | 
						|
          (img_size.GetHeight() > usable_rect.GetHeight() && flags & LSI_SCALE_DOWN) ||
 | 
						|
          (img_size.GetHeight() < usable_rect.GetHeight() && flags & LSI_SCALE_UP);
 | 
						|
      if (scale_width || scale_height)
 | 
						|
      {
 | 
						|
        // NOTE: Using BICUBIC instead of HIGH because it's the same internally
 | 
						|
        //   except that downscaling uses a box filter with awful obvious aliasing
 | 
						|
        //   for non-integral scale factors.
 | 
						|
        image.Rescale(scale_width ? usable_rect.GetWidth() : img_size.GetWidth(),
 | 
						|
                      scale_height ? usable_rect.GetHeight() : img_size.GetHeight(),
 | 
						|
                      wxIMAGE_QUALITY_BICUBIC);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
      // Scale while preserving the aspect ratio.
 | 
						|
      double scale = std::min(static_cast<double>(usable_rect.GetWidth()) / img_size.GetWidth(),
 | 
						|
                              static_cast<double>(usable_rect.GetHeight()) / img_size.GetHeight());
 | 
						|
      int target_width = static_cast<int>(img_size.GetWidth() * scale);
 | 
						|
      int target_height = static_cast<int>(img_size.GetHeight() * scale);
 | 
						|
      // Bilinear produces sharper images when upscaling, bicubic tends to smear/blur sharp edges.
 | 
						|
      if (scale > 1.0 && flags & LSI_SCALE_UP)
 | 
						|
        image.Rescale(target_width, target_height, wxIMAGE_QUALITY_BILINEAR);
 | 
						|
      else if (scale < 1.0 && flags & LSI_SCALE_DOWN)
 | 
						|
        image.Rescale(target_width, target_height, wxIMAGE_QUALITY_BICUBIC);
 | 
						|
    }
 | 
						|
    img_size = image.GetSize();
 | 
						|
  }
 | 
						|
 | 
						|
  // Step 2: Resize the canvas to match the output size.
 | 
						|
  // NOTE: If NOT using LSI_SCALE_DOWN then this will implicitly crop the image
 | 
						|
  if (img_size != output_size || usable_rect.GetPosition() != wxPoint())
 | 
						|
  {
 | 
						|
    wxPoint base = usable_rect.GetPosition();
 | 
						|
    if (flags & LSI_ALIGN_HCENTER)
 | 
						|
      base.x += (usable_rect.GetWidth() - img_size.GetWidth()) / 2;
 | 
						|
    else if (flags & LSI_ALIGN_RIGHT)
 | 
						|
      base.x += usable_rect.GetWidth() - img_size.GetWidth();
 | 
						|
    if (flags & LSI_ALIGN_VCENTER)
 | 
						|
      base.y += (usable_rect.GetHeight() - img_size.GetHeight()) / 2;
 | 
						|
    else if (flags & LSI_ALIGN_BOTTOM)
 | 
						|
      base.y += usable_rect.GetHeight() - img_size.GetHeight();
 | 
						|
 | 
						|
    int r = -1, g = -1, b = -1;
 | 
						|
    if (fill_color.Alpha() != wxALPHA_TRANSPARENT)
 | 
						|
    {
 | 
						|
      r = fill_color.Red();
 | 
						|
      g = fill_color.Green();
 | 
						|
      b = fill_color.Blue();
 | 
						|
    }
 | 
						|
    image.Resize(output_size, base, r, g, b);
 | 
						|
  }
 | 
						|
 | 
						|
  return image;
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace
 | 
						|
 | 
						|
std::string WxStrToStr(const wxString& str)
 | 
						|
{
 | 
						|
  return str.ToUTF8().data();
 | 
						|
}
 | 
						|
 | 
						|
wxString StrToWxStr(const std::string& str)
 | 
						|
{
 | 
						|
  // return wxString::FromUTF8Unchecked(str.c_str());
 | 
						|
  return wxString::FromUTF8(str.c_str());
 | 
						|
}
 |