1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2014-2019 Calle Laakkonen
5 
6    Drawpile is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10 
11    Drawpile is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with Drawpile.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "images.h"
21 
22 #include <QSize>
23 #include <QImageReader>
24 #include <QImageWriter>
25 #include <QImage>
26 #include <QColor>
27 #include <QGuiApplication>
28 
29 namespace utils {
30 
31 //! Check if image dimensions are not too big. Returns true if size is OK
checkImageSize(const QSize & size)32 bool checkImageSize(const QSize &size)
33 {
34 	// The protocol limits width and height to 2^29 (PenMove, 2^32 for others)
35 	// However, QPixmap can be at most 32767 pixels wide/tall
36 	static const int MAX_SIZE = 32767;
37 
38 	return
39 		size.width() <= MAX_SIZE &&
40 		size.height() <= MAX_SIZE;
41 }
42 
isWritableFormat(const QString & filename)43 bool isWritableFormat(const QString &filename)
44 {
45 	const int dot = filename.lastIndexOf('.');
46 	if(dot<0)
47 		return false;
48 	const QByteArray suffix = filename.mid(dot+1).toLower().toLatin1();
49 
50 	// Formats we support
51 	if(suffix == "ora")
52 		return true;
53 
54 	// All formats supported by Qt
55 	for(const QByteArray &fmt : QImageWriter::supportedImageFormats()) {
56 		if(suffix == fmt)
57 			return true;
58 	}
59 
60 	return false;
61 }
62 
writableImageFormats()63 QVector<QPair<QString,QByteArray>> writableImageFormats()
64 {
65 	QVector<QPair<QString,QByteArray>> formats;
66 
67 	// We support ORA ourselves
68 	formats.append(QPair<QString,QByteArray>("OpenRaster", "ora"));
69 
70 	// Get list of available formats
71 	for(const QByteArray &fmt : QImageWriter::supportedImageFormats())
72 	{
73 		// only offer a reasonable subset
74 		if(fmt == "png" || fmt=="jpeg" || fmt=="bmp" || fmt=="gif" || fmt=="tiff")
75 			formats.append(QPair<QString,QByteArray>(QString(fmt).toUpper(), fmt));
76 		else if(fmt=="jp2")
77 			formats.append(QPair<QString,QByteArray>("JPEG2000", fmt));
78 	}
79 	return formats;
80 }
81 
fileFormatFilter(FileFormatOptions formats)82 QString fileFormatFilter(FileFormatOptions formats)
83 {
84 	QStringList filter;
85 	QString readImages, recordings;
86 
87 	if(formats.testFlag(FileFormatOption::Images)) {
88 		if(formats.testFlag(FileFormatOption::Save)) {
89 			// List all image formats for saving
90 			for(const auto &format : utils::writableImageFormats()) {
91 				filter << QStringLiteral("%1 (*.%2)").arg(format.first, QString::fromLatin1(format.second));
92 			}
93 
94 		} else {
95 			// A single Images filter for loading
96 			if(!formats.testFlag(FileFormatOption::QtImagesOnly))
97 				readImages = "*.ora ";
98 
99 			for(QByteArray format : QImageReader::supportedImageFormats()) {
100 				readImages += "*." + format + " ";
101 			}
102 
103 			filter << QGuiApplication::tr("Images (%1)").arg(readImages);
104 		}
105 	}
106 
107 	if(formats.testFlag(FileFormatOption::Recordings)) {
108 		if(formats.testFlag(FileFormatOption::Save)) {
109 			// Recording formats individually for saving
110 			filter
111 				<< QGuiApplication::tr("Binary Recordings (%1)").arg("*.dprec")
112 				<< QGuiApplication::tr("Text Recordings (%1)").arg("*.dptxt")
113 				<< QGuiApplication::tr("Compressed Binary Recordings (%1)").arg("*.dprecz")
114 				<< QGuiApplication::tr("Compressed Text Recordings (%1)").arg("*.dptxtz")
115 				;
116 
117 		} else {
118 			// A single Recordings filter for loading
119 			recordings = "*.dprec *.dptxt *.dprecz *.dptxtz *.dprec.gz *.dptxt.gz";
120 			filter
121 				<< QGuiApplication::tr("Recordings (%1)").arg(recordings)
122 				;
123 		}
124 	}
125 
126 	if(!readImages.isEmpty() && !recordings.isEmpty()) {
127 		filter.prepend(
128 			QGuiApplication::tr("All Supported Files (%1)").arg(readImages + recordings)
129 		);
130 	}
131 
132 	// An all files filter when requested
133 	if(formats.testFlag(FileFormatOption::AllFiles)) {
134 		filter << QGuiApplication::tr("All Files (*)");
135 	}
136 
137 	return filter.join(";;");
138 }
139 
isSolidColorImage(const QImage & image)140 QColor isSolidColorImage(const QImage &image)
141 {
142 	Q_ASSERT(image.format() == QImage::Format_ARGB32_Premultiplied);
143 	if(image.format() != QImage::Format_ARGB32_Premultiplied) {
144 		qWarning("isSolidColorImage: not a premultiplied ARGB32 image!");
145 		return QColor();
146 	}
147 
148 	const quint32 c = *reinterpret_cast<const quint32*>(image.bits());
149 	const quint32 *p = reinterpret_cast<const quint32*>(image.bits());
150 
151 	int len = image.width() * image.height();
152 	while(--len) {
153 		if(*(++p) != c)
154 			return QColor();
155 	}
156 	return QColor::fromRgba(c);
157 }
158 
159 }
160