1 /*
2  *  This file is part of RawTherapee.
3  *
4  *  Copyright (c) 2018 Jean-Christophe FRISCH <natureh.510@gmail.com>
5  *
6  *  RawTherapee is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  RawTherapee is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "rtscalable.h"
21 #include <glib/gstdio.h>
22 #include <regex>
23 #include <glibmm.h>
24 #include <iostream>
25 #include <librsvg/rsvg.h>
26 #include "options.h"
27 
28 double RTScalable::dpi = 0.;
29 int RTScalable::scale = 0;
30 
31 extern Glib::ustring argv0;
32 extern Options options;
33 extern unsigned char initialGdkScale;
34 extern float fontScale;
35 Gtk::TextDirection RTScalable::direction = Gtk::TextDirection::TEXT_DIR_NONE;
36 
setDPInScale(const double newDPI,const int newScale)37 void RTScalable::setDPInScale (const double newDPI, const int newScale)
38 {
39     if (!options.pseudoHiDPISupport) {
40     	scale = 1;
41     	dpi = baseDPI;
42     	return;
43     }
44 
45     if (scale != newScale || (scale == 1 && dpi != newDPI)) {
46         // reload all images
47         scale = newScale;
48         // HOMBRE: On windows, if scale = 2, the dpi is non significant, i.e. should be considered = 192 ; don't know for linux/macos
49         dpi = newDPI;
50         if (scale == 1) {
51             if (dpi >= baseHiDPI) {
52                 scale = 2;
53             }
54         }
55         else if (scale == 2) {
56             if (dpi < baseHiDPI) {
57                 dpi *= 2.;
58             }
59         }
60     }
61 }
62 
getDPI()63 double RTScalable::getDPI ()
64 {
65     return dpi;
66 }
67 
getTweakedDPI()68 double RTScalable::getTweakedDPI ()
69 {
70     return dpi * fontScale;
71 }
72 
getScale()73 int RTScalable::getScale ()
74 {
75     return scale;
76 }
77 
getDirection()78 Gtk::TextDirection RTScalable::getDirection()
79 {
80     return direction;
81 }
82 
init(Gtk::Window * window)83 void RTScalable::init(Gtk::Window *window)
84 {
85     dpi = 0.;
86     scale = 0;
87 
88     setDPInScale(window->get_screen()->get_resolution(), rtengine::max((int)initialGdkScale, window->get_scale_factor()));
89     direction = window->get_direction();
90 }
91 
deleteDir(const Glib::ustring & path)92 void RTScalable::deleteDir(const Glib::ustring& path)
93 {
94     int error = 0;
95     try {
96 
97         Glib::Dir dir (path);
98 
99         // Removing the directory content
100         for (auto entry = dir.begin(); entry != dir.end(); ++entry) {
101             error |= g_remove (Glib::build_filename (path, *entry).c_str());
102         }
103 
104         if (error != 0 && options.rtSettings.verbose) {
105             std::cerr << "Failed to delete all entries in '" << path << "': " << g_strerror(errno) << std::endl;
106         }
107 
108     } catch (Glib::Error&) {
109         error = 1;
110     }
111 
112     // Removing the directory itself
113     if (!error) {
114         try {
115 
116             error = g_remove (path.c_str());
117 
118         } catch (Glib::Error&) {}
119     }
120 }
121 
cleanup(bool all)122 void RTScalable::cleanup(bool all)
123 {
124     Glib::ustring imagesCacheFolder = Glib::build_filename (options.cacheBaseDir, "svg2png");
125     Glib::ustring sDPI = Glib::ustring::compose("%1", (int)getTweakedDPI());
126 
127     try {
128         Glib::Dir dir(imagesCacheFolder);
129 
130         for (Glib::DirIterator entry = dir.begin(); entry != dir.end(); ++entry) {
131             const Glib::ustring fileName = *entry;
132             const Glib::ustring filePath = Glib::build_filename(imagesCacheFolder, fileName);
133             if (fileName == "." || fileName == ".." || !Glib::file_test(filePath, Glib::FILE_TEST_IS_DIR)) {
134                 continue;
135             }
136 
137             if (all || fileName != sDPI) {
138                 deleteDir(filePath);
139             }
140         }
141     } catch (Glib::Exception&) {
142     }
143 
144 }
145 
146 /*
147  * This function try to find the svg file converted to png in a cache and return
148  * the Cairo::ImageSurface. If it can't find it, it will generate it.
149  *
150  * If the provided filename doesn't end with ".svg" (and then we're assuming it's a png file),
151  * it will try to load that file directly from the source images folder. Scaling is disabled
152  * for anything else than svg files.
153  *
154  * This function will always return a usable value, but it might be a garbage image
155  * if something went wrong.
156  */
loadImage(const Glib::ustring & fname,double dpi)157 Cairo::RefPtr<Cairo::ImageSurface> RTScalable::loadImage(const Glib::ustring &fname, double dpi)
158 {
159     // Magic color       : #2a7fff
160     // Dark theme color  : #CCCCCC
161     // Light theme color : #252525  -- not used
162 
163     Glib::ustring imagesFolder = Glib::build_filename (argv0, "images");
164     Glib::ustring imagesCacheFolder = Glib::build_filename (options.cacheBaseDir, "svg2png");
165 
166     // -------------------- Looking for the cached PNG file first --------------------
167 
168     Glib::ustring imagesCacheFolderDPI = Glib::build_filename (imagesCacheFolder, Glib::ustring::compose("%1", (int)dpi));
169     auto path = Glib::build_filename(imagesCacheFolderDPI, fname);
170 
171     std::string svgFile;
172     Glib::ustring iconNameSVG;
173     if (fname.find(".png") != Glib::ustring::npos) {
174         iconNameSVG = fname.substr(0, fname.length() - 3) + Glib::ustring("svg");
175     }
176 
177     bool png_is_good = true;
178     if (!Glib::file_test(path.c_str(), Glib::FILE_TEST_EXISTS)) {
179         png_is_good = false;
180     } else {
181         auto svgpath = Glib::build_filename(imagesFolder, iconNameSVG);
182         if (Glib::file_test(svgpath.c_str(), Glib::FILE_TEST_EXISTS)) {
183             auto pnginfo = Gio::File::create_for_path(path)->query_info(G_FILE_ATTRIBUTE_TIME_MODIFIED);
184             auto svginfo = Gio::File::create_for_path(svgpath)->query_info(G_FILE_ATTRIBUTE_TIME_MODIFIED);
185             if (pnginfo->modification_time() < svginfo->modification_time()) {
186                 png_is_good = false;
187             }
188         }
189     }
190 
191     if (png_is_good) {
192         return Cairo::ImageSurface::create_from_png(path);
193     } else {
194 
195         // -------------------- Looking for the PNG file in install directory --------------------
196 
197         path = Glib::build_filename(imagesFolder, fname);
198         if (Glib::file_test(path.c_str(), Glib::FILE_TEST_EXISTS)) {
199             return Cairo::ImageSurface::create_from_png(path);
200         }
201     }
202 
203     // Last chance: looking for the svg file and creating the cached image file
204 
205     // -------------------- Creating the cache folder for PNGs --------------------
206 
207     if (!Glib::file_test(imagesCacheFolderDPI.c_str(), Glib::FILE_TEST_EXISTS)) {
208         auto error = g_mkdir_with_parents (imagesCacheFolderDPI.c_str(), 0777);
209         if (error != 0) {
210             std::cerr << "ERROR: Can't create \"" << imagesCacheFolderDPI << "\" cache folder: " << g_strerror(error)  << std::endl;
211             Cairo::RefPtr<Cairo::ImageSurface> surf = Cairo::ImageSurface::create(Cairo::FORMAT_RGB24, 10, 10);
212             return surf;
213         }
214     }
215 
216     // -------------------- Loading the SVG file --------------------
217 
218     // std::string svgFile;
219     // Glib::ustring iconNameSVG;
220     // if (fname.find(".png") != Glib::ustring::npos) {
221     //     iconNameSVG = fname.substr(0, fname.length() - 3) + Glib::ustring("svg");
222     // }
223     try {
224         path = Glib::build_filename (imagesFolder, iconNameSVG);
225         //printf("Trying to get content of %s\n", path.c_str());
226         svgFile = Glib::file_get_contents(Glib::build_filename (imagesFolder, iconNameSVG));
227     }
228     catch (Glib::FileError &err) {
229         std::cerr << "ERROR: " << err.what() << std::endl;
230         Cairo::RefPtr<Cairo::ImageSurface> surf = Cairo::ImageSurface::create(Cairo::FORMAT_RGB24, 10, 10);
231         return surf;
232     }
233 
234     // -------------------- Updating the the magic color --------------------
235 
236     std::string updatedSVG = std::regex_replace(svgFile, std::regex("#2a7fff"), "#CCCCCC");
237 
238     // -------------------- Creating the rsvg handle --------------------
239 
240     GError **error = nullptr;
241     RsvgHandle *handle = rsvg_handle_new_from_data((unsigned const char*)updatedSVG.c_str(), updatedSVG.length(), error);
242 
243     if (handle == nullptr) {
244         std::cerr << "ERROR: Can't use the provided data for \"" << fname << "\" to create a RsvgHandle:" << std::endl
245                   << Glib::ustring((*error)->message) << std::endl;
246         Cairo::RefPtr<Cairo::ImageSurface> surf = Cairo::ImageSurface::create(Cairo::FORMAT_RGB24, 10, 10);
247         return surf;
248     }
249 
250     // -------------------- Drawing the image to a Cairo::ImageSurface --------------------
251 
252     RsvgDimensionData dim;
253     rsvg_handle_get_dimensions(handle, &dim);
254     double r = dpi / baseDPI;
255     Cairo::RefPtr<Cairo::ImageSurface> surf = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, (int)(dim.width * r + 0.499), (int)(dim.height * r + 0.499));
256     Cairo::RefPtr<Cairo::Context> c = Cairo::Context::create(surf);
257     c->set_source_rgba (0., 0., 0., 0.);
258     c->set_operator (Cairo::OPERATOR_CLEAR);
259     c->paint ();
260     c->set_operator (Cairo::OPERATOR_OVER);
261     c->scale(r, r);
262     rsvg_handle_render_cairo(handle, c->cobj());
263     rsvg_handle_free(handle);
264 
265     // -------------------- Saving the image in cache --------------------
266 
267     surf->write_to_png(Glib::build_filename(imagesCacheFolderDPI, fname));
268 
269     // -------------------- Finished! Pfeeew ! --------------------
270 
271     return surf;
272 }
273