1 /*
2  * SHP loading file filter for The GIMP version 2.x.
3  *
4  * (C) 2000-2001 Tristan Tarrant
5  * (C) 2001-2004 Willem Jan Palenstijn
6  *
7  * You can find the most recent version of this file in the Exult sources,
8  * available from http://exult.sf.net/
9  */
10 
11 #ifdef HAVE_CONFIG_H
12 #include "config.h"
13 #endif
14 
15 #include "databuf.h"
16 #include "ibuf8.h"
17 #include "ignore_unused_variable_warning.h"
18 #include "U7obj.h"
19 #include "utils.h"
20 #include "vgafile.h"
21 
22 #include <cctype>
23 #include <fstream>
24 #include <iostream>
25 #include <string>
26 #include <vector>
27 
28 #ifdef __GNUC__
29 #pragma GCC diagnostic push
30 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
31 #endif  // __GNUC__
32 #include <gtk/gtk.h>
33 #ifdef __GNUC__
34 #pragma GCC diagnostic pop
35 #endif  // __GNUC__
36 
37 #include <libgimp/gimp.h>
38 #include <libgimp/gimpui.h>
39 
40 /* Declare some local functions.
41  */
42 static void   query();
43 static void   run(const gchar      *name,
44                   gint              nparams,
45                   const GimpParam  *param,
46                   gint             *nreturn_vals,
47                   GimpParam       **return_vals);
48 static void   load_palette(const std::string& filename);
49 static void   choose_palette();
50 static gint32 load_image(gchar  *filename);
51 static gint32 save_image(gchar  *filename,
52                          gint32  image_ID,
53                          gint32  drawable_ID,
54                          gint32  orig_image_ID);
55 static GimpRunMode run_mode;
56 
57 static guchar   gimp_cmap[768] = {
58 	0x00, 0x00, 0x00,	0xF8, 0xF0, 0xCC,	0xF4, 0xE4, 0xA4,	0xF0, 0xDC, 0x78,
59 	0xEC, 0xD0, 0x50,	0xEC, 0xC8, 0x28,	0xD8, 0xAC, 0x20,	0xC4, 0x94, 0x18,
60 	0xB0, 0x80, 0x10,	0x9C, 0x68, 0x0C,	0x88, 0x54, 0x08,	0x74, 0x44, 0x04,
61 	0x60, 0x30, 0x00,	0x4C, 0x24, 0x00,	0x38, 0x14, 0x00,	0xF8, 0xFC, 0xFC,
62 	0xFC, 0xD8, 0xD8,	0xFC, 0xB8, 0xB8,	0xFC, 0x98, 0x9C,	0xFC, 0x78, 0x80,
63 	0xFC, 0x58, 0x64,	0xFC, 0x38, 0x4C,	0xFC, 0x1C, 0x34,	0xDC, 0x14, 0x28,
64 	0xC0, 0x0C, 0x1C,	0xA4, 0x08, 0x14,	0x88, 0x04, 0x0C,	0x6C, 0x00, 0x04,
65 	0x50, 0x00, 0x00,	0x34, 0x00, 0x00,	0x18, 0x00, 0x00,	0xFC, 0xEC, 0xD8,
66 	0xFC, 0xDC, 0xB8,	0xFC, 0xCC, 0x98,	0xFC, 0xBC, 0x7C,	0xFC, 0xAC, 0x5C,
67 	0xFC, 0x9C, 0x3C,	0xFC, 0x8C, 0x1C,	0xFC, 0x7C, 0x00,	0xE0, 0x6C, 0x00,
68 	0xC0, 0x60, 0x00,	0xA4, 0x50, 0x00,	0x88, 0x44, 0x00,	0x6C, 0x34, 0x00,
69 	0x50, 0x24, 0x00,	0x34, 0x18, 0x00,	0x18, 0x08, 0x00,	0xFC, 0xFC, 0xD8,
70 	0xF4, 0xF4, 0x9C,	0xEC, 0xEC, 0x60,	0xE4, 0xE4, 0x2C,	0xDC, 0xDC, 0x00,
71 	0xC0, 0xC0, 0x00,	0xA4, 0xA4, 0x00,	0x88, 0x88, 0x00,	0x6C, 0x6C, 0x00,
72 	0x50, 0x50, 0x00,	0x34, 0x34, 0x00,	0x18, 0x18, 0x00,	0xD8, 0xFC, 0xD8,
73 	0xB0, 0xFC, 0xAC,	0x8C, 0xFC, 0x80,	0x6C, 0xFC, 0x54,	0x50, 0xFC, 0x28,
74 	0x38, 0xFC, 0x00,	0x28, 0xDC, 0x00,	0x1C, 0xC0, 0x00,	0x14, 0xA4, 0x00,
75 	0x0C, 0x88, 0x00,	0x04, 0x6C, 0x00,	0x00, 0x50, 0x00,	0x00, 0x34, 0x00,
76 	0x00, 0x18, 0x00,	0xD4, 0xD8, 0xFC,	0xB8, 0xB8, 0xFC,	0x98, 0x98, 0xFC,
77 	0x7C, 0x7C, 0xFC,	0x5C, 0x5C, 0xFC,	0x3C, 0x3C, 0xFC,	0x00, 0x00, 0xFC,
78 	0x00, 0x00, 0xE0,	0x00, 0x00, 0xC0,	0x00, 0x00, 0xA4,	0x00, 0x00, 0x88,
79 	0x00, 0x00, 0x6C,	0x00, 0x00, 0x50,	0x00, 0x00, 0x34,	0x00, 0x00, 0x18,
80 	0xE8, 0xC8, 0xE8,	0xD4, 0x98, 0xD4,	0xC4, 0x6C, 0xC4,	0xB0, 0x48, 0xB0,
81 	0xA0, 0x28, 0xA0,	0x8C, 0x10, 0x8C,	0x7C, 0x00, 0x7C,	0x6C, 0x00, 0x6C,
82 	0x60, 0x00, 0x60,	0x50, 0x00, 0x50,	0x44, 0x00, 0x44,	0x34, 0x00, 0x34,
83 	0x24, 0x00, 0x24,	0x18, 0x00, 0x18,	0xF4, 0xE8, 0xE4,	0xEC, 0xDC, 0xD4,
84 	0xE4, 0xCC, 0xC0,	0xE0, 0xC0, 0xB0,	0xD8, 0xB0, 0xA0,	0xD0, 0xA4, 0x90,
85 	0xC8, 0x98, 0x80,	0xC4, 0x8C, 0x74,	0xAC, 0x7C, 0x64,	0x98, 0x6C, 0x58,
86 	0x80, 0x5C, 0x4C,	0x6C, 0x4C, 0x3C,	0x54, 0x3C, 0x30,	0x3C, 0x2C, 0x24,
87 	0x28, 0x1C, 0x14,	0x10, 0x0C, 0x08,	0xEC, 0xEC, 0xEC,	0xDC, 0xDC, 0xDC,
88 	0xCC, 0xCC, 0xCC,	0xBC, 0xBC, 0xBC,	0xAC, 0xAC, 0xAC,	0x9C, 0x9C, 0x9C,
89 	0x8C, 0x8C, 0x8C,	0x7C, 0x7C, 0x7C,	0x6C, 0x6C, 0x6C,	0x60, 0x60, 0x60,
90 	0x50, 0x50, 0x50,	0x44, 0x44, 0x44,	0x34, 0x34, 0x34,	0x24, 0x24, 0x24,
91 	0x18, 0x18, 0x18,	0x08, 0x08, 0x08,	0xE8, 0xE0, 0xD4,	0xD8, 0xC8, 0xB0,
92 	0xC8, 0xB0, 0x90,	0xB8, 0x98, 0x70,	0xA8, 0x84, 0x58,	0x98, 0x70, 0x40,
93 	0x88, 0x5C, 0x2C,	0x7C, 0x4C, 0x18,	0x6C, 0x3C, 0x0C,	0x5C, 0x34, 0x0C,
94 	0x4C, 0x2C, 0x0C,	0x3C, 0x24, 0x0C,	0x2C, 0x1C, 0x08,	0x20, 0x14, 0x08,
95 	0xEC, 0xE8, 0xE4,	0xDC, 0xD4, 0xD0,	0xCC, 0xC4, 0xBC,	0xBC, 0xB0, 0xAC,
96 	0xAC, 0xA0, 0x98,	0x9C, 0x90, 0x88,	0x8C, 0x80, 0x78,	0x7C, 0x70, 0x68,
97 	0x6C, 0x60, 0x5C,	0x60, 0x54, 0x50,	0x50, 0x48, 0x44,	0x44, 0x3C, 0x38,
98 	0x34, 0x30, 0x2C,	0x24, 0x20, 0x20,	0x18, 0x14, 0x14,	0xE0, 0xE8, 0xD4,
99 	0xC8, 0xD4, 0xB4,	0xB4, 0xC0, 0x98,	0x9C, 0xAC, 0x7C,	0x88, 0x98, 0x60,
100 	0x70, 0x84, 0x4C,	0x5C, 0x70, 0x38,	0x4C, 0x5C, 0x28,	0x40, 0x50, 0x20,
101 	0x38, 0x44, 0x1C,	0x30, 0x3C, 0x18,	0x28, 0x30, 0x14,	0x20, 0x24, 0x10,
102 	0x18, 0x1C, 0x08,	0x0C, 0x10, 0x04,	0xEC, 0xD8, 0xCC,	0xDC, 0xB8, 0xA0,
103 	0xCC, 0x98, 0x7C,	0xBC, 0x80, 0x5C,	0xAC, 0x64, 0x3C,	0x9C, 0x50, 0x24,
104 	0x8C, 0x3C, 0x0C,	0x7C, 0x2C, 0x00,	0x6C, 0x24, 0x00,	0x60, 0x20, 0x00,
105 	0x50, 0x1C, 0x00,	0x44, 0x14, 0x00,	0x34, 0x10, 0x00,	0x24, 0x0C, 0x00,
106 	0xF0, 0xF0, 0xFC,	0xE4, 0xE4, 0xFC,	0xD8, 0xD8, 0xFC,	0xCC, 0xCC, 0xFC,
107 	0xC0, 0xC0, 0xFC,	0xB4, 0xB4, 0xFC,	0xA8, 0xA8, 0xFC,	0x9C, 0x9C, 0xFC,
108 	0x84, 0xD0, 0x00,	0x84, 0xB0, 0x00,	0x7C, 0x94, 0x00,	0x68, 0x78, 0x00,
109 	0x50, 0x58, 0x00,	0x3C, 0x40, 0x00,	0x2C, 0x24, 0x00,	0x1C, 0x08, 0x00,
110 	0x20, 0x00, 0x00,	0xEC, 0xD8, 0xC4,	0xDC, 0xC0, 0xB4,	0xCC, 0xB4, 0xA0,
111 	0xBC, 0x9C, 0x94,	0xAC, 0x90, 0x80,	0x9C, 0x84, 0x74,	0x8C, 0x74, 0x64,
112 	0x7C, 0x64, 0x58,	0x6C, 0x54, 0x4C,	0x60, 0x48, 0x44,	0x50, 0x40, 0x38,
113 	0x44, 0x34, 0x2C,	0x34, 0x2C, 0x24,	0x24, 0x18, 0x18,	0x18, 0x10, 0x10,
114 	0xFC, 0xF8, 0xFC,	0xAC, 0xD4, 0xF0,	0x70, 0xAC, 0xE4,	0x34, 0x8C, 0xD8,
115 	0x00, 0x6C, 0xD0,	0x30, 0x8C, 0xD8,	0x6C, 0xB0, 0xE4,	0xB0, 0xD4, 0xF0,
116 	0xFC, 0xFC, 0xF8,	0xFC, 0xEC, 0x40,	0xFC, 0xC0, 0x28,	0xFC, 0x8C, 0x10,
117 	0xFC, 0x50, 0x00,	0xC8, 0x38, 0x00,	0x98, 0x28, 0x00,	0x68, 0x18, 0x00,
118 	0x7C, 0xDC, 0x7C,	0x44, 0xB4, 0x44,	0x18, 0x90, 0x18,	0x00, 0x6C, 0x00,
119 	0xF8, 0xB8, 0xFC,	0xFC, 0x64, 0xEC,	0xFC, 0x00, 0xB4,	0xCC, 0x00, 0x70,
120 	0xFC, 0xFC, 0x00,	0x00, 0x00, 0xFF,	0x00, 0xFC, 0x00,	0xFC, 0x00, 0x00,
121 	0xFC, 0xFC, 0xFC,	0x61, 0x61, 0x61,	0xC0, 0xC0, 0xC0,	0xFC, 0x00, 0xF1
122 };
123 
124 GimpPlugInInfo PLUG_IN_INFO = {
125 	nullptr,  /* init_proc  */
126 	nullptr,  /* quit_proc  */
127 	query, /* query_proc */
128 	run,   /* run_proc   */
129 };
130 
131 struct u7frame {
132 	guchar *pixels;
133 	size_t datalen;
134 	gint16 leftX;
135 	gint16 leftY;
136 	gint16 rightX;
137 	gint16 rightY;
138 	gint16 width;
139 	gint16 height;
140 };
141 
142 struct u7shape {
143 	u7frame *frames;
144 	size_t num_frames;
145 };
146 
147 
MAIN()148 MAIN()	// NOLINT
149 
150 static void query() {
151 	constexpr const static GimpParamDef load_args[] = {
152 		{ GIMP_PDB_INT32,  const_cast<gchar*>("run_mode"),     const_cast<gchar*>("Interactive, non-interactive") },
153 		{ GIMP_PDB_STRING, const_cast<gchar*>("filename"),     const_cast<gchar*>("The name of the file to load") },
154 		{ GIMP_PDB_STRING, const_cast<gchar*>("raw_filename"), const_cast<gchar*>("The name entered") }
155 	};
156 	constexpr const static GimpParamDef load_return_vals[] = {
157 		{ GIMP_PDB_IMAGE, const_cast<gchar*>("image"), const_cast<gchar*>("Output image") }
158 	};
159 	constexpr const static gint nload_args = sizeof(load_args) / sizeof(load_args[0]);
160 	constexpr const static gint nload_return_vals = (sizeof(load_return_vals) /
161 	                                 sizeof(load_return_vals[0]));
162 
163 	constexpr const static GimpParamDef save_args[] = {
164 		{ GIMP_PDB_INT32,    const_cast<gchar*>("run_mode"),     const_cast<gchar*>("Interactive, non-interactive") },
165 		{ GIMP_PDB_IMAGE,    const_cast<gchar*>("image"),        const_cast<gchar*>("Image to save") },
166 		{ GIMP_PDB_DRAWABLE, const_cast<gchar*>("drawable"),     const_cast<gchar*>("Drawable to save") },
167 		{ GIMP_PDB_STRING,   const_cast<gchar*>("filename"),     const_cast<gchar*>("The name of the file to save") },
168 		{ GIMP_PDB_STRING,   const_cast<gchar*>("raw_filename"), const_cast<gchar*>("The name entered") }
169 	};
170 	constexpr const static gint nsave_args = sizeof(save_args) / sizeof(save_args[0]);
171 
172 	gimp_install_procedure("file_shp_load",
173 	                       "loads files in Ultima 7 SHP format",
174 	                       "FIXME: write help for shp_load",
175 	                       "Tristan Tarrant",
176 	                       "Tristan Tarrant",
177 	                       "2000",
178 	                       "<Load>/SHP",
179 	                       nullptr,
180 	                       GIMP_PLUGIN,
181 	                       nload_args, nload_return_vals,
182 	                       load_args, load_return_vals);
183 
184 	gimp_register_magic_load_handler("file_shp_load",
185 	                                 "shp",
186 	                                 "",
187 	                                 "");
188 
189 	gimp_install_procedure("file_shp_save",
190 	                       "Save files in Ultima 7 SHP format",
191 	                       "FIXME: write help for shp_save",
192 	                       "Tristan Tarrant",
193 	                       "Tristan Tarrant",
194 	                       "2000",
195 	                       "<Save>/SHP",
196 	                       "INDEXEDA",
197 	                       GIMP_PLUGIN,
198 	                       nsave_args, 0,
199 	                       save_args, nullptr);
200 
201 	gimp_register_save_handler("file_shp_save",
202 	                           "shp",
203 	                           "");
204 }
205 
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)206 static void run(
207     const gchar      *name,
208     gint              nparams,
209     const GimpParam  *param,
210     gint             *nreturn_vals,
211     GimpParam       **return_vals
212 ) {
213 	ignore_unused_variable_warning(nparams);
214 	static GimpParam  values[2];
215 
216 	gegl_init(nullptr, nullptr);
217 
218 	run_mode = static_cast<GimpRunMode>(param[0].data.d_int32);
219 
220 	*nreturn_vals = 1;
221 	*return_vals  = values;
222 	values[0].type          = GIMP_PDB_STATUS;
223 	values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
224 
225 	GimpPDBStatusType status = GIMP_PDB_SUCCESS;
226 	if (strcmp(name, "file_shp_load") == 0) {
227 		gimp_ui_init("u7shp", FALSE);
228 		if (run_mode != GIMP_RUN_NONINTERACTIVE) {
229 			choose_palette();
230 		}
231 		gint32 image_ID = load_image(param[1].data.d_string);
232 
233 		if (image_ID != -1) {
234 			*nreturn_vals = 2;
235 			values[1].type         = GIMP_PDB_IMAGE;
236 			values[1].data.d_image = image_ID;
237 		} else {
238 			status = GIMP_PDB_EXECUTION_ERROR;
239 		}
240 	} else if (strcmp(name, "file_shp_save") == 0) {
241 		gint32 orig_image_ID = param[1].data.d_int32;
242 		gint32 image_ID    = orig_image_ID;
243 		gint32 drawable_ID = param[2].data.d_int32;
244 		save_image(param[3].data.d_string,
245 		           image_ID,
246 		           drawable_ID,
247 		           orig_image_ID);
248 	} else {
249 		status = GIMP_PDB_CALLING_ERROR;
250 	}
251 	values[0].data.d_status = status;
252 }
253 
load_palette(const std::string & filename)254 static void load_palette(const std::string& filename) {
255 	U7object pal(filename, 0);
256 	size_t len;
257 	auto data = pal.retrieve(len);
258 	if (!data || len == 0) {
259 		return;
260 	}
261 	const auto *ptr = data.get();
262 	if (len == 768) {
263 		for (unsigned i = 0; i < 256; i++) {
264 			gimp_cmap[i * 3 + 0] = Read1(ptr) << 2;
265 			gimp_cmap[i * 3 + 1] = Read1(ptr) << 2;
266 			gimp_cmap[i * 3 + 2] = Read1(ptr) << 2;
267 		}
268 	} else if (len == 1536) {
269 		// Double palette
270 		for (unsigned i = 0; i < 256; i++) {
271 			gimp_cmap[i * 3 + 0] = Read1(ptr) << 2;
272 			Read1(ptr);	// Skip entry from second palette
273 			gimp_cmap[i * 3 + 1] = Read1(ptr) << 2;
274 			Read1(ptr);	// Skip entry from second palette
275 			gimp_cmap[i * 3 + 2] = Read1(ptr) << 2;
276 			Read1(ptr);	// Skip entry from second palette
277 		}
278 	}
279 }
280 
file_sel_delete(GtkWidget * widget,GtkWidget ** file_sel)281 static void file_sel_delete(GtkWidget *widget, GtkWidget **file_sel) {
282 	ignore_unused_variable_warning(widget);
283 	gtk_widget_destroy(*file_sel);
284 	*file_sel = nullptr;
285 }
286 
file_selected(GtkWidget * widget,gboolean * selected)287 static void file_selected(GtkWidget *widget, gboolean *selected) {
288 	ignore_unused_variable_warning(widget);
289 	*selected = TRUE;
290 }
291 
file_select(const gchar * title)292 std::string file_select(const gchar *title) {
293 	GtkWidget *file_sel = gtk_file_selection_new(title);
294 	gtk_window_set_modal(GTK_WINDOW(file_sel), TRUE);
295 
296 	gtk_signal_connect(GTK_OBJECT(file_sel), "destroy", GTK_SIGNAL_FUNC(file_sel_delete), &file_sel);
297 	gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(file_sel)->cancel_button), "clicked", GTK_SIGNAL_FUNC(file_sel_delete), &file_sel);
298 
299 	gboolean selected = FALSE;
300 	gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(file_sel)->ok_button), "clicked", GTK_SIGNAL_FUNC(file_selected), &selected);
301 
302 	gtk_widget_show(file_sel);
303 
304 	while (!selected && file_sel) {
305 		gtk_main_iteration();
306 	}
307 
308 	/* canceled or window was closed */
309 	if (!selected) {
310 		return "";
311 	}
312 
313 	/* ok */
314 	std::string filename(gtk_file_selection_get_filename(GTK_FILE_SELECTION(file_sel)));
315 	gtk_widget_destroy(file_sel);
316 	return filename;
317 }
318 
choose_palette()319 static void choose_palette() {
320 	load_palette(file_select("Choose palette"));
321 }
322 
323 struct Bounds {
324 	int xright = -1;
325 	int xleft = -1;
326 	int yabove = -1;
327 	int ybelow = -1;
328 };
329 
get_shape_bounds(Shape_file & shape)330 Bounds get_shape_bounds(Shape_file& shape) {
331 	if (shape.is_rle()) {
332 		Bounds bounds;
333 		for (const auto& frame : shape) {
334 			bounds.xright = std::max(bounds.xright, frame->get_xright());
335 			bounds.xleft  = std::max(bounds.xleft , frame->get_xleft());
336 			bounds.yabove = std::max(bounds.yabove, frame->get_yabove());
337 			bounds.ybelow = std::max(bounds.ybelow, frame->get_ybelow());
338 		}
339 		return bounds;
340 	}
341 	// Shape is composed of flats
342 	return Bounds{7, 0, 0, 7};
343 }
344 
load_image(gchar * filename)345 static gint32 load_image(gchar *filename) {
346 	Shape_file shape(filename);
347 #ifdef DEBUG
348 	std::cout << "num_frames = " << shape.get_num_frames() << '\n';
349 #endif
350 	const Bounds bounds = get_shape_bounds(shape);
351 	GimpImageType image_type;
352 	if (shape.is_rle()) {
353 		image_type = GIMP_INDEXEDA_IMAGE;
354 	} else {
355 		image_type = GIMP_INDEXED_IMAGE;
356 	}
357 
358 	const gint32 image_ID = gimp_image_new(bounds.xleft + bounds.xright + 1,
359 	                          bounds.yabove + bounds.ybelow + 1, GIMP_INDEXED);
360 	gimp_image_set_filename(image_ID, filename);
361 	gimp_image_set_colormap(image_ID, gimp_cmap, 256);
362 	int framenum = 0;
363 	for (auto& frame : shape) {
364 		std::string framename = "Frame " + std::to_string(framenum);
365 		const gint32 layer_ID = gimp_layer_new(image_ID, framename.c_str(),
366 		                          frame->get_width(), frame->get_height(),
367 		                          image_type, 100, GIMP_NORMAL_MODE);
368 		gimp_image_insert_layer(image_ID, layer_ID, -1, 0);
369 		gimp_item_transform_translate(layer_ID, bounds.xleft - frame->get_xleft(),
370 		                              bounds.yabove - frame->get_yabove());
371 
372 		GeglBuffer *drawable = gimp_drawable_get_buffer(layer_ID);
373 		const GeglRectangle rect{0, 0,
374 		                         gegl_buffer_get_width(drawable),
375 		                         gegl_buffer_get_height(drawable)};
376 
377 		Image_buffer8 img(frame->get_width(), frame->get_height());  // Render into a buffer.
378 		const unsigned char transp = 255;
379 		img.fill8(transp);          // Fill with transparent pixel.
380 		if (!frame->is_empty()) {
381 			frame->paint(&img, frame->get_xleft(), frame->get_yabove());
382 		}
383 		std::vector<unsigned char> pixels;
384 		const size_t num_pixels = frame->get_width() * frame->get_height();
385 		const unsigned char *pixel_data = nullptr;
386 		if (shape.is_rle()) {
387 			// Need to expand from (pixel)* to (pixel, alpha)*.
388 			pixels.reserve(num_pixels * 2);
389 			const auto *data = img.get_bits();
390 			for (const auto *ptr = data; ptr != data + num_pixels; ptr++) {
391 				pixels.push_back(*ptr);
392 				pixels.push_back(*ptr == transp ? 0 : 255);	// Alpha
393 			}
394 			pixel_data = pixels.data();
395 		} else {
396 			pixel_data = img.get_bits();
397 		}
398 
399 		gegl_buffer_set(drawable, &rect,
400 		                0, nullptr, pixel_data, GEGL_AUTO_ROWSTRIDE);
401 		g_object_unref(drawable);
402 		framenum++;
403 	}
404 
405 	gimp_image_add_hguide(image_ID, bounds.yabove);
406 	gimp_image_add_vguide(image_ID, bounds.xleft);
407 #ifdef DEBUG
408 	std::cout << "Added hguide=" << bounds.yabove << '\n'
409 	          << "Added vguide=" << bounds.xleft << '\n';
410 #endif
411 
412 	return image_ID;
413 }
414 
save_image(gchar * filename,gint32 image_ID,gint32 drawable_ID,gint32 orig_image_ID)415 static gint32 save_image(gchar  *filename,
416                          gint32  image_ID,
417                          gint32  drawable_ID,
418                          gint32  orig_image_ID) {
419 	ignore_unused_variable_warning(drawable_ID, orig_image_ID);
420 	if (run_mode != GIMP_RUN_NONINTERACTIVE) {
421 		std::string name_buf("Saving ");
422 		name_buf += filename;
423 		name_buf += ':';
424 		gimp_progress_init(name_buf.c_str());
425 	}
426 
427 	// Find the guides...
428 	int hotx = -1;
429 	int hoty = -1;
430 	for (gint32 guide_ID = gimp_image_find_next_guide(image_ID, 0);
431 	     guide_ID > 0; guide_ID = gimp_image_find_next_guide(image_ID, guide_ID)) {
432 #ifdef DEBUG
433 		std::cout << "Found guide " << guide_ID << ':';
434 #endif
435 
436 		switch (gimp_image_get_guide_orientation(image_ID, guide_ID)) {
437 		case GIMP_ORIENTATION_HORIZONTAL:
438 			if (hoty < 0) {
439 				hoty = gimp_image_get_guide_position(image_ID, guide_ID);
440 #ifdef DEBUG
441 				std::cout << " horizontal=" << hoty << '\n';
442 #endif
443 			}
444 			break;
445 		case GIMP_ORIENTATION_VERTICAL:
446 			if (hotx < 0) {
447 				hotx = gimp_image_get_guide_position(image_ID, guide_ID);
448 #ifdef DEBUG
449 				std::cout << " vertical=" << hotx << '\n';
450 #endif
451 			}
452 			break;
453 		case GIMP_ORIENTATION_UNKNOWN:
454 			break;
455 		}
456 	}
457 
458 	// get a list of layers for this image_ID
459 	int nlayers;
460 	gint32 *layers = gimp_image_get_layers(image_ID, &nlayers);
461 
462 	if (nlayers > 0 && !gimp_drawable_is_indexed(layers[0])) {
463 		g_message("SHP: You can only save indexed images!");
464 		return -1;
465 	}
466 
467 	Shape shape(nlayers);
468 	int layer = nlayers - 1;
469 	for (auto& frame : shape) {
470 		GeglBuffer *drawable = gimp_drawable_get_buffer(layers[layer]);
471 		gint offsetX;
472 		gint offsetY;
473 		gimp_drawable_offsets(layers[layer], &offsetX, &offsetY);
474 		const int width  = gegl_buffer_get_width(drawable);
475 		const int height = gegl_buffer_get_height(drawable);
476 		const int xleft  = hotx - offsetX;
477 		const int yabove = hoty - offsetY;
478 		const Babl *format = gegl_buffer_get_format(drawable);
479 		const size_t num_pixels = width * height;
480 		const size_t bytes_per_pixel = babl_format_get_bytes_per_pixel(format);
481 		std::vector<guchar> pix(num_pixels * bytes_per_pixel);
482 		const GeglRectangle rect{0, 0,
483 		                         gegl_buffer_get_width(drawable),
484 		                         gegl_buffer_get_height(drawable)};
485 		gegl_buffer_get(drawable, &rect,
486 		                1.0, format, pix.data(), GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
487 		g_object_unref(drawable);
488 
489 		std::vector<unsigned char> out;
490 		unsigned char *outptr;
491 		bool has_alpha = babl_format_has_alpha(format) != 0;
492 		if (has_alpha) {
493 			out.reserve(pix.size() / bytes_per_pixel);
494 			for (size_t ii = 0; ii < pix.size(); ii += bytes_per_pixel) {
495 				out.push_back(pix[ii+1] == 0 ? 255 : pix[ii]);
496 			}
497 			outptr = out.data();
498 		} else {
499 			outptr = pix.data();
500 		}
501 		frame = std::make_unique<Shape_frame>(outptr, width, height,
502 		                                      xleft, yabove, has_alpha);
503 		layer--;
504 	}
505 
506 	OFileDataSource ds(filename);
507 	if (!ds.good()) {
508 		g_message("SHP: can't create \"%s\"\n", filename);
509 		return -1;
510 	}
511 	shape.write(ds);
512 
513 	return 0;
514 }
515