1 | /* |
2 | * Copyright (C) 2013 Bastien Nocera <hadess@hadess.net> |
3 | * |
4 | * Authors: Bastien Nocera <hadess@hadess.net> |
5 | * |
6 | * This program 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 2 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program 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 this program; if not, write to the Free Software |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | * |
20 | */ |
21 | |
22 | #include <string.h> |
23 | #include <glib.h> |
24 | #include <gio/gio.h> |
25 | #include <gdk-pixbuf/gdk-pixbuf.h> |
26 | |
27 | #include <locale.h> |
28 | #include <math.h> |
29 | #include <stdlib.h> |
30 | |
31 | #include "gnome-thumbnailer-skeleton.h" |
32 | |
33 | #ifndef THUMBNAILER_USAGE |
34 | #error "THUMBNAILER_USAGE must be set" |
35 | #endif |
36 | |
37 | static int output_size = 256; |
38 | static gboolean g_fatal_warnings = FALSE; |
39 | static char **filenames = NULL; |
40 | |
41 | #if !GDK_PIXBUF_CHECK_VERSION(2,36,5) |
42 | /** |
43 | * gnome_desktop_thumbnail_scale_down_pixbuf: |
44 | * @pixbuf: a #GdkPixbuf |
45 | * @dest_width: the desired new width |
46 | * @dest_height: the desired new height |
47 | * |
48 | * Scales the pixbuf to the desired size. This function |
49 | * is a lot faster than gdk-pixbuf when scaling down by |
50 | * large amounts. |
51 | * |
52 | * Return value: (transfer full): a scaled pixbuf |
53 | * |
54 | * Since: 2.2 |
55 | **/ |
56 | GdkPixbuf * |
57 | gnome_desktop_thumbnail_scale_down_pixbuf (GdkPixbuf *pixbuf, |
58 | int dest_width, |
59 | int dest_height) |
60 | { |
61 | int source_width, source_height; |
62 | int s_x1, s_y1, s_x2, s_y2; |
63 | int s_xfrac, s_yfrac; |
64 | int dx, dx_frac, dy, dy_frac; |
65 | div_t ddx, ddy; |
66 | int x, y; |
67 | int r, g, b, a; |
68 | int n_pixels; |
69 | gboolean has_alpha; |
70 | guchar *dest, *src, *xsrc, *src_pixels; |
71 | GdkPixbuf *dest_pixbuf; |
72 | int pixel_stride; |
73 | int source_rowstride, dest_rowstride; |
74 | |
75 | if (dest_width == 0 || dest_height == 0) { |
76 | return NULL; |
77 | } |
78 | |
79 | source_width = gdk_pixbuf_get_width (pixbuf); |
80 | source_height = gdk_pixbuf_get_height (pixbuf); |
81 | |
82 | g_assert (source_width >= dest_width); |
83 | g_assert (source_height >= dest_height); |
84 | |
85 | ddx = div (source_width, dest_width); |
86 | dx = ddx.quot; |
87 | dx_frac = ddx.rem; |
88 | |
89 | ddy = div (source_height, dest_height); |
90 | dy = ddy.quot; |
91 | dy_frac = ddy.rem; |
92 | |
93 | g_assert (dx >= 1); |
94 | g_assert (dy >= 1); |
95 | |
96 | has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); |
97 | source_rowstride = gdk_pixbuf_get_rowstride (pixbuf); |
98 | src_pixels = gdk_pixbuf_get_pixels (pixbuf); |
99 | |
100 | dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, has_alpha, 8, |
101 | dest_width, dest_height); |
102 | dest = gdk_pixbuf_get_pixels (dest_pixbuf); |
103 | dest_rowstride = gdk_pixbuf_get_rowstride (dest_pixbuf); |
104 | |
105 | pixel_stride = (has_alpha)?4:3; |
106 | |
107 | s_y1 = 0; |
108 | s_yfrac = -dest_height/2; |
109 | while (s_y1 < source_height) { |
110 | s_y2 = s_y1 + dy; |
111 | s_yfrac += dy_frac; |
112 | if (s_yfrac > 0) { |
113 | s_y2++; |
114 | s_yfrac -= dest_height; |
115 | } |
116 | |
117 | s_x1 = 0; |
118 | s_xfrac = -dest_width/2; |
119 | while (s_x1 < source_width) { |
120 | s_x2 = s_x1 + dx; |
121 | s_xfrac += dx_frac; |
122 | if (s_xfrac > 0) { |
123 | s_x2++; |
124 | s_xfrac -= dest_width; |
125 | } |
126 | |
127 | /* Average block of [x1,x2[ x [y1,y2[ and store in dest */ |
128 | r = g = b = a = 0; |
129 | n_pixels = 0; |
130 | |
131 | src = src_pixels + s_y1 * source_rowstride + s_x1 * pixel_stride; |
132 | for (y = s_y1; y < s_y2; y++) { |
133 | xsrc = src; |
134 | if (has_alpha) { |
135 | for (x = 0; x < s_x2-s_x1; x++) { |
136 | n_pixels++; |
137 | |
138 | r += xsrc[3] * xsrc[0]; |
139 | g += xsrc[3] * xsrc[1]; |
140 | b += xsrc[3] * xsrc[2]; |
141 | a += xsrc[3]; |
142 | xsrc += 4; |
143 | } |
144 | } else { |
145 | for (x = 0; x < s_x2-s_x1; x++) { |
146 | n_pixels++; |
147 | r += *xsrc++; |
148 | g += *xsrc++; |
149 | b += *xsrc++; |
150 | } |
151 | } |
152 | src += source_rowstride; |
153 | } |
154 | |
155 | g_assert (n_pixels > 0); |
156 | |
157 | if (has_alpha) { |
158 | if (a != 0) { |
159 | *dest++ = r / a; |
160 | *dest++ = g / a; |
161 | *dest++ = b / a; |
162 | *dest++ = a / n_pixels; |
163 | } else { |
164 | *dest++ = 0; |
165 | *dest++ = 0; |
166 | *dest++ = 0; |
167 | *dest++ = 0; |
168 | } |
169 | } else { |
170 | *dest++ = r / n_pixels; |
171 | *dest++ = g / n_pixels; |
172 | *dest++ = b / n_pixels; |
173 | } |
174 | |
175 | s_x1 = s_x2; |
176 | } |
177 | s_y1 = s_y2; |
178 | dest += dest_rowstride - dest_width * pixel_stride; |
179 | } |
180 | |
181 | return dest_pixbuf; |
182 | } |
183 | #endif |
184 | |
185 | static char * |
186 | get_target_uri (GFile *file) |
187 | { |
188 | GFileInfo *info; |
189 | char *target; |
190 | |
191 | info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, flags: G_FILE_QUERY_INFO_NONE, NULL, NULL); |
192 | if (info == NULL) |
193 | return NULL; |
194 | target = g_strdup (str: g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI)); |
195 | g_object_unref (object: info); |
196 | |
197 | return target; |
198 | } |
199 | |
200 | static char * |
201 | get_target_path (GFile *input) |
202 | { |
203 | if (g_file_has_uri_scheme (file: input, uri_scheme: "trash" ) != FALSE || |
204 | g_file_has_uri_scheme (file: input, uri_scheme: "recent" ) != FALSE) { |
205 | GFile *file; |
206 | char *input_uri; |
207 | char *input_path; |
208 | |
209 | input_uri = get_target_uri (file: input); |
210 | file = g_file_new_for_uri (uri: input_uri); |
211 | g_free (mem: input_uri); |
212 | input_path = g_file_get_path (file); |
213 | g_object_unref (object: file); |
214 | return input_path; |
215 | } |
216 | return g_file_get_path (file: input); |
217 | } |
218 | |
219 | static const GOptionEntry entries[] = { |
220 | { "size" , 's', 0, G_OPTION_ARG_INT, &output_size, "Size of the thumbnail in pixels" , NULL }, |
221 | {"g-fatal-warnings" , 0, 0, G_OPTION_ARG_NONE, &g_fatal_warnings, "Make all warnings fatal" , NULL}, |
222 | { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, "[INPUT FILE] [OUTPUT FILE]" }, |
223 | { NULL } |
224 | }; |
225 | |
226 | int main (int argc, char **argv) |
227 | { |
228 | char *input_filename; |
229 | GdkPixbuf *pixbuf; |
230 | GError *error = NULL; |
231 | GOptionContext *context; |
232 | GFile *input; |
233 | const char *output; |
234 | |
235 | #ifdef THUMBNAILER_RETURNS_PIXBUF |
236 | /* Nothing */ |
237 | #elif THUMBNAILER_RETURNS_DATA |
238 | char *data = NULL; |
239 | gsize length; |
240 | #endif |
241 | |
242 | setlocale (LC_ALL, locale: "" ); |
243 | |
244 | #if !GLIB_CHECK_VERSION(2, 36, 0) |
245 | g_type_init (); |
246 | #endif |
247 | |
248 | /* Options parsing */ |
249 | context = g_option_context_new (THUMBNAILER_USAGE); |
250 | g_option_context_add_main_entries (context, entries, NULL); |
251 | |
252 | if (g_option_context_parse (context, argc: &argc, argv: &argv, error: &error) == FALSE) { |
253 | g_warning ("Couldn't parse command-line options: %s" , error->message); |
254 | g_error_free (error); |
255 | return 1; |
256 | } |
257 | |
258 | /* Set fatal warnings if required */ |
259 | if (g_fatal_warnings) { |
260 | GLogLevelFlags fatal_mask; |
261 | |
262 | fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK); |
263 | fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL; |
264 | g_log_set_always_fatal (fatal_mask); |
265 | } |
266 | |
267 | if (filenames == NULL || g_strv_length (str_array: filenames) != 2) { |
268 | g_print (format: "Expects an input and an output file\n" ); |
269 | return 1; |
270 | } |
271 | |
272 | input = g_file_new_for_commandline_arg (arg: filenames[0]); |
273 | input_filename = get_target_path (input); |
274 | g_object_unref (object: input); |
275 | |
276 | if (input_filename == NULL) { |
277 | g_warning ("Could not get file path for %s" , filenames[0]); |
278 | return 1; |
279 | } |
280 | |
281 | output = filenames[1]; |
282 | |
283 | #ifdef THUMBNAILER_RETURNS_PIXBUF |
284 | pixbuf = file_to_pixbuf (path: input_filename, destination_size: output_size, error: &error); |
285 | if (pixbuf != NULL) { |
286 | int width, height; |
287 | |
288 | width = gdk_pixbuf_get_width (pixbuf); |
289 | height = gdk_pixbuf_get_height (pixbuf); |
290 | |
291 | /* Handle naive thumbnailers that don't resize */ |
292 | if (output_size != 0 && |
293 | (height > output_size || width > output_size)) { |
294 | GdkPixbuf *scaled; |
295 | double scale; |
296 | |
297 | scale = (double)output_size / MAX (width, height); |
298 | |
299 | #if !GDK_PIXBUF_CHECK_VERSION(2,36,5) |
300 | scaled = gnome_desktop_thumbnail_scale_down_pixbuf (pixbuf, |
301 | floor (width * scale + 0.5), |
302 | floor (height * scale + 0.5)); |
303 | #else |
304 | scaled = gdk_pixbuf_scale_simple (src: pixbuf, |
305 | dest_width: floor (x: width * scale + 0.5), |
306 | dest_height: floor (x: height * scale + 0.5), |
307 | interp_type: GDK_INTERP_BILINEAR); |
308 | #endif |
309 | gdk_pixbuf_copy_options (src_pixbuf: pixbuf, dest_pixbuf: scaled); |
310 | g_object_unref (object: pixbuf); |
311 | pixbuf = scaled; |
312 | } |
313 | } |
314 | #elif THUMBNAILER_RETURNS_DATA |
315 | data = file_to_data (input_filename, &length, &error); |
316 | if (data) { |
317 | GInputStream *mem_stream; |
318 | |
319 | mem_stream = g_memory_input_stream_new_from_data (data, length, g_free); |
320 | pixbuf = gdk_pixbuf_new_from_stream_at_scale (mem_stream, output_size, -1, TRUE, NULL, &error); |
321 | g_object_unref (mem_stream); |
322 | } else { |
323 | pixbuf = NULL; |
324 | } |
325 | #else |
326 | #error "One of THUMBNAILER_RETURNS_PIXBUF or THUMBNAILER_RETURNS_DATA must be set" |
327 | #endif |
328 | g_free (mem: input_filename); |
329 | |
330 | if (!pixbuf) { |
331 | g_warning ("Could not thumbnail '%s': %s" , filenames[0], |
332 | error ? error->message : "Thumbnailer failed without returning an error" ); |
333 | g_clear_error (err: &error); |
334 | g_strfreev (str_array: filenames); |
335 | return 1; |
336 | } |
337 | |
338 | if (gdk_pixbuf_save (pixbuf, filename: output, type: "png" , error: &error, NULL) == FALSE) { |
339 | g_warning ("Couldn't save the thumbnail '%s' for file '%s': %s" , output, filenames[0], error->message); |
340 | g_error_free (error); |
341 | return 1; |
342 | } |
343 | |
344 | g_object_unref (object: pixbuf); |
345 | |
346 | return 0; |
347 | } |
348 | |