1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 
3 /* Copyright (C) 2018-2021 Hans Petter Jansson
4  *
5  * This file is part of Chafa, a program that turns images into character art.
6  *
7  * Chafa is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License as published
9  * by the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Chafa is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with Chafa.  If not, see <http://www.gnu.org/licenses/>. */
19 
20 #include "config.h"
21 #include <assert.h>
22 #include <errno.h>
23 #include <stdbool.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 
31 #include <libnsgif.h>
32 #include "gif-loader.h"
33 
34 #define BYTES_PER_PIXEL 4
35 #define MAX_IMAGE_BYTES (128 * 1024 * 1024)
36 
37 struct GifLoader
38 {
39     FileMapping *mapping;
40     const guint8 *file_data;
41     size_t file_data_len;
42     gif_animation gif;
43     gif_result code;
44     gint current_frame_index;
45     guint gif_is_initialized : 1;
46     guint frame_is_decoded : 1;
47 };
48 
49 static void *
bitmap_create(int width,int height)50 bitmap_create (int width, int height)
51 {
52     /* ensure a stupidly large bitmap is not created */
53     if (((long long) width * (long long) height) > (MAX_IMAGE_BYTES/BYTES_PER_PIXEL))
54         return NULL;
55 
56     return g_malloc0 (width * height * BYTES_PER_PIXEL);
57 }
58 
59 static void
bitmap_set_opaque(void * bitmap,bool opaque)60 bitmap_set_opaque (void *bitmap, bool opaque)
61 {
62     (void) opaque;  /* unused */
63     (void) bitmap;  /* unused */
64     g_assert (bitmap);
65 }
66 
67 static bool
bitmap_test_opaque(void * bitmap)68 bitmap_test_opaque (void *bitmap)
69 {
70     (void) bitmap;  /* unused */
71     g_assert (bitmap != NULL);
72     return false;
73 }
74 
75 static unsigned char *
bitmap_get_buffer(void * bitmap)76 bitmap_get_buffer (void *bitmap)
77 {
78     g_assert (bitmap != NULL);
79     return bitmap;
80 }
81 
82 static void
bitmap_destroy(void * bitmap)83 bitmap_destroy (void *bitmap)
84 {
85     g_assert (bitmap != NULL);
86     g_free (bitmap);
87 }
88 
89 static void
bitmap_modified(void * bitmap)90 bitmap_modified (void *bitmap)
91 {
92     (void) bitmap;  /* unused */
93     g_assert (bitmap != NULL);
94 }
95 
96 static GifLoader *
gif_loader_new(void)97 gif_loader_new (void)
98 {
99     return g_new0 (GifLoader, 1);
100 }
101 
102 GifLoader *
gif_loader_new_from_mapping(FileMapping * mapping)103 gif_loader_new_from_mapping (FileMapping *mapping)
104 {
105     gif_bitmap_callback_vt bitmap_callbacks =
106     {
107         bitmap_create,
108         bitmap_destroy,
109         bitmap_get_buffer,
110         bitmap_set_opaque,
111         bitmap_test_opaque,
112         bitmap_modified
113     };
114     gif_result code;
115     GifLoader *loader = NULL;
116     gboolean success = FALSE;
117 
118     g_return_val_if_fail (mapping != NULL, NULL);
119 
120     if (!file_mapping_has_magic (mapping, 0, "GIF89a", 6))
121         goto out;
122 
123     loader = gif_loader_new ();
124     loader->mapping = mapping;
125 
126     loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len);
127     if (!loader->file_data)
128         goto out;
129 
130     gif_create (&loader->gif, &bitmap_callbacks);
131     loader->gif_is_initialized = TRUE;
132 
133     do
134     {
135         code = gif_initialise (&loader->gif, loader->file_data_len, (gpointer) loader->file_data);
136 
137         if (code != GIF_OK && code != GIF_WORKING)
138             goto out;
139     }
140     while (code != GIF_OK);
141 
142     success = TRUE;
143 
144 out:
145     if (!success)
146     {
147         if (loader)
148         {
149             if (loader->gif_is_initialized)
150                 gif_finalise (&loader->gif);
151 
152             g_free (loader);
153             loader = NULL;
154         }
155     }
156 
157     return loader;
158 }
159 
160 void
gif_loader_destroy(GifLoader * loader)161 gif_loader_destroy (GifLoader *loader)
162 {
163     if (loader->mapping)
164         file_mapping_destroy (loader->mapping);
165 
166     if (loader->gif_is_initialized)
167         gif_finalise (&loader->gif);
168 
169     g_free (loader);
170 }
171 
172 void
gif_loader_get_geometry(GifLoader * loader,gint * width_out,gint * height_out)173 gif_loader_get_geometry (GifLoader *loader, gint *width_out, gint *height_out)
174 {
175     g_return_if_fail (loader != NULL);
176     g_return_if_fail (loader->gif_is_initialized);
177 
178     *width_out = loader->gif.width;
179     *height_out = loader->gif.height;
180 }
181 
182 gint
gif_loader_get_n_frames(GifLoader * loader)183 gif_loader_get_n_frames (GifLoader *loader)
184 {
185     g_return_val_if_fail (loader != NULL, 0);
186     g_return_val_if_fail (loader->gif_is_initialized, 0);
187 
188     return loader->gif.frame_count;
189 }
190 
191 const guint8 *
gif_loader_get_frame_data(GifLoader * loader,gint * post_frame_delay_hs_out)192 gif_loader_get_frame_data (GifLoader *loader, gint *post_frame_delay_hs_out)
193 {
194     g_return_val_if_fail (loader != NULL, NULL);
195     g_return_val_if_fail (loader->gif_is_initialized, NULL);
196 
197     if (!loader->frame_is_decoded)
198     {
199         gif_result code = gif_decode_frame (&loader->gif, loader->current_frame_index);
200         if (code != GIF_OK)
201             return NULL;
202     }
203 
204     loader->frame_is_decoded = TRUE;
205 
206     if (post_frame_delay_hs_out)
207         *post_frame_delay_hs_out = loader->gif.frames [loader->current_frame_index].frame_delay;
208     return loader->gif.frame_image;
209 }
210 
211 void
gif_loader_first_frame(GifLoader * loader)212 gif_loader_first_frame (GifLoader *loader)
213 {
214     g_return_if_fail (loader != NULL);
215     g_return_if_fail (loader->gif_is_initialized);
216 
217     if (loader->current_frame_index == 0)
218         return;
219 
220     loader->current_frame_index = 0;
221     loader->frame_is_decoded = FALSE;
222 }
223 
224 gboolean
gif_loader_next_frame(GifLoader * loader)225 gif_loader_next_frame (GifLoader *loader)
226 {
227     g_return_val_if_fail (loader != NULL, FALSE);
228     g_return_val_if_fail (loader->gif_is_initialized, FALSE);
229 
230     if (loader->current_frame_index + 1 >= (gint) loader->gif.frame_count)
231         return FALSE;
232 
233     loader->current_frame_index++;
234     loader->frame_is_decoded = FALSE;
235     return TRUE;
236 }
237