1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "ui/image/image.h"
9 
10 #include "storage/cache/storage_cache_database.h"
11 #include "data/data_session.h"
12 #include "main/main_session.h"
13 #include "ui/ui_utility.h"
14 
15 using namespace Images;
16 
17 namespace Images {
18 namespace {
19 
PixKey(int width,int height,Options options)20 [[nodiscard]] uint64 PixKey(int width, int height, Options options) {
21 	return static_cast<uint64>(width)
22 		| (static_cast<uint64>(height) << 24)
23 		| (static_cast<uint64>(options) << 48);
24 }
25 
SinglePixKey(Options options)26 [[nodiscard]] uint64 SinglePixKey(Options options) {
27 	return PixKey(0, 0, options);
28 }
29 
30 } // namespace
31 
ExpandInlineBytes(const QByteArray & bytes)32 QByteArray ExpandInlineBytes(const QByteArray &bytes) {
33 	if (bytes.size() < 3 || bytes[0] != '\x01') {
34 		return QByteArray();
35 	}
36 	const char header[] = "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49"
37 		"\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xdb\x00\x43\x00\x28\x1c"
38 		"\x1e\x23\x1e\x19\x28\x23\x21\x23\x2d\x2b\x28\x30\x3c\x64\x41\x3c\x37\x37"
39 		"\x3c\x7b\x58\x5d\x49\x64\x91\x80\x99\x96\x8f\x80\x8c\x8a\xa0\xb4\xe6\xc3"
40 		"\xa0\xaa\xda\xad\x8a\x8c\xc8\xff\xcb\xda\xee\xf5\xff\xff\xff\x9b\xc1\xff"
41 		"\xff\xff\xfa\xff\xe6\xfd\xff\xf8\xff\xdb\x00\x43\x01\x2b\x2d\x2d\x3c\x35"
42 		"\x3c\x76\x41\x41\x76\xf8\xa5\x8c\xa5\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8"
43 		"\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8"
44 		"\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8"
45 		"\xf8\xf8\xf8\xf8\xf8\xff\xc0\x00\x11\x08\x00\x00\x00\x00\x03\x01\x22\x00"
46 		"\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01"
47 		"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08"
48 		"\x09\x0a\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05"
49 		"\x04\x04\x00\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05\x12\x21\x31\x41\x06"
50 		"\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xa1\x08\x23\x42\xb1\xc1\x15\x52"
51 		"\xd1\xf0\x24\x33\x62\x72\x82\x09\x0a\x16\x17\x18\x19\x1a\x25\x26\x27\x28"
52 		"\x29\x2a\x34\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a\x53"
53 		"\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75"
54 		"\x76\x77\x78\x79\x7a\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96"
55 		"\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6"
56 		"\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6"
57 		"\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4"
58 		"\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01"
59 		"\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08"
60 		"\x09\x0a\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05"
61 		"\x04\x04\x00\x01\x02\x77\x00\x01\x02\x03\x11\x04\x05\x21\x31\x06\x12\x41"
62 		"\x51\x07\x61\x71\x13\x22\x32\x81\x08\x14\x42\x91\xa1\xb1\xc1\x09\x23\x33"
63 		"\x52\xf0\x15\x62\x72\xd1\x0a\x16\x24\x34\xe1\x25\xf1\x17\x18\x19\x1a\x26"
64 		"\x27\x28\x29\x2a\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49\x4a"
65 		"\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74"
66 		"\x75\x76\x77\x78\x79\x7a\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94"
67 		"\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4"
68 		"\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4"
69 		"\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4"
70 		"\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00"
71 		"\x3f\x00";
72 	const char footer[] = "\xff\xd9";
73 	auto real = QByteArray(header, sizeof(header) - 1);
74 	real[164] = bytes[1];
75 	real[166] = bytes[2];
76 	return real
77 		+ bytes.mid(3)
78 		+ QByteArray::fromRawData(footer, sizeof(footer) - 1);
79 }
80 
FromInlineBytes(const QByteArray & bytes)81 QImage FromInlineBytes(const QByteArray &bytes) {
82 	return Read({ .content = ExpandInlineBytes(bytes) }).image;
83 }
84 
85 // Thanks TDLib for code.
ExpandPathInlineBytes(const QByteArray & bytes)86 QByteArray ExpandPathInlineBytes(const QByteArray &bytes) {
87 	auto result = QByteArray();
88 	result.reserve(3 * (bytes.size() + 1));
89 	result.append('M');
90 	for (unsigned char c : bytes) {
91 		if (c >= 128 + 64) {
92 			result.append("AACAAAAHAAALMAAAQASTAVAAAZ"
93 				"aacaaaahaaalmaaaqastava.az0123456789-,"[c - 128 - 64]);
94 		} else {
95 			if (c >= 128) {
96 				result.append(',');
97 			} else if (c >= 64) {
98 				result.append('-');
99 			}
100 			//char buffer[3] = { 0 }; // Unavailable on macOS < 10.15.
101 			//std::to_chars(buffer, buffer + 3, (c & 63));
102 			//result.append(buffer);
103 			result.append(QByteArray::number(c & 63));
104 		}
105 	}
106 	result.append('z');
107 	return result;
108 }
109 
PathFromInlineBytes(const QByteArray & bytes)110 QPainterPath PathFromInlineBytes(const QByteArray &bytes) {
111 	if (bytes.isEmpty()) {
112 		return QPainterPath();
113 	}
114 	const auto expanded = ExpandPathInlineBytes(bytes);
115 	const auto path = expanded.data(); // Allows checking for '\0' by index.
116 	auto position = 0;
117 
118 	const auto isAlpha = [](char c) {
119 		c |= 0x20;
120 		return 'a' <= c && c <= 'z';
121 	};
122 	const auto isDigit = [](char c) {
123 		return '0' <= c && c <= '9';
124 	};
125 	const auto skipCommas = [&] {
126 		while (path[position] == ',') {
127 			++position;
128 		}
129 	};
130 	const auto getNumber = [&] {
131 		skipCommas();
132 		auto sign = 1;
133 		if (path[position] == '-') {
134 			sign = -1;
135 			++position;
136 		}
137 		double res = 0;
138 		while (isDigit(path[position])) {
139 			res = res * 10 + path[position++] - '0';
140 		}
141 		if (path[position] == '.') {
142 			++position;
143 			double mul = 0.1;
144 			while (isDigit(path[position])) {
145 				res += (path[position] - '0') * mul;
146 				mul *= 0.1;
147 				++position;
148 			}
149 		}
150 		return sign * res;
151 	};
152 
153 	auto result = QPainterPath();
154 	auto x = 0.;
155 	auto y = 0.;
156 	while (path[position] != '\0') {
157 		skipCommas();
158 		if (path[position] == '\0') {
159 			break;
160 		}
161 
162 		while (path[position] == 'm' || path[position] == 'M') {
163 			auto command = path[position++];
164 			do {
165 				if (command == 'm') {
166 					x += getNumber();
167 					y += getNumber();
168 				} else {
169 					x = getNumber();
170 					y = getNumber();
171 				}
172 				skipCommas();
173 			} while (path[position] != '\0' && !isAlpha(path[position]));
174 		}
175 
176 		auto xStart = x;
177 		auto yStart = y;
178 		result.moveTo(xStart, yStart);
179 		auto haveLastEndControlPoint = false;
180 		auto xLastEndControlPoint = 0.;
181 		auto yLastEndControlPoint = 0.;
182 		auto isClosed = false;
183 		auto command = '-';
184 		while (!isClosed) {
185 			skipCommas();
186 			if (path[position] == '\0') {
187 				LOG(("SVG Error: Receive unclosed path: %1"
188 					).arg(QString::fromLatin1(path)));
189 				return QPainterPath();
190 			}
191 			if (isAlpha(path[position])) {
192 				command = path[position++];
193 			}
194 			switch (command) {
195 			case 'l':
196 			case 'L':
197 			case 'h':
198 			case 'H':
199 			case 'v':
200 			case 'V':
201 				if (command == 'l' || command == 'h') {
202 					x += getNumber();
203 				} else if (command == 'L' || command == 'H') {
204 					x = getNumber();
205 				}
206 				if (command == 'l' || command == 'v') {
207 					y += getNumber();
208 				} else if (command == 'L' || command == 'V') {
209 					y = getNumber();
210 				}
211 				result.lineTo(x, y);
212 				haveLastEndControlPoint = false;
213 				break;
214 			case 'C':
215 			case 'c':
216 			case 'S':
217 			case 's': {
218 				auto xStartControlPoint = 0.;
219 				auto yStartControlPoint = 0.;
220 				if (command == 'S' || command == 's') {
221 					if (haveLastEndControlPoint) {
222 						xStartControlPoint = 2 * x - xLastEndControlPoint;
223 						yStartControlPoint = 2 * y - yLastEndControlPoint;
224 					} else {
225 						xStartControlPoint = x;
226 						yStartControlPoint = y;
227 					}
228 				} else {
229 					xStartControlPoint = getNumber();
230 					yStartControlPoint = getNumber();
231 					if (command == 'c') {
232 						xStartControlPoint += x;
233 						yStartControlPoint += y;
234 					}
235 				}
236 
237 				xLastEndControlPoint = getNumber();
238 				yLastEndControlPoint = getNumber();
239 				if (command == 'c' || command == 's') {
240 					xLastEndControlPoint += x;
241 					yLastEndControlPoint += y;
242 				}
243 				haveLastEndControlPoint = true;
244 
245 				if (command == 'c' || command == 's') {
246 					x += getNumber();
247 					y += getNumber();
248 				} else {
249 					x = getNumber();
250 					y = getNumber();
251 				}
252 				result.cubicTo(
253 					xStartControlPoint,
254 					yStartControlPoint,
255 					xLastEndControlPoint,
256 					yLastEndControlPoint,
257 					x,
258 					y);
259 				break;
260 			}
261 			case 'm':
262 			case 'M':
263 				--position;
264 				[[fallthrough]];
265 			case 'z':
266 			case 'Z':
267 				if (x != xStart || y != yStart) {
268 					x = xStart;
269 					y = yStart;
270 					result.lineTo(x, y);
271 				}
272 				isClosed = true;
273 				break;
274 			default:
275 				LOG(("SVG Error: Receive invalid command %1 at pos %2: %3"
276 					).arg(command
277 					).arg(position
278 					).arg(QString::fromLatin1(path)));
279 				return QPainterPath();
280 			}
281 		}
282 	}
283 	return result;
284 }
285 
286 } // namespace Images
287 
Image(const QString & path)288 Image::Image(const QString &path)
289 : Image(Read({ .path = path }).image) {
290 }
291 
Image(const QByteArray & content)292 Image::Image(const QByteArray &content)
293 : Image(Read({ .content = content }).image) {
294 }
295 
Image(QImage && data)296 Image::Image(QImage &&data)
297 : _data(data.isNull() ? Empty()->original() : std::move(data)) {
298 	Expects(!_data.isNull());
299 }
300 
Empty()301 not_null<Image*> Image::Empty() {
302 	static auto result = Image([] {
303 		const auto factor = cIntRetinaFactor();
304 		auto data = QImage(
305 			factor,
306 			factor,
307 			QImage::Format_ARGB32_Premultiplied);
308 		data.fill(Qt::transparent);
309 		data.setDevicePixelRatio(cRetinaFactor());
310 		return data;
311 	}());
312 	return &result;
313 }
314 
BlankMedia()315 not_null<Image*> Image::BlankMedia() {
316 	static auto result = Image([] {
317 		const auto factor = cIntRetinaFactor();
318 		auto data = QImage(
319 			factor,
320 			factor,
321 			QImage::Format_ARGB32_Premultiplied);
322 		data.fill(Qt::black);
323 		data.setDevicePixelRatio(cRetinaFactor());
324 		return data;
325 	}());
326 	return &result;
327 }
328 
original() const329 QImage Image::original() const {
330 	return _data;
331 }
332 
pix(int w,int h) const333 const QPixmap &Image::pix(int w, int h) const {
334 	if (w <= 0 || !width() || !height()) {
335 		w = width();
336 	} else {
337 		w *= cIntRetinaFactor();
338 		h *= cIntRetinaFactor();
339 	}
340 	auto options = Option::Smooth | Option::None;
341 	auto k = PixKey(w, h, options);
342 	auto i = _cache.find(k);
343 	if (i == _cache.cend()) {
344 		auto p = pixNoCache(w, h, options);
345 		p.setDevicePixelRatio(cRetinaFactor());
346 		i = _cache.emplace_or_assign(k, p).first;
347 	}
348 	return i->second;
349 }
350 
pixRounded(int w,int h,ImageRoundRadius radius,RectParts corners) const351 const QPixmap &Image::pixRounded(
352 		int w,
353 		int h,
354 		ImageRoundRadius radius,
355 		RectParts corners) const {
356 	if (w <= 0 || !width() || !height()) {
357 		w = width();
358 	} else {
359 		w *= cIntRetinaFactor();
360 		h *= cIntRetinaFactor();
361 	}
362 	auto options = Option::Smooth | Option::None;
363 	auto cornerOptions = [](RectParts corners) {
364 		return (corners & RectPart::TopLeft ? Option::RoundedTopLeft : Option::None)
365 			| (corners & RectPart::TopRight ? Option::RoundedTopRight : Option::None)
366 			| (corners & RectPart::BottomLeft ? Option::RoundedBottomLeft : Option::None)
367 			| (corners & RectPart::BottomRight ? Option::RoundedBottomRight : Option::None);
368 	};
369 	if (radius == ImageRoundRadius::Large) {
370 		options |= Option::RoundedLarge | cornerOptions(corners);
371 	} else if (radius == ImageRoundRadius::Small) {
372 		options |= Option::RoundedSmall | cornerOptions(corners);
373 	} else if (radius == ImageRoundRadius::Ellipse) {
374 		options |= Option::Circled | cornerOptions(corners);
375 	}
376 	auto k = PixKey(w, h, options);
377 	auto i = _cache.find(k);
378 	if (i == _cache.cend()) {
379 		auto p = pixNoCache(w, h, options);
380 		p.setDevicePixelRatio(cRetinaFactor());
381 		i = _cache.emplace_or_assign(k, p).first;
382 	}
383 	return i->second;
384 }
385 
pixCircled(int w,int h) const386 const QPixmap &Image::pixCircled(int w, int h) const {
387 	if (w <= 0 || !width() || !height()) {
388 		w = width();
389 	} else {
390 		w *= cIntRetinaFactor();
391 		h *= cIntRetinaFactor();
392 	}
393 	auto options = Option::Smooth | Option::Circled;
394 	auto k = PixKey(w, h, options);
395 	auto i = _cache.find(k);
396 	if (i == _cache.cend()) {
397 		auto p = pixNoCache(w, h, options);
398 		p.setDevicePixelRatio(cRetinaFactor());
399 		i = _cache.emplace_or_assign(k, p).first;
400 	}
401 	return i->second;
402 }
403 
pixBlurredCircled(int w,int h) const404 const QPixmap &Image::pixBlurredCircled(int w, int h) const {
405 	if (w <= 0 || !width() || !height()) {
406 		w = width();
407 	} else {
408 		w *= cIntRetinaFactor();
409 		h *= cIntRetinaFactor();
410 	}
411 	auto options = Option::Smooth | Option::Circled | Option::Blurred;
412 	auto k = PixKey(w, h, options);
413 	auto i = _cache.find(k);
414 	if (i == _cache.cend()) {
415 		auto p = pixNoCache(w, h, options);
416 		p.setDevicePixelRatio(cRetinaFactor());
417 		i = _cache.emplace_or_assign(k, p).first;
418 	}
419 	return i->second;
420 }
421 
pixBlurred(int w,int h) const422 const QPixmap &Image::pixBlurred(int w, int h) const {
423 	if (w <= 0 || !width() || !height()) {
424 		w = width() * cIntRetinaFactor();
425 	} else {
426 		w *= cIntRetinaFactor();
427 		h *= cIntRetinaFactor();
428 	}
429 	auto options = Option::Smooth | Option::Blurred;
430 	auto k = PixKey(w, h, options);
431 	auto i = _cache.find(k);
432 	if (i == _cache.cend()) {
433 		auto p = pixNoCache(w, h, options);
434 		p.setDevicePixelRatio(cRetinaFactor());
435 		i = _cache.emplace_or_assign(k, p).first;
436 	}
437 	return i->second;
438 }
439 
pixColored(style::color add,int w,int h) const440 const QPixmap &Image::pixColored(style::color add, int w, int h) const {
441 	if (w <= 0 || !width() || !height()) {
442 		w = width() * cIntRetinaFactor();
443 	} else {
444 		w *= cIntRetinaFactor();
445 		h *= cIntRetinaFactor();
446 	}
447 	auto options = Option::Smooth | Option::Colored;
448 	auto k = PixKey(w, h, options);
449 	auto i = _cache.find(k);
450 	if (i == _cache.cend()) {
451 		auto p = pixColoredNoCache(add, w, h, true);
452 		p.setDevicePixelRatio(cRetinaFactor());
453 		i = _cache.emplace_or_assign(k, p).first;
454 	}
455 	return i->second;
456 }
457 
pixBlurredColored(style::color add,int w,int h) const458 const QPixmap &Image::pixBlurredColored(
459 		style::color add,
460 		int w,
461 		int h) const {
462 	if (w <= 0 || !width() || !height()) {
463 		w = width() * cIntRetinaFactor();
464 	} else {
465 		w *= cIntRetinaFactor();
466 		h *= cIntRetinaFactor();
467 	}
468 	auto options = Option::Blurred | Option::Smooth | Option::Colored;
469 	auto k = PixKey(w, h, options);
470 	auto i = _cache.find(k);
471 	if (i == _cache.cend()) {
472 		auto p = pixBlurredColoredNoCache(add, w, h);
473 		p.setDevicePixelRatio(cRetinaFactor());
474 		i = _cache.emplace_or_assign(k, p).first;
475 	}
476 	return i->second;
477 }
478 
pixSingle(int w,int h,int outerw,int outerh,ImageRoundRadius radius,RectParts corners,const style::color * colored) const479 const QPixmap &Image::pixSingle(
480 		int w,
481 		int h,
482 		int outerw,
483 		int outerh,
484 		ImageRoundRadius radius,
485 		RectParts corners,
486 		const style::color *colored) const {
487 	if (w <= 0 || !width() || !height()) {
488 		w = width() * cIntRetinaFactor();
489 	} else {
490 		w *= cIntRetinaFactor();
491 		h *= cIntRetinaFactor();
492 	}
493 
494 	auto options = Option::Smooth | Option::None;
495 	auto cornerOptions = [](RectParts corners) {
496 		return (corners & RectPart::TopLeft ? Option::RoundedTopLeft : Option::None)
497 			| (corners & RectPart::TopRight ? Option::RoundedTopRight : Option::None)
498 			| (corners & RectPart::BottomLeft ? Option::RoundedBottomLeft : Option::None)
499 			| (corners & RectPart::BottomRight ? Option::RoundedBottomRight : Option::None);
500 	};
501 	if (radius == ImageRoundRadius::Large) {
502 		options |= Option::RoundedLarge | cornerOptions(corners);
503 	} else if (radius == ImageRoundRadius::Small) {
504 		options |= Option::RoundedSmall | cornerOptions(corners);
505 	} else if (radius == ImageRoundRadius::Ellipse) {
506 		options |= Option::Circled | cornerOptions(corners);
507 	}
508 	if (colored) {
509 		options |= Option::Colored;
510 	}
511 
512 	auto k = SinglePixKey(options);
513 	auto i = _cache.find(k);
514 	if (i == _cache.cend() || i->second.width() != (outerw * cIntRetinaFactor()) || i->second.height() != (outerh * cIntRetinaFactor())) {
515 		auto p = pixNoCache(w, h, options, outerw, outerh, colored);
516 		p.setDevicePixelRatio(cRetinaFactor());
517 		i = _cache.emplace_or_assign(k, p).first;
518 	}
519 	return i->second;
520 }
521 
pixBlurredSingle(int w,int h,int outerw,int outerh,ImageRoundRadius radius,RectParts corners,const style::color * colored) const522 const QPixmap &Image::pixBlurredSingle(
523 		int w,
524 		int h,
525 		int outerw,
526 		int outerh,
527 		ImageRoundRadius radius,
528 		RectParts corners,
529 		const style::color *colored) const {
530 	if (w <= 0 || !width() || !height()) {
531 		w = width() * cIntRetinaFactor();
532 	} else {
533 		w *= cIntRetinaFactor();
534 		h *= cIntRetinaFactor();
535 	}
536 
537 	auto options = Option::Smooth | Option::Blurred;
538 	auto cornerOptions = [](RectParts corners) {
539 		return (corners & RectPart::TopLeft ? Option::RoundedTopLeft : Option::None)
540 			| (corners & RectPart::TopRight ? Option::RoundedTopRight : Option::None)
541 			| (corners & RectPart::BottomLeft ? Option::RoundedBottomLeft : Option::None)
542 			| (corners & RectPart::BottomRight ? Option::RoundedBottomRight : Option::None);
543 	};
544 	if (radius == ImageRoundRadius::Large) {
545 		options |= Option::RoundedLarge | cornerOptions(corners);
546 	} else if (radius == ImageRoundRadius::Small) {
547 		options |= Option::RoundedSmall | cornerOptions(corners);
548 	} else if (radius == ImageRoundRadius::Ellipse) {
549 		options |= Option::Circled | cornerOptions(corners);
550 	}
551 	if (colored) {
552 		options |= Option::Colored;
553 	}
554 
555 	auto k = SinglePixKey(options);
556 	auto i = _cache.find(k);
557 	if (i == _cache.cend() || i->second.width() != (outerw * cIntRetinaFactor()) || i->second.height() != (outerh * cIntRetinaFactor())) {
558 		auto p = pixNoCache(w, h, options, outerw, outerh, colored);
559 		p.setDevicePixelRatio(cRetinaFactor());
560 		i = _cache.emplace_or_assign(k, p).first;
561 	}
562 	return i->second;
563 }
564 
pixNoCache(int w,int h,Options options,int outerw,int outerh,const style::color * colored) const565 QPixmap Image::pixNoCache(
566 		int w,
567 		int h,
568 		Options options,
569 		int outerw,
570 		int outerh,
571 		const style::color *colored) const {
572 	if (_data.isNull()) {
573 		if (h <= 0 && height() > 0) {
574 			h = qRound(width() * w / float64(height()));
575 		}
576 		return Empty()->pixNoCache(w, h, options, outerw, outerh);
577 	}
578 
579 	if (isNull() && outerw > 0 && outerh > 0) {
580 		outerw *= cIntRetinaFactor();
581 		outerh *= cIntRetinaFactor();
582 
583 		QImage result(outerw, outerh, QImage::Format_ARGB32_Premultiplied);
584 		result.setDevicePixelRatio(style::DevicePixelRatio());
585 
586 		{
587 			QPainter p(&result);
588 			if (w < outerw) {
589 				p.fillRect(0, 0, (outerw - w) / 2, result.height(), st::imageBg);
590 				p.fillRect(((outerw - w) / 2) + w, 0, result.width() - (((outerw - w) / 2) + w), result.height(), st::imageBg);
591 			}
592 			if (h < outerh) {
593 				p.fillRect(qMax(0, (outerw - w) / 2), 0, qMin(result.width(), w), (outerh - h) / 2, st::imageBg);
594 				p.fillRect(qMax(0, (outerw - w) / 2), ((outerh - h) / 2) + h, qMin(result.width(), w), result.height() - (((outerh - h) / 2) + h), st::imageBg);
595 			}
596 			p.fillRect(qMax(0, (outerw - w) / 2), qMax(0, (outerh - h) / 2), qMin(result.width(), w), qMin(result.height(), h), st::imageBgTransparent);
597 		}
598 
599 		auto corners = [](Options options) {
600 			return ((options & Option::RoundedTopLeft) ? RectPart::TopLeft : RectPart::None)
601 				| ((options & Option::RoundedTopRight) ? RectPart::TopRight : RectPart::None)
602 				| ((options & Option::RoundedBottomLeft) ? RectPart::BottomLeft : RectPart::None)
603 				| ((options & Option::RoundedBottomRight) ? RectPart::BottomRight : RectPart::None);
604 		};
605 		if (options & Option::Circled) {
606 			prepareCircle(result);
607 		} else if (options & Option::RoundedLarge) {
608 			prepareRound(result, ImageRoundRadius::Large, corners(options));
609 		} else if (options & Option::RoundedSmall) {
610 			prepareRound(result, ImageRoundRadius::Small, corners(options));
611 		}
612 		if (options & Option::Colored) {
613 			Assert(colored != nullptr);
614 			result = prepareColored(*colored, std::move(result));
615 		}
616 		return Ui::PixmapFromImage(std::move(result));
617 	}
618 
619 	return Ui::PixmapFromImage(
620 		prepare(_data, w, h, options, outerw, outerh, colored));
621 }
622 
pixColoredNoCache(style::color add,int w,int h,bool smooth) const623 QPixmap Image::pixColoredNoCache(
624 		style::color add,
625 		int w,
626 		int h,
627 		bool smooth) const {
628 	if (_data.isNull()) {
629 		return Empty()->pix();
630 	}
631 
632 	auto img = _data;
633 	if (w <= 0 || !width() || !height() || (w == width() && (h <= 0 || h == height()))) {
634 		return Ui::PixmapFromImage(prepareColored(add, std::move(img)));
635 	}
636 	const auto transformation = smooth
637 		? Qt::SmoothTransformation
638 		: Qt::FastTransformation;
639 	if (h <= 0) {
640 		return Ui::PixmapFromImage(
641 			prepareColored(add, img.scaledToWidth(w, transformation)));
642 	}
643 	return Ui::PixmapFromImage(
644 		prepareColored(
645 			add,
646 			img.scaled(w, h, Qt::IgnoreAspectRatio, transformation)));
647 }
648 
pixBlurredColoredNoCache(style::color add,int w,int h) const649 QPixmap Image::pixBlurredColoredNoCache(
650 		style::color add,
651 		int w,
652 		int h) const {
653 	if (_data.isNull()) {
654 		return Empty()->pix();
655 	}
656 
657 	auto img = prepareBlur(_data);
658 	if (h <= 0) {
659 		img = img.scaledToWidth(w, Qt::SmoothTransformation);
660 	} else {
661 		img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
662 	}
663 
664 	return Ui::PixmapFromImage(prepareColored(add, img));
665 }
666