Initial commit for real hidpi support
Note: This commit has only been tested on MacOS Changes: - Icons now use the native hidpi support from Gtk (through Icon Theme) - Icons are now directly generated from scalable file (i.e. SVG file) - Widget sizes are scaled based on DPI and scale factor - Font size is scaled based on DPI and scale factor
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
* This file is part of RawTherapee.
|
||||
*
|
||||
* Copyright (c) 2018 Jean-Christophe FRISCH <natureh.510@gmail.com>
|
||||
* Copyright (c) 2022 Pierre CABRERA <pierre.cab@gmail.com>
|
||||
*
|
||||
* RawTherapee is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -18,47 +19,221 @@
|
||||
*/
|
||||
|
||||
#include "rtscalable.h"
|
||||
#include <glib/gstdio.h>
|
||||
#include <regex>
|
||||
#include <gtkmm.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <librsvg/rsvg.h>
|
||||
|
||||
#include "../rtengine/rt_math.h"
|
||||
#include "options.h"
|
||||
#include "../rtengine/settings.h"
|
||||
#include "config.h"
|
||||
#include "guiutils.h"
|
||||
|
||||
double RTScalable::dpi = 0.;
|
||||
int RTScalable::scale = 0;
|
||||
// Default static parameter values
|
||||
double RTScalable::dpi = 96.;
|
||||
int RTScalable::scale = 1;
|
||||
|
||||
extern Glib::ustring argv0;
|
||||
extern unsigned char initialGdkScale;
|
||||
extern float fontScale;
|
||||
Gtk::TextDirection RTScalable::direction = Gtk::TextDirection::TEXT_DIR_NONE;
|
||||
void RTScalable::updateDPInScale(const Gtk::Window* window, double &newDPI, int &newScale)
|
||||
{
|
||||
if (window) {
|
||||
const auto screen = window->get_screen();
|
||||
newDPI = screen->get_resolution(); // Get DPI retrieved from the OS
|
||||
newScale = window->get_scale_factor(); // Get scale factor associated to the window
|
||||
}
|
||||
}
|
||||
|
||||
Cairo::RefPtr<Cairo::ImageSurface> RTScalable::loadSurfaceFromIcon(const Glib::ustring &iconName, const Gtk::IconSize iconSize)
|
||||
{
|
||||
GThreadLock lock; // All icon theme access or image access on separate thread HAVE to be protected
|
||||
|
||||
Cairo::RefPtr<Cairo::ImageSurface> surf; // Create Cairo::RefPtr<Cairo::ImageSurface> nullptr
|
||||
|
||||
// Get icon theme
|
||||
const auto theme = Gtk::IconTheme::get_default();
|
||||
|
||||
// Get pixel size from Gtk::IconSize
|
||||
int wSize, hSize;
|
||||
|
||||
if (!Gtk::IconSize::lookup(iconSize, wSize, hSize)) { // Size in invalid
|
||||
wSize = hSize = 16; // Set to a default size of 16px (i.e. Gtk::ICON_SIZE_SMALL_TOOLBAR one)
|
||||
}
|
||||
|
||||
// Get scale based on DPI and scale
|
||||
// Note: hSize not used because icon are considered squared
|
||||
const int size = wSize;
|
||||
|
||||
// Looking for corresponding icon (if existing)
|
||||
const auto iconInfo = theme->lookup_icon(iconName, size);
|
||||
const auto iconPath = iconInfo.get_filename();
|
||||
|
||||
if ((iconPath.empty() || !iconInfo) && rtengine::settings->verbose) {
|
||||
std::cerr << "Failed to load icon \"" << iconName << "\" for size " << size << "px" << std::endl;
|
||||
return surf;
|
||||
}
|
||||
|
||||
// Create surface from corresponding icon
|
||||
const auto pos = iconPath.find_last_of('.');
|
||||
|
||||
if (pos >= 0 && pos < iconPath.length()) {
|
||||
const auto fext = iconPath.substr(pos + 1, iconPath.length()).lowercase();
|
||||
|
||||
// Case where iconPath is a PNG file
|
||||
if (fext == "png") {
|
||||
// Create surface from PNG file
|
||||
surf = RTScalable::loadSurfaceFromPNG(iconPath, true);
|
||||
}
|
||||
|
||||
// Case where iconPath is a SVG file
|
||||
if (fext == "svg") {
|
||||
// Create surface from SVG file
|
||||
surf = RTScalable::loadSurfaceFromSVG(iconPath, size, size, true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Scale current surface size to desired scaled size
|
||||
if (surface) {
|
||||
// Note: surface is considered made from squared icon
|
||||
const double scale_factor = static_cast<double>(size) / static_cast<double>(surface->get_width());
|
||||
surf = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32,
|
||||
static_cast<int>(surface->get_width() * scale_factor + 0.5),
|
||||
static_cast<int>(surface->get_height() * scale_factor + 0.5));
|
||||
const auto cr = Cairo::Context::create(surf);
|
||||
cr->scale(scale_factor, scale_factor);
|
||||
cr->set_source(surface, 0, 0);
|
||||
cr->paint();
|
||||
}
|
||||
*/
|
||||
|
||||
return surf;
|
||||
}
|
||||
|
||||
Cairo::RefPtr<Cairo::ImageSurface> RTScalable::loadSurfaceFromPNG(const Glib::ustring &fname, const bool is_path)
|
||||
{
|
||||
GThreadLock lock; // All icon theme access or image access on separate thread HAVE to be protected
|
||||
|
||||
Cairo::RefPtr<Cairo::ImageSurface> surf; // Create Cairo::RefPtr<Cairo::ImageSurface> nullptr
|
||||
|
||||
Glib::ustring path;
|
||||
|
||||
if (is_path) {
|
||||
// Directly use fname as a path
|
||||
path = fname;
|
||||
} else {
|
||||
// Look for PNG file in "images" folder
|
||||
Glib::ustring imagesFolder = Glib::build_filename(DATA_SEARCH_PATH, "images");
|
||||
path = Glib::build_filename(imagesFolder, fname);
|
||||
}
|
||||
|
||||
// Create surface from PNG file if file exist
|
||||
if (Glib::file_test(path.c_str(), Glib::FILE_TEST_EXISTS)) {
|
||||
surf = Cairo::ImageSurface::create_from_png(path);
|
||||
} else {
|
||||
if (rtengine::settings->verbose) {
|
||||
std::cerr << "Failed to load PNG file \"" << fname << "\"" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return surf;
|
||||
}
|
||||
|
||||
Cairo::RefPtr<Cairo::ImageSurface> RTScalable::loadSurfaceFromSVG(const Glib::ustring &fname, const int width, const int height, const bool is_path)
|
||||
{
|
||||
GThreadLock lock; // All icon theme access or image access on separate thread HAVE to be protected
|
||||
|
||||
Cairo::RefPtr<Cairo::ImageSurface> surf; // Create Cairo::RefPtr<Cairo::ImageSurface> nullptr
|
||||
|
||||
Glib::ustring path;
|
||||
|
||||
if (is_path) {
|
||||
// Directly use fname as a path
|
||||
path = fname;
|
||||
} else {
|
||||
// Look for PNG file in "images" folder
|
||||
Glib::ustring imagesFolder = Glib::build_filename(DATA_SEARCH_PATH, "images");
|
||||
path = Glib::build_filename(imagesFolder, fname);
|
||||
}
|
||||
|
||||
// Create surface from SVG file if file exist
|
||||
if (Glib::file_test(path.c_str(), Glib::FILE_TEST_EXISTS)) {
|
||||
// Read content of SVG file
|
||||
std::string svgFile;
|
||||
try {
|
||||
svgFile = Glib::file_get_contents(path);
|
||||
}
|
||||
catch (Glib::FileError &err) {
|
||||
std::cerr << "Failed to load SVG file \"" << fname << "\": " << err.what() << std::endl;
|
||||
return surf;
|
||||
}
|
||||
|
||||
// Create surface with librsvg library
|
||||
GError* error = nullptr;
|
||||
RsvgHandle* handle = rsvg_handle_new_from_data((unsigned const char*)svgFile.c_str(), svgFile.length(), &error);
|
||||
|
||||
if (error) {
|
||||
std::cerr << "Failed to load SVG file \"" << fname << "\": " << std::endl
|
||||
<< Glib::ustring(error->message) << std::endl;
|
||||
return surf;
|
||||
}
|
||||
|
||||
if (width == -1 || height == -1) {
|
||||
// Use SVG image natural width and height
|
||||
RsvgDimensionData dim;
|
||||
rsvg_handle_get_dimensions(handle, &dim); // Get SVG image dimensions
|
||||
surf = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, dim.width, dim.height);
|
||||
} else {
|
||||
// Use given width and height
|
||||
surf = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width, height);
|
||||
}
|
||||
|
||||
// Render (and erase with) default surface background
|
||||
Cairo::RefPtr<Cairo::Context> c = Cairo::Context::create(surf);
|
||||
c->set_source_rgba (0., 0., 0., 0.);
|
||||
c->set_operator (Cairo::OPERATOR_CLEAR);
|
||||
c->paint();
|
||||
|
||||
// Render surface based on SVG image
|
||||
error = nullptr;
|
||||
RsvgRectangle rect = {
|
||||
.x = 0.,
|
||||
.y = 0.,
|
||||
.width = static_cast<double>(width * RTScalable::getScale()), // SVG image is upscaled to avoid blur effect
|
||||
.height = static_cast<double>(height * RTScalable::getScale()) // SVG image is upscaled to avoid blur effect
|
||||
};
|
||||
c->set_operator (Cairo::OPERATOR_OVER);
|
||||
c->scale(1. / RTScalable::getScale(), 1. / RTScalable::getScale()); // Cairo surface is scaled to match image dimensions
|
||||
|
||||
const bool success = rsvg_handle_render_document(handle, c->cobj(), &rect, &error);
|
||||
|
||||
if (!success && error) {
|
||||
std::cerr << "Failed to load SVG file \"" << fname << "\": " << std::endl
|
||||
<< Glib::ustring(error->message) << std::endl;
|
||||
return surf;
|
||||
}
|
||||
|
||||
rsvg_handle_free(handle);
|
||||
} else {
|
||||
if (rtengine::settings->verbose) {
|
||||
std::cerr << "Failed to load SVG file \"" << fname << "\"" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return surf;
|
||||
}
|
||||
|
||||
void RTScalable::init(const Gtk::Window* window)
|
||||
{
|
||||
// Retrieve DPI and Scale paremeters from OS
|
||||
updateDPInScale(window, dpi, scale);
|
||||
}
|
||||
|
||||
void RTScalable::setDPInScale (const Gtk::Window* window)
|
||||
{
|
||||
updateDPInScale(window, dpi, scale);
|
||||
}
|
||||
|
||||
void RTScalable::setDPInScale (const double newDPI, const int newScale)
|
||||
{
|
||||
if (!options.pseudoHiDPISupport) {
|
||||
scale = 1;
|
||||
dpi = baseDPI;
|
||||
return;
|
||||
}
|
||||
|
||||
if (scale != newScale || (scale == 1 && dpi != newDPI)) {
|
||||
// reload all images
|
||||
scale = newScale;
|
||||
// HOMBRE: On windows, if scale = 2, the dpi is non significant, i.e. should be considered = 192 ; don't know for linux/macos
|
||||
dpi = newDPI;
|
||||
if (scale == 1) {
|
||||
if (dpi >= baseHiDPI) {
|
||||
scale = 2;
|
||||
}
|
||||
}
|
||||
else if (scale == 2) {
|
||||
if (dpi < baseHiDPI) {
|
||||
dpi *= 2.;
|
||||
}
|
||||
}
|
||||
}
|
||||
dpi = newDPI;
|
||||
scale = newScale;
|
||||
}
|
||||
|
||||
double RTScalable::getDPI ()
|
||||
@@ -66,188 +241,26 @@ double RTScalable::getDPI ()
|
||||
return dpi;
|
||||
}
|
||||
|
||||
double RTScalable::getTweakedDPI ()
|
||||
{
|
||||
return dpi * static_cast<double>(fontScale);
|
||||
}
|
||||
|
||||
int RTScalable::getScale ()
|
||||
{
|
||||
return scale;
|
||||
}
|
||||
|
||||
Gtk::TextDirection RTScalable::getDirection()
|
||||
double RTScalable::getGlobalScale()
|
||||
{
|
||||
return direction;
|
||||
return (static_cast<double>(RTScalable::getDPI()) /
|
||||
static_cast<double>(RTScalable::baseDPI) *
|
||||
RTScalable::getScale());
|
||||
}
|
||||
|
||||
void RTScalable::init(Gtk::Window *window)
|
||||
int RTScalable::scalePixelSize(const int pixel_size)
|
||||
{
|
||||
dpi = 0.;
|
||||
scale = 0;
|
||||
|
||||
setDPInScale(window->get_screen()->get_resolution(), rtengine::max((int)initialGdkScale, window->get_scale_factor()));
|
||||
direction = window->get_direction();
|
||||
const double s = getGlobalScale();
|
||||
return static_cast<int>(pixel_size * s + 0.5); // Rounded scaled size
|
||||
}
|
||||
|
||||
void RTScalable::deleteDir(const Glib::ustring& path)
|
||||
double RTScalable::scalePixelSize(const double pixel_size)
|
||||
{
|
||||
int error = 0;
|
||||
try {
|
||||
|
||||
Glib::Dir dir (path);
|
||||
|
||||
// Removing the directory content
|
||||
for (auto entry = dir.begin(); entry != dir.end(); ++entry) {
|
||||
error |= g_remove (Glib::build_filename (path, *entry).c_str());
|
||||
}
|
||||
|
||||
if (error != 0 && rtengine::settings->verbose) {
|
||||
std::cerr << "Failed to delete all entries in '" << path << "': " << g_strerror(errno) << std::endl;
|
||||
}
|
||||
|
||||
} catch (Glib::Error&) {
|
||||
error = 1;
|
||||
}
|
||||
|
||||
// Removing the directory itself
|
||||
if (!error) {
|
||||
try {
|
||||
|
||||
error = g_remove (path.c_str());
|
||||
|
||||
} catch (Glib::Error&) {}
|
||||
}
|
||||
}
|
||||
|
||||
void RTScalable::cleanup(bool all)
|
||||
{
|
||||
Glib::ustring imagesCacheFolder = Glib::build_filename (options.cacheBaseDir, "svg2png");
|
||||
Glib::ustring sDPI = Glib::ustring::compose("%1", (int)getTweakedDPI());
|
||||
|
||||
try {
|
||||
Glib::Dir dir(imagesCacheFolder);
|
||||
|
||||
for (Glib::DirIterator entry = dir.begin(); entry != dir.end(); ++entry) {
|
||||
const Glib::ustring fileName = *entry;
|
||||
const Glib::ustring filePath = Glib::build_filename(imagesCacheFolder, fileName);
|
||||
if (fileName == "." || fileName == ".." || !Glib::file_test(filePath, Glib::FILE_TEST_IS_DIR)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (all || fileName != sDPI) {
|
||||
deleteDir(filePath);
|
||||
}
|
||||
}
|
||||
} catch (Glib::Exception&) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* This function try to find the svg file converted to png in a cache and return
|
||||
* the Cairo::ImageSurface. If it can't find it, it will generate it.
|
||||
*
|
||||
* If the provided filename doesn't end with ".svg" (and then we're assuming it's a png file),
|
||||
* it will try to load that file directly from the source images folder. Scaling is disabled
|
||||
* for anything else than svg files.
|
||||
*
|
||||
* This function will always return a usable value, but it might be a garbage image
|
||||
* if something went wrong.
|
||||
*/
|
||||
Cairo::RefPtr<Cairo::ImageSurface> RTScalable::loadImage(const Glib::ustring &fname, double dpi)
|
||||
{
|
||||
// Magic color : #2a7fff
|
||||
// Dark theme color : #CCCCCC
|
||||
// Light theme color : #252525 -- not used
|
||||
|
||||
Glib::ustring imagesFolder = Glib::build_filename (argv0, "images");
|
||||
Glib::ustring imagesCacheFolder = Glib::build_filename (options.cacheBaseDir, "svg2png");
|
||||
|
||||
// -------------------- Looking for the cached PNG file first --------------------
|
||||
|
||||
Glib::ustring imagesCacheFolderDPI = Glib::build_filename (imagesCacheFolder, Glib::ustring::compose("%1", (int)dpi));
|
||||
auto path = Glib::build_filename(imagesCacheFolderDPI, fname);
|
||||
|
||||
if (Glib::file_test(path.c_str(), Glib::FILE_TEST_EXISTS)) {
|
||||
return Cairo::ImageSurface::create_from_png(path);
|
||||
} else {
|
||||
|
||||
// -------------------- Looking for the PNG file in install directory --------------------
|
||||
|
||||
path = Glib::build_filename(imagesFolder, fname);
|
||||
if (Glib::file_test(path.c_str(), Glib::FILE_TEST_EXISTS)) {
|
||||
return Cairo::ImageSurface::create_from_png(path);
|
||||
}
|
||||
}
|
||||
|
||||
// Last chance: looking for the svg file and creating the cached image file
|
||||
|
||||
// -------------------- Creating the cache folder for PNGs --------------------
|
||||
|
||||
if (!Glib::file_test(imagesCacheFolderDPI.c_str(), Glib::FILE_TEST_EXISTS)) {
|
||||
auto error = g_mkdir_with_parents (imagesCacheFolderDPI.c_str(), 0777);
|
||||
if (error != 0) {
|
||||
std::cerr << "ERROR: Can't create \"" << imagesCacheFolderDPI << "\" cache folder: " << g_strerror(error) << std::endl;
|
||||
Cairo::RefPtr<Cairo::ImageSurface> surf = Cairo::ImageSurface::create(Cairo::FORMAT_RGB24, 10, 10);
|
||||
return surf;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Loading the SVG file --------------------
|
||||
|
||||
std::string svgFile;
|
||||
Glib::ustring iconNameSVG;
|
||||
if (fname.find(".png") != Glib::ustring::npos) {
|
||||
iconNameSVG = fname.substr(0, fname.length() - 3) + Glib::ustring("svg");
|
||||
}
|
||||
try {
|
||||
path = Glib::build_filename (imagesFolder, iconNameSVG);
|
||||
//printf("Trying to get content of %s\n", path.c_str());
|
||||
svgFile = Glib::file_get_contents(Glib::build_filename (imagesFolder, iconNameSVG));
|
||||
}
|
||||
catch (Glib::FileError &err) {
|
||||
std::cerr << "ERROR: " << err.what() << std::endl;
|
||||
Cairo::RefPtr<Cairo::ImageSurface> surf = Cairo::ImageSurface::create(Cairo::FORMAT_RGB24, 10, 10);
|
||||
return surf;
|
||||
}
|
||||
|
||||
// -------------------- Updating the the magic color --------------------
|
||||
|
||||
std::string updatedSVG = std::regex_replace(svgFile, std::regex("#2a7fff"), "#CCCCCC");
|
||||
|
||||
// -------------------- Creating the rsvg handle --------------------
|
||||
|
||||
GError **error = nullptr;
|
||||
RsvgHandle *handle = rsvg_handle_new_from_data((unsigned const char*)updatedSVG.c_str(), updatedSVG.length(), error);
|
||||
|
||||
if (error && !handle) {
|
||||
std::cerr << "ERROR: Can't use the provided data for \"" << fname << "\" to create a RsvgHandle:" << std::endl
|
||||
<< Glib::ustring((*error)->message) << std::endl;
|
||||
Cairo::RefPtr<Cairo::ImageSurface> surf = Cairo::ImageSurface::create(Cairo::FORMAT_RGB24, 10, 10);
|
||||
return surf;
|
||||
}
|
||||
|
||||
// -------------------- Drawing the image to a Cairo::ImageSurface --------------------
|
||||
|
||||
RsvgDimensionData dim;
|
||||
rsvg_handle_get_dimensions(handle, &dim);
|
||||
double r = dpi / baseDPI;
|
||||
Cairo::RefPtr<Cairo::ImageSurface> surf = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, (int)(dim.width * r + 0.499), (int)(dim.height * r + 0.499));
|
||||
Cairo::RefPtr<Cairo::Context> c = Cairo::Context::create(surf);
|
||||
c->set_source_rgba (0., 0., 0., 0.);
|
||||
c->set_operator (Cairo::OPERATOR_CLEAR);
|
||||
c->paint ();
|
||||
c->set_operator (Cairo::OPERATOR_OVER);
|
||||
c->scale(r, r);
|
||||
rsvg_handle_render_cairo(handle, c->cobj());
|
||||
rsvg_handle_free(handle);
|
||||
|
||||
// -------------------- Saving the image in cache --------------------
|
||||
|
||||
surf->write_to_png(Glib::build_filename(imagesCacheFolderDPI, fname));
|
||||
|
||||
// -------------------- Finished! Pfeeew ! --------------------
|
||||
|
||||
return surf;
|
||||
const double s = getGlobalScale();
|
||||
return (pixel_size * s);
|
||||
}
|
||||
|
Reference in New Issue
Block a user