mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-31 22:46:10 +00:00
Move image-related modules to ui/image/.
This commit is contained in:
809
Telegram/SourceFiles/ui/image/image.cpp
Normal file
809
Telegram/SourceFiles/ui/image/image.cpp
Normal file
@@ -0,0 +1,809 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/image/image.h"
|
||||
|
||||
#include "ui/image/image_source.h"
|
||||
#include "storage/cache/storage_cache_database.h"
|
||||
#include "data/data_session.h"
|
||||
#include "auth_session.h"
|
||||
|
||||
namespace Images {
|
||||
namespace {
|
||||
|
||||
QMap<QString, Image*> LocalFileImages;
|
||||
QMap<QString, Image*> WebUrlImages;
|
||||
QMap<StorageKey, Image*> StorageImages;
|
||||
QMap<StorageKey, Image*> WebCachedImages;
|
||||
QMap<StorageKey, Image*> GeoPointImages;
|
||||
|
||||
int64 GlobalAcquiredSize = 0;
|
||||
int64 LocalAcquiredSize = 0;
|
||||
|
||||
uint64 PixKey(int width, int height, Images::Options options) {
|
||||
return static_cast<uint64>(width) | (static_cast<uint64>(height) << 24) | (static_cast<uint64>(options) << 48);
|
||||
}
|
||||
|
||||
uint64 SinglePixKey(Images::Options options) {
|
||||
return PixKey(0, 0, options);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ClearRemote() {
|
||||
for (auto image : base::take(StorageImages)) {
|
||||
delete image;
|
||||
}
|
||||
for (auto image : base::take(WebUrlImages)) {
|
||||
delete image;
|
||||
}
|
||||
for (auto image : base::take(WebCachedImages)) {
|
||||
delete image;
|
||||
}
|
||||
for (auto image : base::take(GeoPointImages)) {
|
||||
delete image;
|
||||
}
|
||||
LocalAcquiredSize = GlobalAcquiredSize;
|
||||
}
|
||||
|
||||
void ClearAll() {
|
||||
for (auto image : base::take(LocalFileImages)) {
|
||||
delete image;
|
||||
}
|
||||
ClearRemote();
|
||||
}
|
||||
|
||||
void CheckCacheSize() {
|
||||
const auto now = GlobalAcquiredSize;
|
||||
if (GlobalAcquiredSize > LocalAcquiredSize + MemoryForImageCache) {
|
||||
Auth().data().forgetMedia();
|
||||
LocalAcquiredSize = GlobalAcquiredSize;
|
||||
}
|
||||
}
|
||||
|
||||
ImagePtr Create(const QString &file, QByteArray format) {
|
||||
if (file.startsWith(qstr("http://"), Qt::CaseInsensitive)
|
||||
|| file.startsWith(qstr("https://"), Qt::CaseInsensitive)) {
|
||||
const auto key = file;
|
||||
auto i = WebUrlImages.constFind(key);
|
||||
if (i == WebUrlImages.cend()) {
|
||||
i = WebUrlImages.insert(
|
||||
key,
|
||||
new Image(std::make_unique<WebUrlSource>(file)));
|
||||
}
|
||||
return ImagePtr(i.value());
|
||||
} else {
|
||||
QFileInfo f(file);
|
||||
const auto key = qsl("//:%1//:%2//:"
|
||||
).arg(f.size()
|
||||
).arg(f.lastModified().toTime_t()
|
||||
) + file;
|
||||
auto i = LocalFileImages.constFind(key);
|
||||
if (i == LocalFileImages.cend()) {
|
||||
i = LocalFileImages.insert(
|
||||
key,
|
||||
new Image(std::make_unique<LocalFileSource>(
|
||||
file,
|
||||
QByteArray(),
|
||||
format)));
|
||||
}
|
||||
return ImagePtr(i.value());
|
||||
}
|
||||
}
|
||||
|
||||
ImagePtr Create(const QString &url, QSize box) {
|
||||
const auto key = qsl("//:%1//:%2//:").arg(box.width()).arg(box.height()) + url;
|
||||
auto i = WebUrlImages.constFind(key);
|
||||
if (i == WebUrlImages.cend()) {
|
||||
i = WebUrlImages.insert(
|
||||
key,
|
||||
new Image(std::make_unique<WebUrlSource>(url, box)));
|
||||
}
|
||||
return ImagePtr(i.value());
|
||||
}
|
||||
|
||||
ImagePtr Create(const QString &url, int width, int height) {
|
||||
const auto key = url;
|
||||
auto i = WebUrlImages.constFind(key);
|
||||
if (i == WebUrlImages.cend()) {
|
||||
i = WebUrlImages.insert(
|
||||
key,
|
||||
new Image(std::make_unique<WebUrlSource>(url, width, height)));
|
||||
} else {
|
||||
i.value()->setInformation(0, width, height);
|
||||
}
|
||||
return ImagePtr(i.value());
|
||||
}
|
||||
|
||||
ImagePtr Create(const QByteArray &filecontent, QByteArray format) {
|
||||
auto image = App::readImage(filecontent, &format, false);
|
||||
return Create(filecontent, format, std::move(image));
|
||||
}
|
||||
|
||||
ImagePtr Create(QImage &&image, QByteArray format) {
|
||||
return ImagePtr(new Image(std::make_unique<ImageSource>(
|
||||
std::move(image),
|
||||
format)));
|
||||
}
|
||||
|
||||
ImagePtr Create(
|
||||
const QByteArray &filecontent,
|
||||
QByteArray format,
|
||||
QImage &&image) {
|
||||
return ImagePtr(new Image(std::make_unique<LocalFileSource>(
|
||||
QString(),
|
||||
filecontent,
|
||||
format,
|
||||
std::move(image))));
|
||||
}
|
||||
|
||||
ImagePtr Create(int width, int height) {
|
||||
return ImagePtr(new Image(std::make_unique<DelayedStorageSource>(
|
||||
width,
|
||||
height)));
|
||||
}
|
||||
|
||||
ImagePtr Create(const StorageImageLocation &location, int size) {
|
||||
const auto key = storageKey(location);
|
||||
auto i = StorageImages.constFind(key);
|
||||
if (i == StorageImages.cend()) {
|
||||
i = StorageImages.insert(
|
||||
key,
|
||||
new Image(std::make_unique<StorageSource>(location, size)));
|
||||
} else {
|
||||
i.value()->refreshFileReference(location.fileReference());
|
||||
}
|
||||
return ImagePtr(i.value());
|
||||
}
|
||||
|
||||
ImagePtr Create(
|
||||
const StorageImageLocation &location,
|
||||
const QByteArray &bytes) {
|
||||
const auto key = storageKey(location);
|
||||
auto i = StorageImages.constFind(key);
|
||||
if (i == StorageImages.cend()) {
|
||||
i = StorageImages.insert(
|
||||
key,
|
||||
new Image(std::make_unique<StorageSource>(
|
||||
location,
|
||||
bytes.size())));
|
||||
} else {
|
||||
i.value()->refreshFileReference(location.fileReference());
|
||||
}
|
||||
i.value()->setImageBytes(bytes);
|
||||
return ImagePtr(i.value());
|
||||
}
|
||||
|
||||
QSize getImageSize(const QVector<MTPDocumentAttribute> &attributes) {
|
||||
for (const auto &attribute : attributes) {
|
||||
if (attribute.type() == mtpc_documentAttributeImageSize) {
|
||||
auto &size = attribute.c_documentAttributeImageSize();
|
||||
return QSize(size.vw.v, size.vh.v);
|
||||
}
|
||||
}
|
||||
return QSize();
|
||||
}
|
||||
|
||||
ImagePtr Create(const MTPDwebDocument &document) {
|
||||
const auto size = getImageSize(document.vattributes.v);
|
||||
if (size.isEmpty()) {
|
||||
return Image::Blank();
|
||||
}
|
||||
|
||||
// We don't use size from WebDocument, because it is not reliable.
|
||||
// It can be > 0 and different from the real size that we get in upload.WebFile result.
|
||||
auto filesize = 0; // document.vsize.v;
|
||||
return Create(
|
||||
WebFileLocation(
|
||||
Global::WebFileDcId(),
|
||||
document.vurl.v,
|
||||
document.vaccess_hash.v),
|
||||
size.width(),
|
||||
size.height(),
|
||||
filesize);
|
||||
}
|
||||
|
||||
ImagePtr Create(const MTPDwebDocumentNoProxy &document) {
|
||||
const auto size = getImageSize(document.vattributes.v);
|
||||
if (size.isEmpty()) {
|
||||
return Image::Blank();
|
||||
}
|
||||
|
||||
return Create(qs(document.vurl), size.width(), size.height());
|
||||
}
|
||||
|
||||
ImagePtr Create(const MTPDwebDocument &document, QSize box) {
|
||||
//const auto size = getImageSize(document.vattributes.v);
|
||||
//if (size.isEmpty()) {
|
||||
// return Image::Blank();
|
||||
//}
|
||||
|
||||
// We don't use size from WebDocument, because it is not reliable.
|
||||
// It can be > 0 and different from the real size that we get in upload.WebFile result.
|
||||
auto filesize = 0; // document.vsize.v;
|
||||
return Create(
|
||||
WebFileLocation(
|
||||
Global::WebFileDcId(),
|
||||
document.vurl.v,
|
||||
document.vaccess_hash.v),
|
||||
box,
|
||||
filesize);
|
||||
}
|
||||
|
||||
ImagePtr Create(const MTPDwebDocumentNoProxy &document, QSize box) {
|
||||
//const auto size = getImageSize(document.vattributes.v);
|
||||
//if (size.isEmpty()) {
|
||||
// return Image::Blank();
|
||||
//}
|
||||
|
||||
return Create(qs(document.vurl), box);
|
||||
}
|
||||
|
||||
ImagePtr Create(const MTPWebDocument &document) {
|
||||
switch (document.type()) {
|
||||
case mtpc_webDocument:
|
||||
return Create(document.c_webDocument());
|
||||
case mtpc_webDocumentNoProxy:
|
||||
return Create(document.c_webDocumentNoProxy());
|
||||
}
|
||||
Unexpected("Type in getImage(MTPWebDocument).");
|
||||
}
|
||||
|
||||
ImagePtr Create(const MTPWebDocument &document, QSize box) {
|
||||
switch (document.type()) {
|
||||
case mtpc_webDocument:
|
||||
return Create(document.c_webDocument(), box);
|
||||
case mtpc_webDocumentNoProxy:
|
||||
return Create(document.c_webDocumentNoProxy(), box);
|
||||
}
|
||||
Unexpected("Type in getImage(MTPWebDocument).");
|
||||
}
|
||||
|
||||
ImagePtr Create(
|
||||
const WebFileLocation &location,
|
||||
QSize box,
|
||||
int size) {
|
||||
const auto key = storageKey(location);
|
||||
auto i = WebCachedImages.constFind(key);
|
||||
if (i == WebCachedImages.cend()) {
|
||||
i = WebCachedImages.insert(
|
||||
key,
|
||||
new Image(std::make_unique<WebCachedSource>(
|
||||
location,
|
||||
box,
|
||||
size)));
|
||||
}
|
||||
return ImagePtr(i.value());
|
||||
}
|
||||
|
||||
ImagePtr Create(
|
||||
const WebFileLocation &location,
|
||||
int width,
|
||||
int height,
|
||||
int size) {
|
||||
const auto key = storageKey(location);
|
||||
auto i = WebCachedImages.constFind(key);
|
||||
if (i == WebCachedImages.cend()) {
|
||||
i = WebCachedImages.insert(
|
||||
key,
|
||||
new Image(std::make_unique<WebCachedSource>(
|
||||
location,
|
||||
width,
|
||||
height,
|
||||
size)));
|
||||
}
|
||||
return ImagePtr(i.value());
|
||||
}
|
||||
|
||||
ImagePtr Create(const GeoPointLocation &location) {
|
||||
const auto key = storageKey(location);
|
||||
auto i = GeoPointImages.constFind(key);
|
||||
if (i == GeoPointImages.cend()) {
|
||||
i = GeoPointImages.insert(
|
||||
key,
|
||||
new Image(std::make_unique<GeoPointSource>(location)));
|
||||
}
|
||||
return ImagePtr(i.value());
|
||||
}
|
||||
|
||||
} // namespace Images
|
||||
|
||||
Image::Image(std::unique_ptr<Images::Source> &&source)
|
||||
: _source(std::move(source)) {
|
||||
}
|
||||
|
||||
void Image::replaceSource(std::unique_ptr<Images::Source> &&source) {
|
||||
_source = std::move(source);
|
||||
}
|
||||
|
||||
ImagePtr Image::Blank() {
|
||||
static const auto blankImage = [] {
|
||||
const auto factor = cIntRetinaFactor();
|
||||
auto data = QImage(
|
||||
factor,
|
||||
factor,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
data.fill(Qt::transparent);
|
||||
data.setDevicePixelRatio(cRetinaFactor());
|
||||
return Images::Create(
|
||||
std::move(data),
|
||||
"GIF");
|
||||
}();
|
||||
return blankImage;
|
||||
}
|
||||
|
||||
bool Image::isNull() const {
|
||||
return (this == Blank().get());
|
||||
}
|
||||
|
||||
const QPixmap &Image::pix(
|
||||
Data::FileOrigin origin,
|
||||
int32 w,
|
||||
int32 h) const {
|
||||
checkSource();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width();
|
||||
} else {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
auto options = Images::Option::Smooth | Images::Option::None;
|
||||
auto k = Images::PixKey(w, h, options);
|
||||
auto i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
auto p = pixNoCache(origin, w, h, options);
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixRounded(
|
||||
Data::FileOrigin origin,
|
||||
int32 w,
|
||||
int32 h,
|
||||
ImageRoundRadius radius,
|
||||
RectParts corners) const {
|
||||
checkSource();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width();
|
||||
} else {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
auto options = Images::Option::Smooth | Images::Option::None;
|
||||
auto cornerOptions = [](RectParts corners) {
|
||||
return (corners & RectPart::TopLeft ? Images::Option::RoundedTopLeft : Images::Option::None)
|
||||
| (corners & RectPart::TopRight ? Images::Option::RoundedTopRight : Images::Option::None)
|
||||
| (corners & RectPart::BottomLeft ? Images::Option::RoundedBottomLeft : Images::Option::None)
|
||||
| (corners & RectPart::BottomRight ? Images::Option::RoundedBottomRight : Images::Option::None);
|
||||
};
|
||||
if (radius == ImageRoundRadius::Large) {
|
||||
options |= Images::Option::RoundedLarge | cornerOptions(corners);
|
||||
} else if (radius == ImageRoundRadius::Small) {
|
||||
options |= Images::Option::RoundedSmall | cornerOptions(corners);
|
||||
} else if (radius == ImageRoundRadius::Ellipse) {
|
||||
options |= Images::Option::Circled | cornerOptions(corners);
|
||||
}
|
||||
auto k = Images::PixKey(w, h, options);
|
||||
auto i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
auto p = pixNoCache(origin, w, h, options);
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixCircled(
|
||||
Data::FileOrigin origin,
|
||||
int32 w,
|
||||
int32 h) const {
|
||||
checkSource();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width();
|
||||
} else {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
auto options = Images::Option::Smooth | Images::Option::Circled;
|
||||
auto k = Images::PixKey(w, h, options);
|
||||
auto i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
auto p = pixNoCache(origin, w, h, options);
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixBlurredCircled(
|
||||
Data::FileOrigin origin,
|
||||
int32 w,
|
||||
int32 h) const {
|
||||
checkSource();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width();
|
||||
} else {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
auto options = Images::Option::Smooth | Images::Option::Circled | Images::Option::Blurred;
|
||||
auto k = Images::PixKey(w, h, options);
|
||||
auto i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
auto p = pixNoCache(origin, w, h, options);
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixBlurred(
|
||||
Data::FileOrigin origin,
|
||||
int32 w,
|
||||
int32 h) const {
|
||||
checkSource();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width() * cIntRetinaFactor();
|
||||
} else {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
auto options = Images::Option::Smooth | Images::Option::Blurred;
|
||||
auto k = Images::PixKey(w, h, options);
|
||||
auto i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
auto p = pixNoCache(origin, w, h, options);
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixColored(
|
||||
Data::FileOrigin origin,
|
||||
style::color add,
|
||||
int32 w,
|
||||
int32 h) const {
|
||||
checkSource();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width() * cIntRetinaFactor();
|
||||
} else {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
auto options = Images::Option::Smooth | Images::Option::Colored;
|
||||
auto k = Images::PixKey(w, h, options);
|
||||
auto i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
auto p = pixColoredNoCache(origin, add, w, h, true);
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixBlurredColored(
|
||||
Data::FileOrigin origin,
|
||||
style::color add,
|
||||
int32 w,
|
||||
int32 h) const {
|
||||
checkSource();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width() * cIntRetinaFactor();
|
||||
} else {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
auto options = Images::Option::Blurred | Images::Option::Smooth | Images::Option::Colored;
|
||||
auto k = Images::PixKey(w, h, options);
|
||||
auto i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
auto p = pixBlurredColoredNoCache(origin, add, w, h);
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixSingle(
|
||||
Data::FileOrigin origin,
|
||||
int32 w,
|
||||
int32 h,
|
||||
int32 outerw,
|
||||
int32 outerh,
|
||||
ImageRoundRadius radius,
|
||||
RectParts corners,
|
||||
const style::color *colored) const {
|
||||
checkSource();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width() * cIntRetinaFactor();
|
||||
} else {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
|
||||
auto options = Images::Option::Smooth | Images::Option::None;
|
||||
auto cornerOptions = [](RectParts corners) {
|
||||
return (corners & RectPart::TopLeft ? Images::Option::RoundedTopLeft : Images::Option::None)
|
||||
| (corners & RectPart::TopRight ? Images::Option::RoundedTopRight : Images::Option::None)
|
||||
| (corners & RectPart::BottomLeft ? Images::Option::RoundedBottomLeft : Images::Option::None)
|
||||
| (corners & RectPart::BottomRight ? Images::Option::RoundedBottomRight : Images::Option::None);
|
||||
};
|
||||
if (radius == ImageRoundRadius::Large) {
|
||||
options |= Images::Option::RoundedLarge | cornerOptions(corners);
|
||||
} else if (radius == ImageRoundRadius::Small) {
|
||||
options |= Images::Option::RoundedSmall | cornerOptions(corners);
|
||||
} else if (radius == ImageRoundRadius::Ellipse) {
|
||||
options |= Images::Option::Circled | cornerOptions(corners);
|
||||
}
|
||||
if (colored) {
|
||||
options |= Images::Option::Colored;
|
||||
}
|
||||
|
||||
auto k = Images::SinglePixKey(options);
|
||||
auto i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend() || i->width() != (outerw * cIntRetinaFactor()) || i->height() != (outerh * cIntRetinaFactor())) {
|
||||
if (i != _sizesCache.cend()) {
|
||||
Images::GlobalAcquiredSize -= int64(i->width()) * i->height() * 4;
|
||||
}
|
||||
auto p = pixNoCache(origin, w, h, options, outerw, outerh, colored);
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixBlurredSingle(
|
||||
Data::FileOrigin origin,
|
||||
int32 w,
|
||||
int32 h,
|
||||
int32 outerw,
|
||||
int32 outerh,
|
||||
ImageRoundRadius radius,
|
||||
RectParts corners) const {
|
||||
checkSource();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width() * cIntRetinaFactor();
|
||||
} else {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
|
||||
auto options = Images::Option::Smooth | Images::Option::Blurred;
|
||||
auto cornerOptions = [](RectParts corners) {
|
||||
return (corners & RectPart::TopLeft ? Images::Option::RoundedTopLeft : Images::Option::None)
|
||||
| (corners & RectPart::TopRight ? Images::Option::RoundedTopRight : Images::Option::None)
|
||||
| (corners & RectPart::BottomLeft ? Images::Option::RoundedBottomLeft : Images::Option::None)
|
||||
| (corners & RectPart::BottomRight ? Images::Option::RoundedBottomRight : Images::Option::None);
|
||||
};
|
||||
if (radius == ImageRoundRadius::Large) {
|
||||
options |= Images::Option::RoundedLarge | cornerOptions(corners);
|
||||
} else if (radius == ImageRoundRadius::Small) {
|
||||
options |= Images::Option::RoundedSmall | cornerOptions(corners);
|
||||
} else if (radius == ImageRoundRadius::Ellipse) {
|
||||
options |= Images::Option::Circled | cornerOptions(corners);
|
||||
}
|
||||
|
||||
auto k = Images::SinglePixKey(options);
|
||||
auto i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend() || i->width() != (outerw * cIntRetinaFactor()) || i->height() != (outerh * cIntRetinaFactor())) {
|
||||
if (i != _sizesCache.cend()) {
|
||||
Images::GlobalAcquiredSize -= int64(i->width()) * i->height() * 4;
|
||||
}
|
||||
auto p = pixNoCache(origin, w, h, options, outerw, outerh);
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
QPixmap Image::pixNoCache(
|
||||
Data::FileOrigin origin,
|
||||
int w,
|
||||
int h,
|
||||
Images::Options options,
|
||||
int outerw,
|
||||
int outerh,
|
||||
const style::color *colored) const {
|
||||
if (!loading()) {
|
||||
const_cast<Image*>(this)->load(origin);
|
||||
}
|
||||
checkSource();
|
||||
|
||||
if (_data.isNull()) {
|
||||
if (h <= 0 && height() > 0) {
|
||||
h = qRound(width() * w / float64(height()));
|
||||
}
|
||||
return Blank()->pixNoCache(origin, w, h, options, outerw, outerh);
|
||||
}
|
||||
|
||||
if (isNull() && outerw > 0 && outerh > 0) {
|
||||
outerw *= cIntRetinaFactor();
|
||||
outerh *= cIntRetinaFactor();
|
||||
|
||||
QImage result(outerw, outerh, QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(cRetinaFactor());
|
||||
|
||||
{
|
||||
QPainter p(&result);
|
||||
if (w < outerw) {
|
||||
p.fillRect(0, 0, (outerw - w) / 2, result.height(), st::imageBg);
|
||||
p.fillRect(((outerw - w) / 2) + w, 0, result.width() - (((outerw - w) / 2) + w), result.height(), st::imageBg);
|
||||
}
|
||||
if (h < outerh) {
|
||||
p.fillRect(qMax(0, (outerw - w) / 2), 0, qMin(result.width(), w), (outerh - h) / 2, st::imageBg);
|
||||
p.fillRect(qMax(0, (outerw - w) / 2), ((outerh - h) / 2) + h, qMin(result.width(), w), result.height() - (((outerh - h) / 2) + h), st::imageBg);
|
||||
}
|
||||
p.fillRect(qMax(0, (outerw - w) / 2), qMax(0, (outerh - h) / 2), qMin(result.width(), w), qMin(result.height(), h), st::imageBgTransparent);
|
||||
}
|
||||
|
||||
auto corners = [](Images::Options options) {
|
||||
return ((options & Images::Option::RoundedTopLeft) ? RectPart::TopLeft : RectPart::None)
|
||||
| ((options & Images::Option::RoundedTopRight) ? RectPart::TopRight : RectPart::None)
|
||||
| ((options & Images::Option::RoundedBottomLeft) ? RectPart::BottomLeft : RectPart::None)
|
||||
| ((options & Images::Option::RoundedBottomRight) ? RectPart::BottomRight : RectPart::None);
|
||||
};
|
||||
if (options & Images::Option::Circled) {
|
||||
Images::prepareCircle(result);
|
||||
} else if (options & Images::Option::RoundedLarge) {
|
||||
Images::prepareRound(result, ImageRoundRadius::Large, corners(options));
|
||||
} else if (options & Images::Option::RoundedSmall) {
|
||||
Images::prepareRound(result, ImageRoundRadius::Small, corners(options));
|
||||
}
|
||||
if (options & Images::Option::Colored) {
|
||||
Assert(colored != nullptr);
|
||||
result = Images::prepareColored(*colored, std::move(result));
|
||||
}
|
||||
return App::pixmapFromImageInPlace(std::move(result));
|
||||
}
|
||||
|
||||
return App::pixmapFromImageInPlace(Images::prepare(_data, w, h, options, outerw, outerh, colored));
|
||||
}
|
||||
|
||||
QPixmap Image::pixColoredNoCache(
|
||||
Data::FileOrigin origin,
|
||||
style::color add,
|
||||
int32 w,
|
||||
int32 h,
|
||||
bool smooth) const {
|
||||
if (!loading()) {
|
||||
const_cast<Image*>(this)->load(origin);
|
||||
}
|
||||
checkSource();
|
||||
|
||||
if (_data.isNull()) {
|
||||
return Blank()->pix(origin);
|
||||
}
|
||||
|
||||
auto img = _data;
|
||||
if (w <= 0 || !width() || !height() || (w == width() && (h <= 0 || h == height()))) {
|
||||
return App::pixmapFromImageInPlace(Images::prepareColored(add, std::move(img)));
|
||||
}
|
||||
if (h <= 0) {
|
||||
return App::pixmapFromImageInPlace(Images::prepareColored(add, img.scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation)));
|
||||
}
|
||||
return App::pixmapFromImageInPlace(Images::prepareColored(add, img.scaled(w, h, Qt::IgnoreAspectRatio, smooth ? Qt::SmoothTransformation : Qt::FastTransformation)));
|
||||
}
|
||||
|
||||
QPixmap Image::pixBlurredColoredNoCache(
|
||||
Data::FileOrigin origin,
|
||||
style::color add,
|
||||
int32 w,
|
||||
int32 h) const {
|
||||
if (!loading()) {
|
||||
const_cast<Image*>(this)->load(origin);
|
||||
}
|
||||
checkSource();
|
||||
|
||||
if (_data.isNull()) {
|
||||
return Blank()->pix(origin);
|
||||
}
|
||||
|
||||
auto img = Images::prepareBlur(_data);
|
||||
if (h <= 0) {
|
||||
img = img.scaledToWidth(w, Qt::SmoothTransformation);
|
||||
} else {
|
||||
img = img.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
return App::pixmapFromImageInPlace(Images::prepareColored(add, img));
|
||||
}
|
||||
|
||||
std::optional<Storage::Cache::Key> Image::cacheKey() const {
|
||||
return _source->cacheKey();
|
||||
}
|
||||
|
||||
bool Image::loaded() const {
|
||||
checkSource();
|
||||
return !_data.isNull();
|
||||
}
|
||||
|
||||
void Image::checkSource() const {
|
||||
auto data = _source->takeLoaded();
|
||||
if (_data.isNull() && !data.isNull()) {
|
||||
invalidateSizeCache();
|
||||
_data = std::move(data);
|
||||
if (!_data.isNull()) {
|
||||
Images::GlobalAcquiredSize += int64(_data.width()) * _data.height() * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Image::forget() const {
|
||||
_source->takeLoaded();
|
||||
_source->forget();
|
||||
invalidateSizeCache();
|
||||
if (!_data.isNull()) {
|
||||
Images::GlobalAcquiredSize -= int64(_data.width()) * _data.height() * 4;
|
||||
_data = QImage();
|
||||
}
|
||||
}
|
||||
|
||||
void Image::setDelayedStorageLocation(
|
||||
Data::FileOrigin origin,
|
||||
const StorageImageLocation &location) {
|
||||
_source->setDelayedStorageLocation(location);
|
||||
if (!loaded()) {
|
||||
_source->performDelayedLoad(origin);
|
||||
}
|
||||
}
|
||||
|
||||
void Image::setImageBytes(const QByteArray &bytes) {
|
||||
_source->setImageBytes(bytes);
|
||||
checkSource();
|
||||
}
|
||||
|
||||
void Image::invalidateSizeCache() const {
|
||||
for (const auto &image : std::as_const(_sizesCache)) {
|
||||
if (!image.isNull()) {
|
||||
Images::GlobalAcquiredSize -= int64(image.width()) * image.height() * 4;
|
||||
}
|
||||
}
|
||||
_sizesCache.clear();
|
||||
}
|
||||
|
||||
Image::~Image() {
|
||||
forget();
|
||||
}
|
Reference in New Issue
Block a user