libyui-gtk  2.44.9
ygdkmngloader.c
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 
5 /* YGdkMngLoader image loader */
6 // check the header file for information about this loader
7 
8 #include "ygdkmngloader.h"
9 #include <stdio.h>
10 #include <string.h>
11 #include <stdlib.h>
12 #include <assert.h>
13 #include <sys/mman.h>
14 
15 #include <gdk-pixbuf/gdk-pixbuf.h>
16 
17 /* A few chunks ids */
18 #define MNG_UINT_MHDR 0x4d484452L
19 #define MNG_UINT_BACK 0x4241434bL
20 #define MNG_UINT_PLTE 0x504c5445L
21 #define MNG_UINT_tRNS 0x74524e53L
22 #define MNG_UINT_IHDR 0x49484452L
23 #define MNG_UINT_IDAT 0x49444154L
24 #define MNG_UINT_IEND 0x49454e44L
25 #define MNG_UINT_MEND 0x4d454e44L
26 #define MNG_UINT_FRAM 0x4652414dL
27 #define MNG_UINT_LOOP 0x4c4f4f50L
28 #define MNG_UINT_ENDL 0x454e444cL
29 #define MNG_UINT_TERM 0x5445524dL
30 
31 //** Utilities to read the MNG file
32 
33 typedef struct DataStream {
34  const guint8 *data;
35  const long size;
36  long offset;
37 } DataStream;
38 static DataStream data_stream_constructor (const guint8 *raw_data, long size)
39 {
40  DataStream data = { raw_data, size, 0 };
41  return data;
42 }
43 
44 static gboolean read_signature (DataStream *data)
45 {
46  if (data->offset+8 > data->size)
47  return FALSE;
48  data->offset += 8;
49  return memcmp (data->data + data->offset-8, "\212MNG\r\n\032\n", 8) == 0;
50 }
51 
52 static gboolean read_uint8 (DataStream *data, guint8 *value)
53 {
54  if (data->offset+1 > data->size)
55  return FALSE;
56  *value = data->data [data->offset++];
57  return TRUE;
58 }
59 
60 static gboolean read_uint32 (DataStream *data, guint32 *value)
61 {
62  if (data->offset+4 > data->size)
63  return FALSE;
64  *value = data->data[data->offset+0] << 24;
65  *value |= data->data[data->offset+1] << 16;
66  *value |= data->data[data->offset+2] << 8;
67  *value |= data->data[data->offset+3];
68  data->offset += 4;
69  return TRUE;
70 }
71 
72 static gboolean read_data (DataStream *data, guint32 size, GdkPixbufLoader *loader,
73  GError **error)
74 {
75  if (data->offset+size > data->size)
76  {
77  g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
78  "Unexpected end of file when reading PNG chunk");
79  return FALSE;
80  }
81  gboolean ret;
82  ret = gdk_pixbuf_loader_write (loader, data->data + data->offset, size, error);
83  data->offset += size;
84  return ret;
85 }
86 
87 static void image_prepared_cb (GdkPixbufLoader *loader, YGdkMngPixbuf *mng_pixbuf)
88 {
89  GdkPixbuf *pix = gdk_pixbuf_loader_get_pixbuf (loader);
90  mng_pixbuf->frames = g_list_append (mng_pixbuf->frames, pix);
91 }
92 
93 //** YGdkMngPixbuf
94 
95 G_DEFINE_TYPE (YGdkMngPixbuf, ygdk_mng_pixbuf, GDK_TYPE_PIXBUF_ANIMATION)
96 
97 static void ygdk_mng_pixbuf_init (YGdkMngPixbuf *pixbuf)
98 {
99 }
100 
101 gboolean ygdk_mng_pixbuf_is_file_mng (const gchar *filename)
102 {
103  FILE *file = fopen (filename, "rb");
104  if (!file)
105  goto is_file_mng_failed;
106 
107  guchar raw_data [8];
108  if (fread (raw_data, 1, 8, file) < 8)
109  goto is_file_mng_failed;
110 
111  gboolean ret = ygdk_mng_pixbuf_is_data_mng (raw_data, 8);
112  fclose (file);
113  return ret;
114 
115 is_file_mng_failed:
116  if (file)
117  fclose (file);
118  return FALSE;
119 }
120 
121 gboolean ygdk_mng_pixbuf_is_data_mng (const guint8 *raw_data, long size)
122 {
123  DataStream data = data_stream_constructor (raw_data, size);
124  return read_signature (&data);
125 }
126 
127 // (void) error is to overcome -Werror=unused-but-set-variable
128 #define SET_ERROR(msg) { error = TRUE; (void) error; \
129  g_set_error (error_msg, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, msg); }
130 
131 GdkPixbufAnimation *ygdk_mng_pixbuf_new_from_file (const gchar *filename,
132  GError **error_msg)
133 {
134  gboolean error = FALSE;
135  FILE *file = fopen (filename, "rb");
136  if (!file) {
137  error = TRUE;
138  SET_ERROR ("Could not open specified file")
139  return NULL;
140  }
141 
142  fseek (file, 0, SEEK_END);
143  long file_size = ftell (file);
144  fseek (file, 0, SEEK_SET);
145 
146  GdkPixbufAnimation *mng_pixbuf = 0;
147  guchar *data = mmap (NULL, file_size, PROT_READ, MAP_PRIVATE,
148  fileno (file), 0);
149  if (data == MAP_FAILED)
150  SET_ERROR ("Could not map file")
151  else {
152  mng_pixbuf = ygdk_mng_pixbuf_new_from_data (data, file_size, error_msg);
153  munmap (data, file_size);
154  }
155  fclose (file);
156  return mng_pixbuf;
157 }
158 
159 GdkPixbufAnimation *ygdk_mng_pixbuf_new_from_data (const guint8 *raw_data, long size,
160  GError **error_msg)
161 {
162  DataStream data = data_stream_constructor (raw_data, size);
163 
164  gboolean error = FALSE;
165  if (!read_signature (&data)) {
166  SET_ERROR ("Not a MNG file")
167  return NULL;
168  }
169 
170  YGdkMngPixbuf *mng_pixbuf = g_object_new (YGDK_TYPE_MNG_PIXBUF, NULL);
171  mng_pixbuf->iteration_max = 0x7fffffff;
172 
173  guint32 chunk_size, chunk_id;
174  long chunk_offset;
175  GdkPixbufLoader *loader = NULL; /* currently loading... */
176  gboolean first_read = TRUE;
177 
178  do {
179  error = !read_uint32 (&data, &chunk_size);
180  error = error || !read_uint32 (&data, &chunk_id);
181  if (error) {
182  SET_ERROR ("Unexpected end of file on new chunk")
183  break;
184  }
185  chunk_offset = data.offset + chunk_size + 4/*CRC*/;
186 
187  if (first_read && chunk_id != MNG_UINT_MHDR)
188  {
189  SET_ERROR ("MHDR chunk must come first")
190  break;
191  }
192 
193  // not currently reading a PNG stream
194  if (!loader)
195  {
196  switch (chunk_id)
197  {
198  case MNG_UINT_MHDR:
199  if (!first_read)
200  {
201  SET_ERROR ("Only one MHDR chunk allowed")
202  break;
203  }
204 
205  if (chunk_size == 7*4)
206  {
207  // Read MHDR chunk data
208  error = !read_uint32 (&data, &mng_pixbuf->frame_width);
209  error = error || !read_uint32 (&data, &mng_pixbuf->frame_height);
210  error = error || !read_uint32 (&data, &mng_pixbuf->ticks_per_second);
211  if (error)
212  SET_ERROR ("Unexpected end of file on MHDR chunk")
213  /* Next atttributes: Nominal_layer_count, Nominal_frame_count,
214  Nominal_play_time, Simplicity_profile */
215  else if (mng_pixbuf->frame_width <= 0 ||
216  mng_pixbuf->frame_height <= 0 ||
217  mng_pixbuf->ticks_per_second < 0)
218  SET_ERROR ("Invalid MHDR parameter")
219 //fprintf(stderr, "ticks per second: %d\n", mng_pixbuf->ticks_per_second);
220  }
221  else
222  SET_ERROR ("MHDR chunk must be 28 bytes long")
223  break;
224  case MNG_UINT_IHDR:
225  loader = gdk_pixbuf_loader_new_with_type ("png", NULL);
226  g_signal_connect (G_OBJECT (loader), "area-prepared",
227  G_CALLBACK (image_prepared_cb), mng_pixbuf);
228  gdk_pixbuf_loader_set_size (loader, mng_pixbuf->frame_width,
229  mng_pixbuf->frame_height);
230 
231  {
232  const guchar sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
233  if(!gdk_pixbuf_loader_write (loader, sig, 8, error_msg))
234  {
235  error = TRUE;
236  break;
237  }
238  }
239  break;
240  case MNG_UINT_TERM:
241  if (chunk_size > 1)
242  {
243  // Read TERM chunk data
244  guint8 t = 0;
245  error = !read_uint8 (&data, &t); // term action
246  if (t == 3 && chunk_size == 2+8)
247  {
248  error = error || !read_uint8 (&data, &t); // after term action
249  error = error || !read_uint32 (&data, &mng_pixbuf->last_frame_delay);
250  error = error || !read_uint32 (&data, &mng_pixbuf->iteration_max);
251  if (error)
252  SET_ERROR ("Unexpected end of file on TERM chunk")
253  }
254  else
255  SET_ERROR ("TERM chunk must be 10 bytes when term action is 3")
256  }
257  else
258  SET_ERROR ("TERM chunk must have at least 1 byte")
259  break;
260  case MNG_UINT_BACK:
261  // TODO:
262  break;
263  case MNG_UINT_IDAT:
264  case MNG_UINT_IEND:
265  if (loader != NULL)
266  SET_ERROR ("Corrupted PNG chunk closures")
267  break;
268  case MNG_UINT_MEND:
269  default:
270  break;
271  }
272  }
273 
274  if (error)
275  break;
276 
277  // loading a PNG stream
278  if (loader)
279  {
280  data.offset -= 8;
281  if (!read_data (&data, chunk_size+8+4, loader, error_msg))
282  error = TRUE;
283  else if (chunk_id == MNG_UINT_IEND)
284  {
285  if (!gdk_pixbuf_loader_close (loader, error_msg))
286  {
287  error = TRUE;
288  break;
289  }
290  loader = NULL;
291  }
292  }
293 
294  data.offset = chunk_offset;
295  first_read = FALSE;
296  } while (chunk_id != MNG_UINT_MEND && !error);
297 
298  if (error)
299  {
300  g_object_unref (G_OBJECT (mng_pixbuf));
301  return NULL;
302  }
303  return GDK_PIXBUF_ANIMATION (mng_pixbuf);
304 }
305 
306 #undef SET_ERROR
307 
308 static gboolean ygdk_mng_pixbuf_is_static_image (GdkPixbufAnimation *anim)
309 {
310  YGdkMngPixbuf *mng_anim = YGDK_MNG_PIXBUF (anim);
311  return g_list_length (mng_anim->frames) == 1;
312 }
313 
314 static GdkPixbuf *ygdk_mng_pixbuf_get_static_image (GdkPixbufAnimation *anim)
315 {
316  YGdkMngPixbuf *mng_anim = YGDK_MNG_PIXBUF (anim);
317  return g_list_nth_data (mng_anim->frames, 0);
318 }
319 
320 static void ygdk_mng_pixbuf_get_size (GdkPixbufAnimation *anim, int *width, int *height)
321 {
322  YGdkMngPixbuf *mng_anim = YGDK_MNG_PIXBUF (anim);
323  if (width) *width = mng_anim->frame_width;
324  if (height) *height = mng_anim->frame_height;
325 }
326 
327 static GdkPixbufAnimationIter *ygdk_mng_pixbuf_get_iter (GdkPixbufAnimation *anim,
328  const GTimeVal *start_time)
329 {
330  YGdkMngPixbufIter *iter = g_object_new (YGDK_TYPE_MNG_PIXBUF_ITER, NULL);
331  iter->mng_pixbuf = YGDK_MNG_PIXBUF( anim );
332  iter->cur_frame = 0;
333  return GDK_PIXBUF_ANIMATION_ITER( iter );
334 }
335 
336 static void ygdk_mng_pixbuf_class_init (YGdkMngPixbufClass *klass)
337 {
338  ygdk_mng_pixbuf_parent_class = g_type_class_peek_parent (klass);
339 
340  GdkPixbufAnimationClass *pixbuf_class = GDK_PIXBUF_ANIMATION_CLASS (klass);
341  pixbuf_class->is_static_image = ygdk_mng_pixbuf_is_static_image;
342  pixbuf_class->get_static_image = ygdk_mng_pixbuf_get_static_image;
343  pixbuf_class->get_size = ygdk_mng_pixbuf_get_size;
344  pixbuf_class->get_iter = ygdk_mng_pixbuf_get_iter;
345 }
346 
347 //** YGdkMngPixbufIter
348 
349 G_DEFINE_TYPE (YGdkMngPixbufIter, ygdk_mng_pixbuf_iter, GDK_TYPE_PIXBUF_ANIMATION_ITER)
350 
351 static void ygdk_mng_pixbuf_iter_init (YGdkMngPixbufIter *iter)
352 {
353 }
354 
355 static GdkPixbuf *ygdk_mng_pixbuf_iter_get_pixbuf (GdkPixbufAnimationIter *iter)
356 {
357  YGdkMngPixbufIter *mng_iter = YGDK_MNG_PIXBUF_ITER (iter);
358  return g_list_nth_data (mng_iter->mng_pixbuf->frames, mng_iter->cur_frame);
359 }
360 
361 static gboolean ygdk_mng_pixbuf_iter_on_currently_loading_frame (GdkPixbufAnimationIter *iter)
362 {
363  return FALSE;
364 }
365 
366 static int ygdk_mng_pixbuf_iter_get_delay_time (GdkPixbufAnimationIter *iter)
367 {
368  YGdkMngPixbufIter *mng_iter = YGDK_MNG_PIXBUF_ITER (iter);
369  int delay = 1000.0 / mng_iter->mng_pixbuf->ticks_per_second;
370  if (mng_iter->cur_frame == g_list_length (mng_iter->mng_pixbuf->frames)-1)
371  delay += mng_iter->mng_pixbuf->last_frame_delay;
372  return delay;
373 }
374 
375 static gboolean ygdk_mng_pixbuf_iter_advance (GdkPixbufAnimationIter *iter,
376  const GTimeVal *current_time)
377 {
378  YGdkMngPixbufIter *mng_iter = YGDK_MNG_PIXBUF_ITER (iter);
379  YGdkMngPixbuf *mng_pixbuf = mng_iter->mng_pixbuf;
380  if (!mng_pixbuf->frames)
381  return FALSE;
382 
383  gboolean can_advance = TRUE;
384  int frames_len = g_list_length (mng_pixbuf->frames);
385  if (mng_iter->cur_frame+1 == frames_len)
386  {
387  if (mng_pixbuf->iteration_max == 0x7fffffff ||
388  mng_iter->cur_iteration < mng_pixbuf->iteration_max)
389  mng_iter->cur_iteration++;
390  else
391  can_advance = FALSE;
392  }
393 
394  if (can_advance)
395  mng_iter->cur_frame = (mng_iter->cur_frame+1) % frames_len;
396  return can_advance;
397 }
398 
399 static void ygdk_mng_pixbuf_iter_class_init (YGdkMngPixbufIterClass *klass)
400 {
401  ygdk_mng_pixbuf_iter_parent_class = g_type_class_peek_parent (klass);
402 
403  GdkPixbufAnimationIterClass *iter_class = GDK_PIXBUF_ANIMATION_ITER_CLASS (klass);
404  iter_class->get_delay_time = ygdk_mng_pixbuf_iter_get_delay_time;
405  iter_class->get_pixbuf = ygdk_mng_pixbuf_iter_get_pixbuf;
406  iter_class->on_currently_loading_frame = ygdk_mng_pixbuf_iter_on_currently_loading_frame;
407  iter_class->advance = ygdk_mng_pixbuf_iter_advance;
408 }
409