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