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