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