1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7 #include "objimageexport.h"
8 
9 #include <QImageWriter>
10 #include <structmember.h>
11 #include <QFileInfo>
12 #include <vector>
13 
14 #include "cmdutil.h"
15 #include "scpage.h"
16 #include "scribuscore.h"
17 #include "scribusdoc.h"
18 #include "scribusview.h"
19 
20 typedef struct
21 {
22 	PyObject_HEAD
23 	PyObject *name; // string - filename
24 	PyObject *type; // string - image type (PNG, JPEG etc.)
25 	PyObject *allTypes; // list - available types
26 	int dpi; // DPI of the bitmap
27 	int scale; // how is bitmap scaled 100 = 100%
28 	int quality; // quality/compression <1; 100>
29 	int transparentBkgnd; // background transparency
30 } ImageExport;
31 
ImageExport_dealloc(ImageExport * self)32 static void ImageExport_dealloc(ImageExport* self)
33 {
34 	Py_XDECREF(self->name);
35 	Py_XDECREF(self->type);
36 	Py_XDECREF(self->allTypes);
37 	Py_TYPE(self)->tp_free((PyObject *)self);
38 }
39 
ImageExport_new(PyTypeObject * type,PyObject *,PyObject *)40 static PyObject * ImageExport_new(PyTypeObject *type, PyObject * /*args*/, PyObject * /*kwds*/)
41 {
42 	if (!checkHaveDocument())
43 		return nullptr;
44 
45 	ImageExport *self;
46 	self = (ImageExport *)type->tp_alloc(type, 0);
47 	if (self != nullptr) {
48 		self->name = PyUnicode_FromString("ImageExport.png");
49 		self->type = PyUnicode_FromString("PNG");
50 		self->allTypes = PyList_New(0);
51 		self->dpi = 72;
52 		self->scale = 100;
53 		self->quality = 100;
54 		self->transparentBkgnd = 0;
55 	}
56 	return (PyObject *) self;
57 }
58 
ImageExport_init(ImageExport *,PyObject *,PyObject *)59 static int ImageExport_init(ImageExport * /*self*/, PyObject * /*args*/, PyObject * /*kwds*/)
60 {
61 	return 0;
62 }
63 
64 static PyMemberDef ImageExport_members[] = {
65 	{const_cast<char*>("dpi"), T_INT, offsetof(ImageExport, dpi), 0, imgexp_dpi__doc__},
66 	{const_cast<char*>("scale"), T_INT, offsetof(ImageExport, scale), 0, imgexp_scale__doc__},
67 	{const_cast<char*>("quality"), T_INT, offsetof(ImageExport, quality), 0, imgexp_quality__doc__},
68 	{const_cast<char*>("transparentBkgnd"), T_INT, offsetof(ImageExport, transparentBkgnd), 0, imgexp_transparentBkgnd__doc__},
69 	{nullptr, 0, 0, 0, nullptr} // sentinel
70 };
71 
ImageExport_getName(ImageExport * self,void *)72 static PyObject *ImageExport_getName(ImageExport *self, void * /*closure*/)
73 {
74 	Py_INCREF(self->name);
75 	return self->name;
76 }
77 
ImageExport_setName(ImageExport * self,PyObject * value,void *)78 static int ImageExport_setName(ImageExport *self, PyObject *value, void * /*closure*/)
79 {
80 	if (!PyUnicode_Check(value)) {
81 		PyErr_SetString(PyExc_TypeError, QObject::tr("The filename must be a string.", "python error").toLocal8Bit().constData());
82 		return -1;
83 	}
84 	if (PyUnicode_GET_LENGTH(value) < 1)
85 	{
86 		PyErr_SetString(PyExc_TypeError, QObject::tr("The filename should not be empty string.", "python error").toLocal8Bit().constData());
87 		return -1;
88 	}
89 	Py_DECREF(self->name);
90 	Py_INCREF(value);
91 	self->name = value;
92 	return 0;
93 }
94 
ImageExport_getType(ImageExport * self,void *)95 static PyObject *ImageExport_getType(ImageExport *self, void * /*closure*/)
96 {
97 	Py_INCREF(self->type);
98 	return self->type;
99 }
100 
ImageExport_setType(ImageExport * self,PyObject * value,void *)101 static int ImageExport_setType(ImageExport *self, PyObject *value, void * /*closure*/)
102 {
103 	if (value == nullptr) {
104 		PyErr_SetString(PyExc_TypeError, QObject::tr("Cannot delete image type settings.", "python error").toLocal8Bit().constData());
105 		return -1;
106 	}
107 	if (!PyUnicode_Check(value)) {
108 		PyErr_SetString(PyExc_TypeError, QObject::tr("The image type must be a string.", "python error").toLocal8Bit().constData());
109 		return -1;
110 	}
111 	Py_DECREF(self->type);
112 	Py_INCREF(value);
113 	self->type = value;
114 	return 0;
115 }
116 
ImageExport_getAllTypes(ImageExport *,void *)117 static PyObject *ImageExport_getAllTypes(ImageExport * /*self*/, void * /*closure*/)
118 {
119 	PyObject *l;
120 	int pos = 0;
121 	QList<QByteArray> list = QImageWriter::supportedImageFormats();
122 	l = PyList_New(list.count());
123 	for (QList<QByteArray>::Iterator it = list.begin(); it != list.end(); ++it)
124 	{
125 		PyList_SetItem(l, pos, PyUnicode_FromString(QString((*it)).toLatin1().constData()));
126 		++pos;
127 	}
128 	return l;
129 }
130 
ImageExport_setAllTypes(ImageExport *,PyObject *,void *)131 static int ImageExport_setAllTypes(ImageExport * /*self*/, PyObject * /*value*/, void * /*closure*/)
132 {
133 	PyErr_SetString(PyExc_ValueError, QObject::tr("'allTypes' attribute is READ-ONLY", "python error").toLocal8Bit().constData());
134 	return -1;
135 }
136 
137 static PyGetSetDef ImageExport_getseters [] = {
138 	{const_cast<char*>("name"), (getter)ImageExport_getName, (setter)ImageExport_setName, imgexp_filename__doc__, nullptr},
139 	{const_cast<char*>("type"), (getter)ImageExport_getType, (setter)ImageExport_setType, imgexp_type__doc__, nullptr},
140 	{const_cast<char*>("allTypes"), (getter)ImageExport_getAllTypes, (setter)ImageExport_setAllTypes, imgexp_alltypes__doc__, nullptr},
141 	{nullptr, nullptr, nullptr, nullptr, nullptr}  // sentinel
142 };
143 
ImageExport_save(ImageExport * self)144 static PyObject *ImageExport_save(ImageExport *self)
145 {
146 	if (!checkHaveDocument())
147 		return nullptr;
148 	ScribusDoc*  doc = ScCore->primaryMainWindow()->doc;
149 	ScribusView*view = ScCore->primaryMainWindow()->view;
150 
151 	/* a little magic here - I need to compute the "maxGr" value...
152 	* We need to know the right size of the page for landscape,
153 	* portrait and user defined sizes.
154 	*/
155 	double pixmapSize = (doc->pageHeight() > doc->pageWidth()) ? doc->pageHeight() : doc->pageWidth();
156 	PageToPixmapFlags flags = Pixmap_DrawBackground;
157 	if (self->transparentBkgnd)
158 		flags &= ~Pixmap_DrawBackground;
159 	QImage im = view->PageToPixmap(doc->currentPage()->pageNr(), qRound(pixmapSize * self->scale * (self->dpi / 72.0) / 100.0), flags);
160 	int dpi = qRound(100.0 / 2.54 * self->dpi);
161 	im.setDotsPerMeterY(dpi);
162 	im.setDotsPerMeterX(dpi);
163 
164 	QString imgFileName = PyUnicode_asQString(self->name);
165 	if (!im.save(imgFileName, PyUnicode_AsUTF8(self->type)))
166 	{
167 		PyErr_SetString(ScribusException, QObject::tr("Failed to export image", "python error").toLocal8Bit().constData());
168 		return nullptr;
169 	}
170 // 	Py_INCREF(Py_True); // return True not None for backward compat
171  //	return Py_True;
172 //	Py_RETURN_TRUE;
173 	return PyBool_FromLong(static_cast<long>(true));
174 }
175 
ImageExport_saveAs(ImageExport * self,PyObject * args)176 static PyObject *ImageExport_saveAs(ImageExport *self, PyObject *args)
177 {
178 	char* value;
179 	if (!checkHaveDocument())
180 		return nullptr;
181 	if (!PyArg_ParseTuple(args, const_cast<char*>("es"), "utf-8", &value))
182 		return nullptr;
183 
184 	ScribusDoc*  doc = ScCore->primaryMainWindow()->doc;
185 	ScribusView*view = ScCore->primaryMainWindow()->view;
186 
187 	/* a little magic here - I need to compute the "maxGr" value...
188 	* We need to know the right size of the page for landscape,
189 	* portrait and user defined sizes.
190 	*/
191 	double pixmapSize = (doc->pageHeight() > doc->pageWidth()) ? doc->pageHeight() : doc->pageWidth();
192 	PageToPixmapFlags flags = Pixmap_DrawBackground;
193 	if (self->transparentBkgnd)
194 		flags &= ~Pixmap_DrawBackground;
195 	QImage im = view->PageToPixmap(doc->currentPage()->pageNr(), qRound(pixmapSize * self->scale * (self->dpi / 72.0) / 100.0), flags);
196 	int dpi = qRound(100.0 / 2.54 * self->dpi);
197 	im.setDotsPerMeterY(dpi);
198 	im.setDotsPerMeterX(dpi);
199 
200 	QString outputFileName = QString::fromUtf8(value);
201 	if (!im.save(outputFileName, PyUnicode_AsUTF8(self->type)))
202 	{
203 		PyErr_SetString(ScribusException, QObject::tr("Failed to export image", "python error").toLocal8Bit().constData());
204 		return nullptr;
205 	}
206 // 	Py_INCREF(Py_True); // return True not None for backward compat
207  //	return Py_True;
208 //	Py_RETURN_TRUE;
209 	return PyBool_FromLong(static_cast<long>(true));
210 }
211 
212 static PyMethodDef ImageExport_methods[] = {
213 	{const_cast<char*>("save"), (PyCFunction)ImageExport_save, METH_NOARGS, imgexp_save__doc__},
214 	{const_cast<char*>("saveAs"), (PyCFunction)ImageExport_saveAs, METH_VARARGS, imgexp_saveas__doc__},
215 	{nullptr, (PyCFunction)(nullptr), 0, nullptr} // sentinel
216 };
217 
218 PyTypeObject ImageExport_Type = {
219 	PyVarObject_HEAD_INIT(nullptr, 0)   // PyObject_VAR_HEAD
220 	const_cast<char*>("scribus.ImageExport"), // char *tp_name; /* For printing, in format "<module>.<name>" */
221 	sizeof(ImageExport),   // int tp_basicsize, /* For allocation */
222 	0,  // int tp_itemsize; /* For allocation */
223 
224 	/* Methods to implement standard operations */
225 
226 	(destructor) ImageExport_dealloc, //	 destructor tp_dealloc;
227 #if PY_VERSION_HEX >= 0x03080000
228 	0,       //     Py_ssize_t tp_vectorcall_offset
229 #else
230 	nullptr, //     printfunc tp_print;
231 #endif
232 	nullptr, //	 getattrfunc tp_getattr;
233 	nullptr, //	 setattrfunc tp_setattr;
234 	nullptr, //	 cmpfunc tp_as_async;
235 	nullptr, //	 reprfunc tp_repr;
236 
237 	/* Method suites for standard classes */
238 
239 	nullptr, //	 PyNumberMethods *tp_as_number;
240 	nullptr, //	 PySequenceMethods *tp_as_sequence;
241 	nullptr, //	 PyMappingMethods *tp_as_mapping;
242 
243 	/* More standard operations (here for binary compatibility) */
244 
245 	nullptr, //	 hashfunc tp_hash;
246 	nullptr, //	 ternaryfunc tp_call;
247 	nullptr, //	 reprfunc tp_str;
248 	nullptr, //	 getattrofunc tp_getattro;
249 	nullptr, //	 setattrofunc tp_setattro;
250 
251 	/* Functions to access object as input/output buffer */
252 	nullptr, //	 PyBufferProcs *tp_as_buffer;
253 
254 	/* Flags to define presence of optional/expanded features */
255 	Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,	// long tp_flags;
256 
257 	imgexp__doc__, // char *tp_doc; /* Documentation string */
258 
259 	/* Assigned meaning in release 2.0 */
260 	/* call function for all accessible objects */
261 	nullptr, //	 traverseproc tp_traverse;
262 
263 	/* delete references to contained objects */
264 	nullptr, //	 inquiry tp_clear;
265 
266 	/* Assigned meaning in release 2.1 */
267 	/* rich comparisons */
268 	nullptr, //	 richcmpfunc tp_richcompare;
269 
270 	/* weak reference enabler */
271 	0, //	 long tp_weaklistoffset;
272 
273 	/* Added in release 2.2 */
274 	/* Iterators */
275 	nullptr, //	 getiterfunc tp_iter;
276 	nullptr, //	 iternextfunc tp_iternext;
277 
278 	/* Attribute descriptor and subclassing stuff */
279 	ImageExport_methods, //	 struct PyMethodDef *tp_methods;
280 	ImageExport_members, //	 struct PyMemberDef *tp_members;
281 	ImageExport_getseters, //	 struct PyGetSetDef *tp_getset;
282 	nullptr, //	 struct _typeobject *tp_base;
283 	nullptr, //	 PyObject *tp_dict;
284 	nullptr, //	 descrgetfunc tp_descr_get;
285 	nullptr, //	 descrsetfunc tp_descr_set;
286 	0, //	 long tp_dictoffset;
287 	(initproc)ImageExport_init, //	 initproc tp_init;
288 	nullptr, //	 allocfunc tp_alloc;
289 	ImageExport_new, //	 newfunc tp_new;
290 	nullptr, //	 freefunc tp_free; /* Low-level free-memory routine */
291 	nullptr, //	 inquiry tp_is_gc; /* For PyObject_IS_GC */
292 	nullptr, //	 PyObject *tp_bases;
293 	nullptr, //	 PyObject *tp_mro; /* method resolution order */
294 	nullptr, //	 PyObject *tp_cache;
295 	nullptr, //	 PyObject *tp_subclasses;
296 	nullptr, //	 PyObject *tp_weaklist;
297 	nullptr, //	 destructor tp_del;
298 	0, //	 unsigned int tp_version_tag;
299 	0, //	 destructor tp_finalize;
300 #if PY_VERSION_HEX >= 0x03080000
301 	nullptr, // tp_vectorcall
302 #endif
303 #if PY_VERSION_HEX >= 0x03080000 && PY_VERSION_HEX < 0x03090000
304 	nullptr, //deprecated tp_print
305 #endif
306 
307 #ifdef COUNT_ALLOCS
308 	/* these must be last and never explicitly initialized */
309 	//    int tp_allocs;
310 	//    int tp_frees;
311 	//    int tp_maxalloc;
312 	//    struct _typeobject *tp_prev;
313 	//    struct _typeobject *tp_next;
314 #endif
315 };
316