1 /************************************************************************
2 *
3 * This file is part of SuperCollider Qt GUI.
4 *
5 * Copyright 2013 Jakob Leben (jakob.leben@gmail.com)
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program 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 General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 ************************************************************************/
21
22 #include "primitives.h"
23 #include "image_primitive_helper_funcs.h"
24 #include "../image.h"
25 #include "../QcApplication.h"
26 #include "../Common.h"
27 #include "../type_codec.hpp"
28 #include "../painting.h"
29 #include "../hacks/hacks_qt.hpp"
30
31 #include <PyrKernel.h>
32 #include <GC.h>
33
34 #include <QImage>
35 #include <QUrl>
36 #include <QPainter>
37 #include <QtSvg/QSvgRenderer>
38 #include <QImageReader>
39 #include <QImageWriter>
40 #include <QEventLoop>
41 #include <QTimer>
42 #include <QtNetwork/QNetworkAccessManager>
43 #include <QtNetwork/QNetworkRequest>
44 #include <QtNetwork/QNetworkReply>
45
46 #include <cassert>
47
48 namespace QC = QtCollider;
49
50 namespace QtCollider {
51
52 QPainter* imgPainter = 0;
53
54 QC_LANG_PRIMITIVE(QImage_NewPath, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
55 if (!QcApplication::compareThread())
56 return QtCollider::wrongThreadError();
57
58 QString path(QtCollider::get<QString>(a));
59 QPixmap pixmap(path);
60 if (pixmap.isNull())
61 return errFailed;
62
63 Image* image = new Image();
64 image->setPixmap(pixmap);
65 initialize_image_object(g, slotRawObject(r), image);
66 return errNone;
67 }
68
69 QC_LANG_PRIMITIVE(QImage_NewSVG, 3, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
70 if (!QcApplication::compareThread())
71 return QtCollider::wrongThreadError();
72
73 QString path(QtCollider::get<QString>(a));
74 QSize size(QtCollider::get<QSize>(a + 1));
75 QString element(QtCollider::get<QString>(a + 2));
76
77 QPainter painter;
78 QSvgRenderer svgRenderer(path);
79
80 if (size.isEmpty() || size.isNull()) {
81 size = svgRenderer.defaultSize();
82 }
83
84 QPixmap pixmap(size);
85 pixmap.fill(Qt::transparent);
86
87 painter.begin(&pixmap);
88 if (element.isEmpty()) {
89 svgRenderer.render(&painter);
90 } else {
91 svgRenderer.render(&painter, element);
92 }
93 painter.end();
94
95 Image* image = new Image();
96 image->setPixmap(pixmap);
97 initialize_image_object(g, slotRawObject(r), image);
98
99 return errNone;
100 }
101
102 QC_LANG_PRIMITIVE(QImage_NewURL, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
103 // FIXME:
104 // We can not run an event loop while waiting to receive data, because that
105 // would allow GUI to try and call into the language, resulting in a deadlock.
106 qcErrorMsg("QImage: loading from URL not yet implemented.");
107 return errFailed;
108 #if 0
109 QString url_str = QtCollider::get(a);
110 QUrl url(url_str);
111 if( !url.isValid() || url.isEmpty() ) {
112 qcErrorMsg( QStringLiteral("QImage: invalid or empty URL: ") + url_str );
113 return errFailed;
114 }
115
116 if( QURL_IS_LOCAL_FILE(url) ) {
117 if( QImage_InitPath( g, slotRawObject(r), url.toLocalFile() ) ) {
118 return errNone;
119 } else {
120 qcErrorMsg( QStringLiteral("QImage: file doesn't exist or can't be opened: ") + url_str );
121 return errFailed;
122 }
123 }
124
125 if( !IsFloat(a+1) && !IsInt(a+1) ) return errWrongType;
126 // Take a safe read to allow Integers:
127 float timeout = QtCollider::get(a+1);
128
129 QNetworkAccessManager manager;
130 QScopedPointer<QNetworkReply> reply( manager.get( QNetworkRequest(url) ) );
131
132 QEventLoop loop;
133 QcApplication::connect( &manager, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()) );
134 QcApplication::connect( reply.data(), SIGNAL(error(QNetworkReply::NetworkError)), &loop, SLOT(quit()) );
135 QTimer::singleShot( 100 * timeout, &loop, SLOT(quit()) );
136 loop.exec(); // blocks
137
138 if( reply->error() != QNetworkReply::NoError ) {
139 qcErrorMsg( QStringLiteral("QImage: error trying to download: ") + url_str );
140 return errFailed;
141 }
142 else if( !reply->isFinished() ) {
143 qcErrorMsg( QStringLiteral("QImage: timeout while trying to download: ") + url_str );
144 reply->abort();
145 return errFailed;
146 }
147
148 QByteArray byteArray = reply->readAll();
149 if( byteArray.isEmpty() ) {
150 qcErrorMsg( QStringLiteral("QImage: no data received: ") + url_str );
151 return errFailed;
152 }
153
154 if( QImage_InitFromData( g, slotRawObject(r), byteArray ) ) {
155 return errNone;
156 }
157 else {
158 qcErrorMsg( QStringLiteral("QImage: failed trying to open downloaded data: ") + url_str );
159 return errFailed;
160 }
161 #endif
162 }
163
164 QC_LANG_PRIMITIVE(QImage_NewEmpty, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
165 if (!QcApplication::compareThread())
166 return QtCollider::wrongThreadError();
167
168 if (NotInt(a) || NotInt(a + 1))
169 return errWrongType;
170 int width = QtCollider::read<int>(a);
171 int height = QtCollider::read<int>(a + 1);
172
173 QImage qimage(width, height, QImage::Format_ARGB32_Premultiplied);
174 qimage.fill(QColor(Qt::transparent).rgba());
175
176 Image* image = new Image;
177 image->setImage(qimage);
178 initialize_image_object(g, slotRawObject(r), image);
179
180 return errNone;
181 }
182
183 QC_LANG_PRIMITIVE(QImage_NewFromWindow, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
184 if (!QcApplication::compareThread())
185 return QtCollider::wrongThreadError();
186
187 QObjectProxy* proxy = QtCollider::get(a);
188 if (!proxy)
189 return errWrongType;
190 QWidget* widget = qobject_cast<QWidget*>(proxy->object());
191 if (!widget)
192 return errWrongType;
193
194 QRect rect;
195 if (IsObj(a + 1) && slotRawObject(a + 1)->classptr == SC_CLASS(Rect))
196 rect = QtCollider::read<QRect>(a + 1);
197 else if (NotNil(a + 1))
198 return errWrongType;
199
200 QPixmap pixmap = widget->grab(rect);
201 if (pixmap.isNull())
202 return errFailed;
203
204 Image* image = new Image;
205 image->setPixmap(pixmap);
206 initialize_image_object(g, slotRawObject(r), image);
207
208 return errNone;
209 }
210
211 QC_LANG_PRIMITIVE(QImage_Free, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
212 if (!QcApplication::compareThread())
213 return QtCollider::wrongThreadError();
214
215 Image* image = to_image(r);
216 if (image->isPainting()) {
217 qcErrorMsg("QImage: can not free while being painted.");
218 return errFailed;
219 }
220
221 image->clear();
222 return errNone;
223 }
224
225 QC_LANG_PRIMITIVE(QImage_HasSmoothTransformation, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
226 if (!QcApplication::compareThread())
227 return QtCollider::wrongThreadError();
228 Image* image = to_image(r);
229 QC::write<bool>(r, image->transformationMode == Qt::SmoothTransformation);
230 return errNone;
231 }
232
233 QC_LANG_PRIMITIVE(QImage_SetSmoothTransformation, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
234 if (!QcApplication::compareThread())
235 return QtCollider::wrongThreadError();
236 bool smooth = QC::get(a);
237 Image* image = to_image(r);
238 image->transformationMode = smooth ? Qt::SmoothTransformation : Qt::FastTransformation;
239 return errNone;
240 }
241
242 QC_LANG_PRIMITIVE(QImage_Width, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
243 if (!QcApplication::compareThread())
244 return QtCollider::wrongThreadError();
245
246 Image* image = to_image(r);
247 SetInt(r, image->width());
248 return errNone;
249 }
250
251 QC_LANG_PRIMITIVE(QImage_Height, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
252 if (!QcApplication::compareThread())
253 return QtCollider::wrongThreadError();
254
255 Image* image = to_image(r);
256 SetInt(r, image->height());
257 return errNone;
258 }
259
260 QC_LANG_PRIMITIVE(QImage_SetSize, 3, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
261 if (!QcApplication::compareThread())
262 return QtCollider::wrongThreadError();
263
264 if (NotInt(a) || NotInt(a + 1) || NotInt(a + 2))
265 return errWrongType;
266 QSize new_size(QtCollider::read<int>(a), QtCollider::read<int>(a + 1));
267 int resize_mode = QtCollider::read<int>(a + 2);
268
269 Image* image = to_image(r);
270 if (image->isPainting()) {
271 qcErrorMsg("QImage: can not resize while being painted.");
272 return errFailed;
273 }
274
275 image->resize(new_size, resize_mode);
276
277 return errNone;
278 }
279
280 QC_LANG_PRIMITIVE(QImage_Write, 3, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
281 if (!QcApplication::compareThread())
282 return QtCollider::wrongThreadError();
283
284 QString path = QC::get(a);
285 QString format = QC::get(a + 1);
286
287 if (NotInt(a + 2))
288 return errWrongType;
289 int quality = QC::read<int>(a + 2);
290
291 QImage& image = to_image(r)->image();
292 if (image.save(path, format.toUpper().toStdString().c_str(), quality))
293 return errNone;
294
295 qcErrorMsg(QStringLiteral("QImage: Failed to write to file: ") + path);
296 return errFailed;
297 }
298
299 QC_LANG_PRIMITIVE(QImage_SetPainter, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
300 if (!QcApplication::compareThread())
301 return QtCollider::wrongThreadError();
302
303 if (QtCollider::paintingAnnounced()) {
304 qcDebugMsg(1, "WARNING: Custom painting already in progress. Will not paint.");
305 return errFailed;
306 }
307
308 Image* image = to_image(r);
309 assert(!image->isPainting());
310
311 assert(imgPainter == 0);
312 imgPainter = new QPainter(&image->image());
313 QtCollider::announcePainting();
314 QtCollider::beginPainting(imgPainter);
315
316 image->setPainting(true);
317
318 return errNone;
319 }
320
321 QC_LANG_PRIMITIVE(QImage_UnsetPainter, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
322 if (!QcApplication::compareThread())
323 return QtCollider::wrongThreadError();
324
325 Image* image = to_image(r);
326
327 assert(image->isPainting());
328 image->setPainting(false);
329
330 assert(imgPainter != 0);
331 QtCollider::endPainting();
332 delete imgPainter;
333 imgPainter = 0;
334
335 return errNone;
336 }
337
338 QC_LANG_PRIMITIVE(QImage_GetPixel, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
339 if (!QcApplication::compareThread())
340 return QtCollider::wrongThreadError();
341
342 if (NotInt(a) || NotInt(a + 1))
343 return errWrongType;
344 int x = QC::read<int>(a);
345 int y = QC::read<int>(a + 1);
346
347 QImage& image = to_image(r)->image();
348
349 if (x >= image.width() || y >= image.height())
350 return errIndexOutOfRange;
351
352 int* line = reinterpret_cast<int*>(image.scanLine(y));
353 SetInt(r, line[x]);
354
355 return errNone;
356 }
357
358 QC_LANG_PRIMITIVE(QImage_GetColor, 2, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
359 if (!QcApplication::compareThread())
360 return QtCollider::wrongThreadError();
361
362 if (NotInt(a) || NotInt(a + 1))
363 return errWrongType;
364 int x = QC::read<int>(a);
365 int y = QC::read<int>(a + 1);
366
367 QImage& image = to_image(r)->image();
368
369 if (x >= image.width() || y >= image.height())
370 return errIndexOutOfRange;
371
372 QRgb* line = reinterpret_cast<QRgb*>(image.scanLine(y));
373 QC::set(r, pixel_to_color(line[x]));
374
375 return errNone;
376 }
377
378 QC_LANG_PRIMITIVE(QImage_SetPixel, 3, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
379 if (!QcApplication::compareThread())
380 return QtCollider::wrongThreadError();
381
382 if (NotInt(a) || NotInt(a + 1) || NotInt(a + 2))
383 return errWrongType;
384 int pixel = QC::read<int>(a);
385 int x = QC::read<int>(a + 1);
386 int y = QC::read<int>(a + 2);
387
388 QImage& image = to_image(r)->image();
389
390 if (x >= image.width() || y >= image.height())
391 return errIndexOutOfRange;
392
393 int* line = reinterpret_cast<int*>(image.scanLine(y));
394 line[x] = pixel;
395
396 return errNone;
397 }
398
399 QC_LANG_PRIMITIVE(QImage_SetColor, 3, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
400 if (!QcApplication::compareThread())
401 return QtCollider::wrongThreadError();
402
403 if (NotObj(a) || slotRawObject(a)->classptr != SC_CLASS(Color) || NotInt(a + 1) || NotInt(a + 2))
404 return errWrongType;
405 QColor color(QC::read<QColor>(a));
406 int x = QC::read<int>(a + 1);
407 int y = QC::read<int>(a + 2);
408
409 QImage& image = to_image(r)->image();
410
411 if (x >= image.width() || y >= image.height())
412 return errIndexOutOfRange;
413
414 QRgb* line = reinterpret_cast<QRgb*>(image.scanLine(y));
415 line[x] = color_to_pixel(color);
416
417 return errNone;
418 }
419
420 QC_LANG_PRIMITIVE(QImage_TransferPixels, 4, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
421 if (!QcApplication::compareThread())
422 return QtCollider::wrongThreadError();
423
424 if (!isKindOfSlot(a, class_int32array)) {
425 qcErrorMsg("QImage: array argument is not a Int32Array");
426 return errWrongType;
427 }
428
429 if (NotInt(a + 2))
430 return errWrongType;
431 int start = QC::read<int>(a + 2);
432
433 if (!(IsTrue(a + 3) || IsFalse(a + 3)))
434 return errWrongType;
435 bool store = IsTrue(a + 3); // t/f g/s
436
437 QImage& image = to_image(r)->image();
438 QRect rect;
439
440 if (IsNil(a + 1)) {
441 rect = image.rect();
442 } else {
443 if (!isKindOfSlot(a + 1, SC_CLASS(Rect)))
444 return errWrongType;
445 rect = QC::read<QRect>(a + 1);
446 if (rect.isEmpty())
447 return errNone;
448 if (!image.rect().contains(rect)) {
449 qcErrorMsg("QImage: source rectangle out of image bounds");
450 return errFailed;
451 }
452 }
453
454 PyrInt32Array* array = reinterpret_cast<PyrInt32Array*>(slotRawObject(a));
455 QRgb* pixelData = reinterpret_cast<QRgb*>(array->i) + start;
456
457 int width = rect.width();
458 int height = rect.height();
459 int size = width * height;
460 int x = rect.x();
461 int y = rect.y();
462 int max_x = width + x;
463 int max_y = height + y;
464
465 if (array->size - start < size)
466 return errIndexOutOfRange;
467
468 if (store) {
469 for (int iy = y; iy < max_y; ++iy) {
470 QRgb* line = reinterpret_cast<QRgb*>(image.scanLine(iy));
471 for (int ix = x; ix < max_x; ++ix) {
472 line[ix] = *pixelData;
473 ++pixelData;
474 }
475 }
476 } else {
477 for (int iy = y; iy < max_y; ++iy) {
478 QRgb* line = reinterpret_cast<QRgb*>(image.scanLine(iy));
479 for (int ix = x; ix < max_x; ++ix) {
480 *pixelData = line[ix];
481 ++pixelData;
482 }
483 }
484 }
485
486 return errNone;
487 }
488
489 QC_LANG_PRIMITIVE(QImage_Fill, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
490 if (!QcApplication::compareThread())
491 return QtCollider::wrongThreadError();
492
493 if (NotObj(a) || slotRawObject(a)->classptr != SC_CLASS(Color))
494 return errWrongType;
495
496 QColor color = QC::read<QColor>(a);
497 QImage& image = to_image(r)->image();
498 image.fill(color_to_pixel(color));
499
500 return errNone;
501 }
502
503 QC_LANG_PRIMITIVE(QImage_Formats, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
504 if (!QcApplication::compareThread())
505 return QtCollider::wrongThreadError();
506
507 if (NotInt(a))
508 return errWrongType;
509 int rw = QC::read<int>(a);
510
511 QList<QByteArray> formats = rw ? QImageWriter::supportedImageFormats() : QImageReader::supportedImageFormats();
512
513 PyrObject* array = newPyrArray(g->gc, formats.size(), 0, true);
514 SetObject(r, array);
515 for (int i = 0; i < formats.size(); ++i) {
516 PyrString* str = newPyrString(g->gc, formats[i].constData(), obj_immutable, false);
517 SetObject(array->slots + i, str);
518 ++array->size;
519 g->gc->GCWriteNew(array, str); // we know str is white so we can use GCWriteNew
520 }
521
522 return errNone;
523 }
524
525 QC_LANG_PRIMITIVE(QImage_PixelToColor, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
526 if (NotInt(a))
527 return errWrongType;
528 QRgb pixel = static_cast<QRgb>(QC::read<int>(a));
529 QC::set(r, pixel_to_color(pixel));
530 return errNone;
531 }
532
533 QC_LANG_PRIMITIVE(QImage_ColorToPixel, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
534 if (NotObj(a) || slotRawObject(a)->classptr != SC_CLASS(Color))
535 return errWrongType;
536 QColor color = QC::read<QColor>(a);
537 int pixel = static_cast<int>(color_to_pixel(color));
538 QC::set(r, pixel);
539 return errNone;
540 }
541
542 QC_LANG_PRIMITIVE(QImage_GetDevicePixelRatio, 0, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
543 if (!QcApplication::compareThread())
544 return QtCollider::wrongThreadError();
545
546 Image* image = to_image(r);
547 SetFloat(r, image->getDevicePixelRatio());
548 return errNone;
549 }
550
551 QC_LANG_PRIMITIVE(QImage_SetDevicePixelRatio, 1, PyrSlot* r, PyrSlot* a, VMGlobals* g) {
552 if (!QcApplication::compareThread())
553 return QtCollider::wrongThreadError();
554
555 double ratio = 1;
556 int err = slotDoubleVal(a, &ratio);
557 if (err == errNone) {
558 Image* image = to_image(r);
559 image->setDevicePixelRatio(ratio);
560 }
561
562 return err;
563 }
564
565
defineQImagePrimitives()566 void defineQImagePrimitives() {
567 LangPrimitiveDefiner definer;
568 definer.define<QImage_NewPath>();
569 definer.define<QImage_NewSVG>();
570 definer.define<QImage_NewURL>();
571 definer.define<QImage_NewEmpty>();
572 definer.define<QImage_NewFromWindow>();
573 definer.define<QImage_Free>();
574 definer.define<QImage_HasSmoothTransformation>();
575 definer.define<QImage_SetSmoothTransformation>();
576 definer.define<QImage_Width>();
577 definer.define<QImage_Height>();
578 definer.define<QImage_SetSize>();
579 definer.define<QImage_Write>();
580 definer.define<QImage_SetPainter>();
581 definer.define<QImage_UnsetPainter>();
582 definer.define<QImage_GetPixel>();
583 definer.define<QImage_GetColor>();
584 definer.define<QImage_SetPixel>();
585 definer.define<QImage_SetColor>();
586 definer.define<QImage_TransferPixels>();
587 definer.define<QImage_Fill>();
588 definer.define<QImage_Formats>();
589 definer.define<QImage_PixelToColor>();
590 definer.define<QImage_ColorToPixel>();
591 definer.define<QImage_GetDevicePixelRatio>();
592 definer.define<QImage_SetDevicePixelRatio>();
593 }
594
595 } // namespace QtCollider
596