1 /*
2  * Photos - access, organize and share your photos on GNOME
3  * Copyright © 2016 – 2019 Red Hat, Inc.
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  */
18 
19 
20 #include "config.h"
21 
22 #include <stdio.h>
23 
24 #include <babl/babl.h>
25 #include <gegl.h>
26 #include <png.h>
27 
28 #include "photos-png-count.h"
29 #include "photos-operation-png-guess-sizes.h"
30 
31 
32 struct _PhotosOperationPngGuessSizes
33 {
34   GeglOperationSink parent_instance;
35   gboolean background;
36   gint bitdepth;
37   gint compression;
38   gsize sizes[2];
39 };
40 
41 enum
42 {
43   PROP_0,
44   PROP_BACKGROUND,
45   PROP_BITDEPTH,
46   PROP_COMPRESSION,
47   PROP_SIZE,
48   PROP_SIZE_1
49 };
50 
51 
52 G_DEFINE_TYPE (PhotosOperationPngGuessSizes, photos_operation_png_guess_sizes, GEGL_TYPE_OPERATION_SINK);
53 
54 
55 static gsize
photos_operation_png_guess_sizes_count(GeglBuffer * buffer,gint compression,gint bitdepth,gboolean background,gdouble zoom,gint src_x,gint src_y,gint width,gint height)56 photos_operation_png_guess_sizes_count (GeglBuffer *buffer,
57                                         gint compression,
58                                         gint bitdepth,
59                                         gboolean background,
60                                         gdouble zoom,
61                                         gint src_x,
62                                         gint src_y,
63                                         gint width,
64                                         gint height)
65 {
66   gint bpp;
67   gint i;
68   gint png_color_type;
69   gchar format_string[16];
70   const Babl *format;
71   const Babl *format_buffer;
72   gsize ret_val = 0;
73   gsize size;
74   guchar *pixels = NULL;
75   png_infop info_ptr = NULL;
76   png_structp png_ptr = NULL;
77 
78   format_buffer = gegl_buffer_get_format (buffer);
79   if (babl_format_has_alpha (format_buffer))
80     {
81       if (babl_format_get_n_components (format_buffer) != 2)
82         {
83           png_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
84           strcpy (format_string, "R'G'B'A ");
85         }
86       else
87         {
88           png_color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
89           strcpy (format_string, "Y'A ");
90         }
91     }
92   else
93     {
94       if (babl_format_get_n_components (format_buffer) != 1)
95         {
96           png_color_type = PNG_COLOR_TYPE_RGB;
97           strcpy (format_string, "R'G'B' ");
98         }
99       else
100         {
101           png_color_type = PNG_COLOR_TYPE_GRAY;
102           strcpy (format_string, "Y' ");
103         }
104     }
105 
106   if (bitdepth == 16)
107     strcat (format_string, "u16");
108   else
109     strcat (format_string, "u8");
110 
111   png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
112   if (png_ptr == NULL)
113     goto out;
114 
115   info_ptr = png_create_info_struct (png_ptr);
116   if (info_ptr == NULL)
117     goto out;
118 
119   if (setjmp (png_jmpbuf (png_ptr)))
120     {
121       ret_val = 0;
122       goto out;
123     }
124 
125   if (compression >= 0)
126     png_set_compression_level (png_ptr, compression);
127 
128   photos_png_init_count (png_ptr, &size);
129 
130   png_set_IHDR (png_ptr,
131                 info_ptr,
132                 width,
133                 height,
134                 bitdepth,
135                 png_color_type,
136                 PNG_INTERLACE_NONE,
137                 PNG_COMPRESSION_TYPE_BASE,
138                 PNG_FILTER_TYPE_DEFAULT);
139 
140   if (background)
141     {
142       png_color_16 white;
143 
144       if (png_color_type == PNG_COLOR_TYPE_RGB || png_color_type == PNG_COLOR_TYPE_RGB_ALPHA)
145         {
146           white.red = 0xff;
147           white.blue = 0xff;
148           white.green = 0xff;
149         }
150       else
151         {
152           white.gray = 0xff;
153         }
154 
155       png_set_bKGD (png_ptr, info_ptr, &white);
156     }
157 
158   png_write_info (png_ptr, info_ptr);
159 
160 #if BYTE_ORDER == LITTLE_ENDIAN
161   if (bitdepth > 8)
162     png_set_swap (png_ptr);
163 #endif
164 
165   format = babl_format (format_string);
166   bpp = babl_format_get_bytes_per_pixel (format);
167   pixels = g_malloc0 (width * bpp);
168 
169   for (i = 0; i < height; i++)
170     {
171       GeglRectangle rect;
172 
173       rect.x = src_x;
174       rect.y = src_y + i;
175       rect.width = width;
176       rect.height = 1;
177       gegl_buffer_get (buffer, &rect, zoom, format, pixels, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
178       png_write_rows (png_ptr, &pixels, 1);
179     }
180 
181   png_write_end (png_ptr, info_ptr);
182   ret_val = size;
183 
184  out:
185   g_free (pixels);
186   png_destroy_write_struct (&png_ptr, &info_ptr);
187   return ret_val;
188 }
189 
190 
191 static gboolean
photos_operation_png_guess_sizes_process(GeglOperation * operation,GeglBuffer * input,const GeglRectangle * roi,gint level)192 photos_operation_png_guess_sizes_process (GeglOperation *operation,
193                                           GeglBuffer *input,
194                                           const GeglRectangle *roi,
195                                           gint level)
196 {
197   PhotosOperationPngGuessSizes *self = PHOTOS_OPERATION_PNG_GUESS_SIZES (operation);
198   gsize i;
199 
200   for (i = 0; i < G_N_ELEMENTS (self->sizes); i++)
201     {
202       GeglRectangle roi_zoomed;
203       gdouble zoom = 1.0 / (gdouble) (1 << i);
204 
205       roi_zoomed.height = (gint) (zoom * roi->height + 0.5);
206       roi_zoomed.width = (gint) (zoom * roi->width + 0.5);
207       roi_zoomed.x = (gint) (zoom * roi->x + 0.5);
208       roi_zoomed.y = (gint) (zoom * roi->y + 0.5);
209 
210       self->sizes[i] = photos_operation_png_guess_sizes_count (input,
211                                                                self->compression,
212                                                                self->bitdepth,
213                                                                self->background,
214                                                                zoom,
215                                                                roi_zoomed.x,
216                                                                roi_zoomed.y,
217                                                                roi_zoomed.width,
218                                                                roi_zoomed.height);
219     }
220 
221   return TRUE;
222 }
223 
224 
225 static void
photos_operation_png_guess_sizes_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)226 photos_operation_png_guess_sizes_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
227 {
228   PhotosOperationPngGuessSizes *self = PHOTOS_OPERATION_PNG_GUESS_SIZES (object);
229 
230   switch (prop_id)
231     {
232     case PROP_BACKGROUND:
233       g_value_set_boolean (value, self->background);
234       break;
235 
236     case PROP_BITDEPTH:
237       g_value_set_int (value, self->bitdepth);
238       break;
239 
240     case PROP_COMPRESSION:
241       g_value_set_int (value, self->compression);
242       break;
243 
244     case PROP_SIZE:
245       g_value_set_uint64 (value, (guint64) self->sizes[0]);
246       break;
247 
248     case PROP_SIZE_1:
249       g_value_set_uint64 (value, (guint64) self->sizes[1]);
250       break;
251 
252     default:
253       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
254       break;
255     }
256 }
257 
258 
259 static void
photos_operation_png_guess_sizes_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)260 photos_operation_png_guess_sizes_set_property (GObject *object,
261                                                guint prop_id,
262                                                const GValue *value,
263                                                GParamSpec *pspec)
264 {
265   PhotosOperationPngGuessSizes *self = PHOTOS_OPERATION_PNG_GUESS_SIZES (object);
266 
267   switch (prop_id)
268     {
269     case PROP_BACKGROUND:
270       self->background = g_value_get_boolean (value);
271       break;
272 
273     case PROP_BITDEPTH:
274       self->bitdepth = g_value_get_int (value);
275       break;
276 
277     case PROP_COMPRESSION:
278       self->compression = g_value_get_int (value);
279       break;
280 
281     default:
282       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
283       break;
284     }
285 }
286 
287 
288 static void
photos_operation_png_guess_sizes_init(PhotosOperationPngGuessSizes * self)289 photos_operation_png_guess_sizes_init (PhotosOperationPngGuessSizes *self)
290 {
291 }
292 
293 
294 static void
photos_operation_png_guess_sizes_class_init(PhotosOperationPngGuessSizesClass * class)295 photos_operation_png_guess_sizes_class_init (PhotosOperationPngGuessSizesClass *class)
296 {
297   GObjectClass *object_class = G_OBJECT_CLASS (class);
298   GeglOperationClass *operation_class = GEGL_OPERATION_CLASS (class);
299   GeglOperationSinkClass *sink_class = GEGL_OPERATION_SINK_CLASS (class);
300 
301   operation_class->opencl_support = FALSE;
302   sink_class->needs_full = TRUE;
303 
304   object_class->get_property = photos_operation_png_guess_sizes_get_property;
305   object_class->set_property = photos_operation_png_guess_sizes_set_property;
306   sink_class->process = photos_operation_png_guess_sizes_process;
307 
308   g_object_class_install_property (object_class,
309                                    PROP_BACKGROUND,
310                                    g_param_spec_boolean ("background",
311                                                          "Background",
312                                                          "Set bKGD chunk information",
313                                                          TRUE,
314                                                          G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
315 
316   g_object_class_install_property (object_class,
317                                    PROP_BITDEPTH,
318                                    g_param_spec_int ("bitdepth",
319                                                      "Bitdepth",
320                                                      "Number of bits per channel — 8 and 16 are the currently "
321                                                      "accepted values",
322                                                      8,
323                                                      16,
324                                                      16,
325                                                      G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
326 
327   g_object_class_install_property (object_class,
328                                    PROP_COMPRESSION,
329                                    g_param_spec_int ("compression",
330                                                      "Compression",
331                                                      "PNG compression level (between -1 and 9)",
332                                                      -1,
333                                                      9,
334                                                      3,
335                                                      G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
336 
337   g_object_class_install_property (object_class,
338                                    PROP_SIZE,
339                                    g_param_spec_uint64 ("size",
340                                                         "Size (level=0)",
341                                                         "Approximate size in bytes after applying PNG compression"
342                                                         "at zoom=1.0",
343                                                         0,
344                                                         G_MAXSIZE,
345                                                         0,
346                                                         G_PARAM_READABLE));
347 
348   g_object_class_install_property (object_class,
349                                    PROP_SIZE_1,
350                                    g_param_spec_uint64 ("size-1",
351                                                         "Size (level=1)",
352                                                         "Approximate size in bytes after applying PNG compression"
353                                                         "at zoom=0.5",
354                                                         0,
355                                                         G_MAXSIZE,
356                                                         0,
357                                                         G_PARAM_READABLE));
358 
359   gegl_operation_class_set_keys (operation_class,
360                                  "name", "photos:png-guess-sizes",
361                                  "title", "PNG Guess Sizes",
362                                  "description", "Guesses the size of a GeglBuffer after applying PNG compression",
363                                  NULL);
364 }
365