1# utilities.py
2#
3# Copyright 2018-2021 Romain F. T.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18from gi.repository import Gtk, Gio
19
20################################################################################
21
22def utilities_get_rgba_name(red, green, blue, alpha):
23	"""To improve accessibility, it is useful to display the name of the colors.
24	Sadly, it's a mess to implement, and it's quite approximative."""
25	color_string = ""
26	alpha_string = ""
27	if alpha == 0.0:
28		return _("Transparent")
29	elif alpha < 1.0:
30		alpha_string = ' - ' + _("%s%% transparent") % int(100 - alpha * 100)
31
32	total = red + green + blue
33	orange_coef = 0.0
34	lumin = total / 3.0
35	# print(lumin)
36	if green != 0:
37		orange_coef = (red/green) * lumin
38
39	if total != 0:
40		rgb_percents = [red/total, green/total, blue/total]
41	else:
42		rgb_percents = [0.333, 0.333, 0.333]
43	# print(rgb_percents)
44
45	grey_coef_r = rgb_percents[0] * lumin / 3
46	grey_coef_g = rgb_percents[1] * lumin / 3
47	grey_coef_b = rgb_percents[2] * lumin / 3
48	is_grey = abs(grey_coef_r - grey_coef_g) < 0.01
49	is_grey = is_grey and abs(grey_coef_g - grey_coef_b) < 0.01
50	is_grey = is_grey and abs(grey_coef_b - grey_coef_r) < 0.01
51
52	if is_grey:
53		if lumin > 0.9:
54			color_string = _("White")
55		elif lumin < 0.1:
56			color_string = _("Black")
57		else:
58			color_string = _("Grey")
59
60	elif rgb_percents[0] > 0.5 and rgb_percents[1] > 0.2 and rgb_percents[1] < 0.4:
61		if orange_coef > 0.87:
62			color_string = _("Orange")
63		else:
64			color_string = _("Brown")
65
66	elif rgb_percents[0] > 0.4 and rgb_percents[1] < 0.3 and rgb_percents[2] < 0.3:
67		if lumin < 0.7 and rgb_percents[0] < 0.7:
68			# Context: the name of the current color is provided as a tooltip to
69			# help users with color blindness, but some color names don't have a
70			# clear definition. Here, the app thinks it's probably brown.
71			color_string = _("Probably brown")
72		else:
73			color_string = _("Red")
74	elif rgb_percents[1] > 0.4 and rgb_percents[0] < 0.4 and rgb_percents[2] < 0.4:
75		color_string = _("Green")
76	elif rgb_percents[2] > 0.4 and rgb_percents[0] < 0.3 and rgb_percents[1] < 0.4:
77		color_string = _("Blue")
78
79	elif rgb_percents[0] > 0.3 and rgb_percents[1] > 0.3 and rgb_percents[2] < 0.3:
80		if rgb_percents[1] < 0.4:
81			color_string = _("Probably brown")
82		else:
83			color_string = _("Yellow")
84	elif rgb_percents[0] > 0.3 and rgb_percents[2] > 0.3 and rgb_percents[1] < 0.3:
85		if lumin > 0.6 and rgb_percents[1] < 0.1:
86			color_string = _("Magenta")
87		else:
88			color_string = _("Purple")
89	elif rgb_percents[1] > 0.3 and rgb_percents[2] > 0.3 and rgb_percents[0] < 0.2:
90		if lumin > 0.7:
91			color_string = _("Cyan")
92		else:
93			# Context: the name of the current color is provided as a tooltip to
94			# help users with color blindness, but some color names don't have a
95			# clear definition. Here, the app thinks it's probably teal.
96			# You can translate "teal" with the name of approaching color, like
97			# turquoise or green-blue.
98			color_string = _("Probably teal")
99
100	else:
101		# Context: the name of the current color is provided as a tooltip to
102		# help users with color blindness, but some color names don't have a
103		# clear definition. Here, the app can't find a corresponding color name.
104		color_string = _("Unknown color name")
105
106	# print(color_string)
107	return (color_string + alpha_string)
108
109################################################################################
110
111def utilities_add_filechooser_filters(dialog):
112	"""Add file filters for images to file chooser dialogs."""
113	allPictures = Gtk.FileFilter()
114	allPictures.set_name(_("All pictures"))
115	allPictures.add_mime_type('image/png')
116	allPictures.add_mime_type('image/jpeg')
117	allPictures.add_mime_type('image/bmp')
118
119	pngPictures = Gtk.FileFilter()
120	pngPictures.set_name(_("PNG images"))
121	pngPictures.add_mime_type('image/png')
122
123	jpegPictures = Gtk.FileFilter()
124	jpegPictures.set_name(_("JPEG images"))
125	jpegPictures.add_mime_type('image/jpeg')
126
127	bmpPictures = Gtk.FileFilter()
128	bmpPictures.set_name(_("BMP images"))
129	bmpPictures.add_mime_type('image/bmp')
130
131	dialog.add_filter(allPictures)
132	dialog.add_filter(pngPictures)
133	dialog.add_filter(jpegPictures)
134	dialog.add_filter(bmpPictures)
135
136################################################################################
137
138def utilities_add_unit_to_spinbtn(spinbutton, width_chars, unit):
139	spinbutton.set_width_chars(width_chars + 3)
140	if unit == 'px':
141		# To translators: it's a measure unit, it appears in tooltips over
142		# numerical inputs
143		_add_spinbutton_icon(spinbutton, 'unit-pixels-symbolic', _("pixels"))
144	elif unit == '%':
145		# To translators: it appears in tooltips over numerical inputs
146		_add_spinbutton_icon(spinbutton, 'unit-percents-symbolic', _("percents"))
147	elif unit == '°':
148		# To translators: it's the angle measure unit, it appears in a tooltip
149		# over a numerical input
150		_add_spinbutton_icon(spinbutton, 'unit-degrees-symbolic', _("degrees"))
151
152def _add_spinbutton_icon(spinbutton, icon, tooltip):
153	p = Gtk.EntryIconPosition.SECONDARY
154	spinbutton.set_icon_from_icon_name(p, icon)
155	spinbutton.set_icon_tooltip_text(p, tooltip)
156	spinbutton.set_icon_sensitive(p, False)
157
158################################################################################
159
160def utilities_gfile_is_image(gfile, error_msg=""):
161	try:
162		infos = gfile.query_info('standard::*', Gio.FileQueryInfoFlags.NONE, None)
163		if 'image/' in infos.get_content_type():
164			# The exact file format of the image isn't validated here because i
165			# can't assume what gdkpixbuf is able to read (it's modular, and it
166			# evolves). An InvalidFileFormatException will be raised by DrImage
167			# if the file can't be loaded.
168			return True, error_msg
169		else:
170			error_msg = error_msg + _("%s isn't an image.") % gfile.get_path()
171	except Exception as err:
172		error_msg = error_msg + err.message
173	return False, error_msg
174
175class InvalidFileFormatException(Exception):
176	def __init__(self, initial_message, fpath):
177		self.message = initial_message
178		cpt = 0
179		with open(fpath, 'rb') as f:
180			riff_bytes = f.read(4)
181			size_bytes = f.read(4)
182			webp_bytes = f.read(4)
183			if riff_bytes == b'RIFF' and webp_bytes == b'WEBP':
184				msg = _("Sorry, WEBP images can't be loaded by this app.") + " �� "
185				if fpath[-5:] != '.webp':
186					# Context: an error message, %s is a file path
187					msg = msg + _("Despite its name, %s is a WEBP file.") % fpath
188				self.message = msg
189		super().__init__(self.message)
190	# This exception is meant to be raised when a file is detected as an image
191	# by Gio (see utility function above) BUT can't be loaded into a GdkPixbuf.
192	# It usually means the file is corrupted, or has a deceptive name (for
193	# example "xxx.jpeg" despite being a text file), or is just an image format
194	# not supported by the GdkPixbuf version installed by the user.
195
196################################################################################
197
198