├── .gitignore ├── LICENSE ├── PsRemoteImage.uplugin ├── README.md ├── Resources └── Icon128.png └── Source └── PsRemoteImage ├── Classes ├── PsRemoteImage.h ├── PsRemoteImageLibrary.h └── SImageWithThrobber.h ├── Private ├── PsRemoteImage.cpp ├── PsRemoteImageLibrary.cpp ├── PsRemoteImageModule.cpp ├── PsRemoteImagePrivatePCH.h └── SImageWithThrobber.cpp ├── PsRemoteImage.Build.cs └── Public └── PsRemoteImageModule.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Visual Studio 2015 database file 5 | *.VC.db 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.ipa 36 | 37 | # These project files can be generated by the engine 38 | *.xcodeproj 39 | *.xcworkspace 40 | *.sln 41 | *.suo 42 | *.opensdf 43 | *.sdf 44 | *.VC.db 45 | *.VC.opendb 46 | 47 | # Precompiled Assets 48 | SourceArt/**/*.png 49 | SourceArt/**/*.tga 50 | 51 | # Binary Files 52 | Binaries/* 53 | 54 | # Builds 55 | Build/* 56 | 57 | # Whitelist PakBlacklist-.txt files 58 | !Build/*/ 59 | Build/*/** 60 | !Build/*/PakBlacklist*.txt 61 | 62 | # Don't ignore icon files in Build 63 | !Build/**/*.ico 64 | 65 | # Built data for maps 66 | *_BuiltData.uasset 67 | 68 | # Configuration files generated by the Editor 69 | Saved/* 70 | 71 | # Compiled source files for the engine to use 72 | Intermediate/* 73 | 74 | # Cache files for the editor to use 75 | DerivedDataCache/* 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pushkin Game Studio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PsRemoteImage.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion" : 3, 3 | 4 | "FriendlyName" : "PsRemoteImage", 5 | "Version" : 2, 6 | "VersionName" : "1.1", 7 | "CreatedBy" : "Pushkin Studio", 8 | "CreatedByURL" : "https://github.com/PushkinStudio", 9 | "EngineVersion" : "4.20.0", 10 | "Description" : "", 11 | "Category" : "", 12 | "MarketplaceURL" : "", 13 | 14 | "Modules" : 15 | [ 16 | { 17 | "Name" : "PsRemoteImage", 18 | "Type" : "Runtime", 19 | "LoadingPhase": "PreDefault" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PsRemoteImage 2 | 3 | UPsRemoteImage is a UMG wrapper for "HTTP/REST image" with caching. 4 | 5 | ## Usage: 6 | 7 | Just add an instance of `UPsRemoteImage` on palette and specify URL. 8 | Should you wish to clear local image cache, please use the appropriate `UPsRemoteImageLibrary` function. 9 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PushkinStudio/PsRemoteImage/f81a3c5356ad21d9729a679c89d3a68904f518c3/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/PsRemoteImage/Classes/PsRemoteImage.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/Widget.h" 7 | #include "Http.h" 8 | 9 | #include "PsRemoteImage.generated.h" 10 | 11 | class SImageWithThrobber; 12 | struct FSlateDynamicImageBrush; 13 | 14 | DECLARE_DYNAMIC_MULTICAST_DELEGATE(FImageLoaded); 15 | 16 | 17 | /** 18 | * UPsRemoteImage is a UMG wrapper for "HTTP/REST image" with caching. 19 | * 20 | * Usage: 21 | * Just add an instance of UPsRemoteImage on palette and specify URL. 22 | * Should you wish to clear local image cache, please use the appropriate UPsRemoteImageLibrary function. 23 | */ 24 | UCLASS() 25 | class PSREMOTEIMAGE_API UPsRemoteImage : public UWidget 26 | { 27 | GENERATED_UCLASS_BODY() 28 | 29 | public: 30 | 31 | #if WITH_EDITOR 32 | /** Get the palette category of the widget */ 33 | const FText GetPaletteCategory() override; 34 | #endif 35 | 36 | /** Set image URL */ 37 | UFUNCTION(BlueprintCallable, Category=PSRemoteImage) 38 | void SetURL(FString InURL); 39 | 40 | /** Show image loading progress indicator */ 41 | UFUNCTION(BlueprintCallable, Category=PSRemoteImage) 42 | void SetShowProgress(bool bInShowProgress); 43 | 44 | /** Image loaded delegate */ 45 | UPROPERTY(BlueprintAssignable) 46 | FImageLoaded OnImageLoaded; 47 | 48 | protected: 49 | /** Begin UWidget Interface */ 50 | TSharedRef RebuildWidget() override; 51 | /** End UWidget Interface */ 52 | 53 | /** Begin UVisual Interface */ 54 | void ReleaseSlateResources(bool bReleaseChildren) override; 55 | /** End UVisual Interface */ 56 | 57 | /** Apply properties to widget */ 58 | void SynchronizeProperties() override; 59 | 60 | protected: 61 | /** Initial image URL */ 62 | UPROPERTY(EditAnywhere, Category=PSRemoteImage) 63 | FString URL; 64 | 65 | /** Toggle show progress indicator while loading */ 66 | UPROPERTY(EditAnywhere, Category=PSRemoteImage) 67 | bool bShowProgress; 68 | 69 | private: 70 | /** Load possibly cached image with URL */ 71 | void LoadImage(FString InURL); 72 | 73 | /** Load image complete callback */ 74 | void LoadImageComplete(); 75 | 76 | /** Check whether image is cached */ 77 | bool IsImageCached(const FString& InURL) const; 78 | 79 | /** Get cache file filename */ 80 | FString GetCacheFilename(const FString& InURL) const; 81 | 82 | /** Add image data to cache */ 83 | void AsyncAddImageToCache(const FString& InURL, const TArray& InImageData); 84 | 85 | /** Get cache file creation date */ 86 | FString GetCacheDate(const FString& InURL) const; 87 | 88 | /** Remove image from cache */ 89 | void RemoveImageFromCache(const FString& InURL); 90 | 91 | /** Load image data from cache */ 92 | void AsyncLoadCachedImage(const FString& InURL); 93 | 94 | /** Success callback of loading cached image */ 95 | void LoadCachedImageSuccess(const FString& InURL); 96 | 97 | /** Success callback of loading cached image */ 98 | void LoadCachedImageFailure(const FString& InURL); 99 | 100 | /** Download image from network */ 101 | void AsyncDownloadImage(const FString& InURL); 102 | 103 | /** Callback of downloading image */ 104 | void DownloadImage_HttpRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); 105 | 106 | /** Download image from network if it is newer that cached file */ 107 | void AsyncDownloadImageCheckCache(const FString& InURL); 108 | 109 | /** Callback of downloading image with cache check */ 110 | void DownloadImageCheckCache_HttpRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); 111 | 112 | /** Update Slate image with image data */ 113 | bool SetImageData(const TArray& InImageData); 114 | 115 | /** Create brush from image data */ 116 | TSharedPtr CreateBrush(const TArray& InImageData) const; 117 | 118 | private: 119 | /** Native Slate Widget */ 120 | TSharedPtr SlateImage; 121 | 122 | /** Brush to draw */ 123 | TSharedPtr Brush; 124 | 125 | /** Loading in progress */ 126 | bool bWorking; 127 | 128 | /** URL has changed while working */ 129 | bool bPendingWork; 130 | 131 | /** Image data buffer */ 132 | TArray ImageData; 133 | }; 134 | -------------------------------------------------------------------------------- /Source/PsRemoteImage/Classes/PsRemoteImageLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Kismet/BlueprintFunctionLibrary.h" 6 | #include "PsRemoteImageLibrary.generated.h" 7 | 8 | UCLASS() 9 | class PSREMOTEIMAGE_API UPsRemoteImageLibrary : public UBlueprintFunctionLibrary 10 | { 11 | GENERATED_UCLASS_BODY() 12 | 13 | public: 14 | /** Get cached directory path */ 15 | static const FString& GetCacheDirectory(); 16 | 17 | /** Clear images cache */ 18 | UFUNCTION(BlueprintCallable, Category = PSRemoteImage) 19 | static void ClearImageCache(); 20 | }; 21 | -------------------------------------------------------------------------------- /Source/PsRemoteImage/Classes/SImageWithThrobber.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Widgets/DeclarativeSyntaxSupport.h" 6 | #include "Widgets/SCompoundWidget.h" 7 | 8 | class SImage; 9 | class SCircularThrobber; 10 | 11 | class SImageWithThrobber : public SCompoundWidget 12 | { 13 | public: 14 | SLATE_BEGIN_ARGS(SImageWithThrobber) 15 | {} 16 | 17 | /** Image resource */ 18 | SLATE_ATTRIBUTE(const FSlateBrush*, Image) 19 | 20 | SLATE_END_ARGS() 21 | 22 | void Construct(const FArguments& InArgs); 23 | 24 | /** Set the Image attribute */ 25 | void SetImage(TAttribute InImage); 26 | 27 | /** Toggle progress indicator */ 28 | void SetShowProgress(bool bShow); 29 | 30 | private: 31 | /** Image object */ 32 | TSharedPtr Image; 33 | 34 | /** Progress indicator object */ 35 | TSharedPtr Progress; 36 | }; 37 | -------------------------------------------------------------------------------- /Source/PsRemoteImage/Private/PsRemoteImage.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | #include "PsRemoteImagePrivatePCH.h" 4 | #include "PsRemoteImage.h" 5 | #include "PsRemoteImageLibrary.h" 6 | #include "SImageWithThrobber.h" 7 | 8 | #include "IImageWrapper.h" 9 | #include "IImageWrapperModule.h" 10 | #include "Misc/Guid.h" 11 | #include "Misc/Paths.h" 12 | #include "Misc/FileHelper.h" 13 | #include "Misc/SecureHash.h" 14 | #include "Misc/DateTime.h" 15 | #include "Async/Async.h" 16 | #include "HAL/FileManager.h" 17 | #include "HAL/PlatformFilemanager.h" 18 | #include "Framework/Application/SlateApplication.h" 19 | #include "Brushes/SlateDynamicImageBrush.h" 20 | 21 | #define LOCTEXT_NAMESPACE "PsRemoteImageModule" 22 | 23 | UPsRemoteImage::UPsRemoteImage(const FObjectInitializer& ObjectInitializer) 24 | : Super(ObjectInitializer) 25 | { 26 | bShowProgress = true; 27 | bWorking = false; 28 | bPendingWork = false; 29 | } 30 | 31 | #if WITH_EDITOR 32 | const FText UPsRemoteImage::GetPaletteCategory() 33 | { 34 | return LOCTEXT("Common", "Common"); 35 | } 36 | #endif // WITH_EDITOR 37 | 38 | void UPsRemoteImage::SetURL(FString InURL) 39 | { 40 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s"), *PS_FUNC_LINE, *InURL); 41 | 42 | URL = InURL; 43 | LoadImage(URL); 44 | } 45 | 46 | void UPsRemoteImage::SetShowProgress(bool bInShowProgress) 47 | { 48 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %d"), *PS_FUNC_LINE, bInShowProgress); 49 | bShowProgress = bInShowProgress; 50 | } 51 | 52 | TSharedRef UPsRemoteImage::RebuildWidget() 53 | { 54 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s"), *PS_FUNC_LINE); 55 | SlateImage = SNew(SImageWithThrobber); 56 | return SlateImage.ToSharedRef(); 57 | } 58 | 59 | void UPsRemoteImage::ReleaseSlateResources(bool bReleaseChildren) 60 | { 61 | Super::ReleaseSlateResources(bReleaseChildren); 62 | 63 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s"), *PS_FUNC_LINE); 64 | SlateImage.Reset(); 65 | } 66 | 67 | void UPsRemoteImage::SynchronizeProperties() 68 | { 69 | Super::SynchronizeProperties(); 70 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s"), *PS_FUNC_LINE); 71 | LoadImage(URL); 72 | } 73 | 74 | void UPsRemoteImage::LoadImage(FString InURL) 75 | { 76 | if (InURL.IsEmpty()) 77 | { 78 | LoadImageComplete(); 79 | return; 80 | } 81 | 82 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s"), *PS_FUNC_LINE, *InURL); 83 | 84 | if (bWorking) 85 | { 86 | bPendingWork = true; 87 | UE_LOG(LogPsRemoteImage, Warning, TEXT("%s work in progress, postponing image loading"), *PS_FUNC_LINE); 88 | return; 89 | } 90 | 91 | bWorking = true; 92 | 93 | if (SlateImage.IsValid()) 94 | { 95 | SlateImage->SetShowProgress(bShowProgress && true); 96 | } 97 | 98 | const bool bImageCached = IsImageCached(InURL); 99 | if (bImageCached) 100 | { 101 | AsyncLoadCachedImage(InURL); 102 | } 103 | else 104 | { 105 | AsyncDownloadImage(InURL); 106 | } 107 | } 108 | 109 | void UPsRemoteImage::LoadImageComplete() 110 | { 111 | ImageData.Empty(); 112 | bWorking = false; 113 | 114 | if (SlateImage.IsValid()) 115 | { 116 | SlateImage->SetShowProgress(false); 117 | } 118 | 119 | if (bPendingWork) 120 | { 121 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s bPendingWork true, URL %s"), *PS_FUNC_LINE, *URL); 122 | bPendingWork = false; 123 | LoadImage(URL); 124 | } 125 | else 126 | { 127 | OnImageLoaded.Broadcast(); 128 | } 129 | } 130 | 131 | bool UPsRemoteImage::IsImageCached(const FString& InURL) const 132 | { 133 | const bool bCached = !InURL.IsEmpty() && FPaths::FileExists(GetCacheFilename(InURL)); 134 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s URL %s : %d"), *PS_FUNC_LINE, *InURL, bCached); 135 | 136 | return bCached; 137 | } 138 | 139 | FString UPsRemoteImage::GetCacheFilename(const FString& InURL) const 140 | { 141 | if (InURL.IsEmpty()) 142 | { 143 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s empty URL"), *PS_FUNC_LINE); 144 | return FString(); 145 | } 146 | 147 | const FString Hash = FMD5::HashAnsiString(*InURL); 148 | const FString Filename = FPaths::Combine(UPsRemoteImageLibrary::GetCacheDirectory(), FString::Printf(TEXT("%s.bin"), *Hash)); 149 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s : %s"), *PS_FUNC_LINE, *InURL, *Filename); 150 | return Filename; 151 | } 152 | 153 | void UPsRemoteImage::AsyncAddImageToCache(const FString &InURL, const TArray& InImageData) 154 | { 155 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s"), *PS_FUNC_LINE, *InURL); 156 | 157 | Async(EAsyncExecution::ThreadPool, [this, InURL, &InImageData]() 158 | { 159 | const FString CacheFilename = GetCacheFilename(InURL); 160 | const bool bWritten = FFileHelper::SaveArrayToFile(InImageData, *CacheFilename); 161 | if (bWritten) 162 | { 163 | IFileManager::Get().SetTimeStamp(*CacheFilename, FDateTime::UtcNow()); 164 | } 165 | 166 | AsyncTask(ENamedThreads::GameThread, [this, InURL]() 167 | { 168 | LoadImageComplete(); 169 | }); 170 | }); 171 | } 172 | 173 | FString UPsRemoteImage::GetCacheDate(const FString& InURL) const 174 | { 175 | const FString CacheFilename = GetCacheFilename(InURL); 176 | const FDateTime ModificationDate = IFileManager::Get().GetTimeStamp(*CacheFilename); 177 | return ModificationDate.ToHttpDate(); 178 | } 179 | 180 | void UPsRemoteImage::RemoveImageFromCache(const FString& InURL) 181 | { 182 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s"), *PS_FUNC_LINE, *InURL); 183 | 184 | const FString CacheFilename = GetCacheFilename(InURL); 185 | const bool bDeleted = FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*CacheFilename); 186 | 187 | if (!bDeleted) 188 | { 189 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s can't delete cached image"), *PS_FUNC_LINE); 190 | } 191 | } 192 | 193 | void UPsRemoteImage::AsyncLoadCachedImage(const FString& InURL) 194 | { 195 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s"), *PS_FUNC_LINE, *InURL); 196 | 197 | Async(EAsyncExecution::ThreadPool, [this, InURL]() 198 | { 199 | const FString CacheFilename = GetCacheFilename(InURL); 200 | TArray ReadImageData; 201 | const bool bReadSuccess = FFileHelper::LoadFileToArray(ReadImageData, *CacheFilename); 202 | if (bReadSuccess) 203 | { 204 | ImageData = ReadImageData; 205 | 206 | AsyncTask(ENamedThreads::GameThread, [this, InURL]() 207 | { 208 | LoadCachedImageSuccess(InURL); 209 | }); 210 | } 211 | else 212 | { 213 | AsyncTask(ENamedThreads::GameThread, [this, InURL]() 214 | { 215 | LoadCachedImageFailure(InURL); 216 | }); 217 | } 218 | }); 219 | } 220 | 221 | void UPsRemoteImage::LoadCachedImageSuccess(const FString& InURL) 222 | { 223 | const bool bLoadedImage = SetImageData(ImageData); 224 | 225 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s bLoadedImage %d"), *PS_FUNC_LINE, *InURL, bLoadedImage); 226 | 227 | if (bLoadedImage) 228 | { 229 | if (SlateImage.IsValid()) 230 | { 231 | SlateImage->SetShowProgress(false); 232 | } 233 | 234 | AsyncDownloadImageCheckCache(InURL); 235 | } 236 | else 237 | { 238 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s can't load cached image, trying to download it"), *PS_FUNC_LINE); 239 | RemoveImageFromCache(InURL); 240 | AsyncDownloadImage(InURL); 241 | } 242 | } 243 | 244 | void UPsRemoteImage::LoadCachedImageFailure(const FString& InURL) 245 | { 246 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s"), *PS_FUNC_LINE, *InURL); 247 | 248 | RemoveImageFromCache(InURL); 249 | AsyncDownloadImage(InURL); 250 | } 251 | 252 | void UPsRemoteImage::AsyncDownloadImage(const FString& InURL) 253 | { 254 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s"), *PS_FUNC_LINE, *InURL); 255 | 256 | TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); 257 | 258 | HttpRequest->OnProcessRequestComplete().BindUObject(this, &UPsRemoteImage::DownloadImage_HttpRequestComplete); 259 | HttpRequest->SetURL(InURL); 260 | HttpRequest->SetVerb(TEXT("GET")); 261 | 262 | HttpRequest->ProcessRequest(); 263 | } 264 | 265 | void UPsRemoteImage::DownloadImage_HttpRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) 266 | { 267 | if (bWasSuccessful && Response.IsValid()) 268 | { 269 | ImageData = Response->GetContent(); 270 | 271 | const bool bLoadedImage = SetImageData(ImageData); 272 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s bWasSuccessful true, bLoadedImage %d"), *PS_FUNC_LINE, bLoadedImage); 273 | if (bLoadedImage) 274 | { 275 | AsyncAddImageToCache(*Request->GetURL(), ImageData); 276 | } 277 | } 278 | else 279 | { 280 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s failed to download image"), *PS_FUNC_LINE); 281 | LoadImageComplete(); 282 | } 283 | } 284 | 285 | void UPsRemoteImage::AsyncDownloadImageCheckCache(const FString& InURL) 286 | { 287 | TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); 288 | 289 | HttpRequest->OnProcessRequestComplete().BindUObject(this, &UPsRemoteImage::DownloadImageCheckCache_HttpRequestComplete); 290 | HttpRequest->SetURL(InURL); 291 | HttpRequest->SetVerb(TEXT("GET")); 292 | 293 | const FString CacheFileDate = GetCacheDate(InURL); 294 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s %s CacheFileDate %s"), *PS_FUNC_LINE, *InURL, *CacheFileDate); 295 | HttpRequest->SetHeader(TEXT("If-Modified-Since"), CacheFileDate); 296 | 297 | HttpRequest->ProcessRequest(); 298 | } 299 | 300 | void UPsRemoteImage::DownloadImageCheckCache_HttpRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) 301 | { 302 | if (Response.IsValid()) 303 | { 304 | if (Response->GetResponseCode() == 304) 305 | { 306 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s 304 Not Modified"), *PS_FUNC_LINE); 307 | LoadImageComplete(); 308 | } 309 | else 310 | { 311 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s image modified on server (code %d)"), *PS_FUNC_LINE, Response->GetResponseCode()); 312 | DownloadImage_HttpRequestComplete(Request, Response, bWasSuccessful); 313 | } 314 | } 315 | else 316 | { 317 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s invalid response"), *PS_FUNC_LINE); 318 | LoadImageComplete(); 319 | } 320 | } 321 | 322 | bool UPsRemoteImage::SetImageData(const TArray& InImageData) 323 | { 324 | if (SlateImage.IsValid()) 325 | { 326 | SlateImage->SetImage(nullptr); 327 | } 328 | else 329 | { 330 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s invalid SlateImage"), *PS_FUNC_LINE); 331 | return false; 332 | } 333 | 334 | Brush = CreateBrush(InImageData); 335 | 336 | if (Brush.IsValid()) 337 | { 338 | SlateImage->SetImage(Brush.Get()); 339 | return true; 340 | } 341 | else 342 | { 343 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s invalid Brush"), *PS_FUNC_LINE); 344 | return false; 345 | } 346 | } 347 | 348 | TSharedPtr UPsRemoteImage::CreateBrush(const TArray& InImageData) const 349 | { 350 | UE_LOG(LogPsRemoteImage, Verbose, TEXT("%s"), *PS_FUNC_LINE); 351 | 352 | TSharedPtr BrushRet; 353 | 354 | IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); 355 | const EImageFormat ImageType = ImageWrapperModule.DetectImageFormat(InImageData.GetData(), InImageData.Num()); 356 | TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(ImageType); 357 | 358 | if (!ImageWrapper.IsValid()) 359 | { 360 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s can't create image wrapper"), *PS_FUNC_LINE); 361 | return nullptr; 362 | } 363 | else if (ImageWrapper->SetCompressed(InImageData.GetData(), InImageData.Num())) 364 | { 365 | const int32 BytesPerPixel = ImageWrapper->GetBitDepth(); 366 | const TArray* RawData = nullptr; 367 | 368 | if (ImageWrapper->GetRaw(ERGBFormat::BGRA, BytesPerPixel, RawData) && RawData && RawData->Num() > 0) 369 | { 370 | const FName ResourceName = FName(*FString::Printf(TEXT("PSRemoteImage_%s"), *FGuid::NewGuid().ToString())); 371 | 372 | if (FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(ResourceName, ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), *RawData)) 373 | { 374 | BrushRet = MakeShareable(new FSlateDynamicImageBrush(ResourceName, FVector2D(ImageWrapper->GetWidth(), ImageWrapper->GetHeight()))); 375 | return BrushRet; 376 | } 377 | else 378 | { 379 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s can't generate resource"), *PS_FUNC_LINE); 380 | return nullptr; 381 | } 382 | } 383 | else 384 | { 385 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s can't get raw data"), *PS_FUNC_LINE); 386 | return nullptr; 387 | } 388 | } 389 | else 390 | { 391 | UE_LOG(LogPsRemoteImage, Error, TEXT("%s can't load compressed data"), *PS_FUNC_LINE); 392 | return nullptr; 393 | } 394 | } 395 | 396 | #undef LOCTEXT_NAMESPACE 397 | -------------------------------------------------------------------------------- /Source/PsRemoteImage/Private/PsRemoteImageLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | #include "PsRemoteImagePrivatePCH.h" 4 | #include "PsRemoteImageLibrary.h" 5 | #include "Misc/Paths.h" 6 | #include "HAL/PlatformFilemanager.h" 7 | 8 | UPsRemoteImageLibrary::UPsRemoteImageLibrary(const FObjectInitializer& ObjectInitializer) 9 | : Super(ObjectInitializer) 10 | { 11 | } 12 | 13 | const FString& UPsRemoteImageLibrary::GetCacheDirectory() 14 | { 15 | static const FString Path = FPaths::Combine(FPaths::ProjectIntermediateDir(), TEXT("PSRemoteImage"), TEXT("Cache")); 16 | return Path; 17 | } 18 | 19 | void UPsRemoteImageLibrary::ClearImageCache() 20 | { 21 | const auto& Path = GetCacheDirectory(); 22 | FPlatformFileManager::Get().GetPlatformFile().DeleteDirectoryRecursively(*Path); 23 | } 24 | -------------------------------------------------------------------------------- /Source/PsRemoteImage/Private/PsRemoteImageModule.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | #include "PsRemoteImagePrivatePCH.h" 4 | #include "PsRemoteImageModule.h" 5 | 6 | #define LOCTEXT_NAMESPACE "PsRemoteImageModule" 7 | 8 | void PsRemoteImageModule::StartupModule() 9 | { 10 | } 11 | 12 | void PsRemoteImageModule::ShutdownModule() 13 | { 14 | } 15 | 16 | #undef LOCTEXT_NAMESPACE 17 | 18 | IMPLEMENT_MODULE(PsRemoteImageModule, PsRemoteImage); 19 | 20 | DEFINE_LOG_CATEGORY(LogPsRemoteImage); 21 | 22 | #undef LOCTEXT_NAMESPACE 23 | -------------------------------------------------------------------------------- /Source/PsRemoteImage/Private/PsRemoteImagePrivatePCH.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Runtime/Launch/Resources/Version.h" 6 | #include "CoreMinimal.h" 7 | 8 | // You should place include statements to your module's private header files here. You only need to 9 | // add includes for headers that are used in most of your module's source files though. 10 | #include "Modules/ModuleManager.h" 11 | 12 | DECLARE_LOG_CATEGORY_EXTERN(LogPsRemoteImage, Log, All); 13 | 14 | #define PS_FUNC (FString(__FUNCTION__)) // Current Class Name + Function Name where this is called 15 | #define PS_LINE (FString::FromInt(__LINE__)) // Current Line Number in the code where this is called 16 | #define PS_FUNC_LINE (PS_FUNC + "(" + PS_LINE + ")") // Current Class and Line Number where this is called! 17 | -------------------------------------------------------------------------------- /Source/PsRemoteImage/Private/SImageWithThrobber.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | #include "PsRemoteImagePrivatePCH.h" 4 | #include "SImageWithThrobber.h" 5 | #include "Widgets/Images/SImage.h" 6 | #include "Widgets/Images/SThrobber.h" 7 | 8 | void SImageWithThrobber::Construct(const FArguments& InArgs) 9 | { 10 | ChildSlot 11 | [ 12 | SNew(SOverlay) 13 | 14 | + SOverlay::Slot() 15 | .HAlign(HAlign_Center) 16 | .VAlign(VAlign_Center) 17 | [ 18 | SAssignNew(Progress, SCircularThrobber) 19 | ] 20 | 21 | + SOverlay::Slot() 22 | [ 23 | SAssignNew(Image, SImage) 24 | .Visibility(EVisibility::Collapsed) 25 | ] 26 | ]; 27 | } 28 | 29 | void SImageWithThrobber::SetImage(TAttribute InImage) 30 | { 31 | Image->SetImage(InImage); 32 | } 33 | 34 | void SImageWithThrobber::SetShowProgress(bool bShow) 35 | { 36 | Progress->SetVisibility(bShow ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed); 37 | Image->SetVisibility(bShow ? EVisibility::Collapsed : EVisibility::SelfHitTestInvisible); 38 | } 39 | -------------------------------------------------------------------------------- /Source/PsRemoteImage/PsRemoteImage.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | using System.IO; 4 | 5 | namespace UnrealBuildTool.Rules 6 | { 7 | public class PsRemoteImage : ModuleRules 8 | { 9 | public PsRemoteImage(ReadOnlyTargetRules Target) : base(Target) 10 | { 11 | //PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 12 | 13 | PrivateIncludePaths.AddRange( 14 | new string[] { 15 | "PsRemoteImage/Private", 16 | }); 17 | 18 | PublicDependencyModuleNames.AddRange( 19 | new string[] 20 | { 21 | "Core", 22 | "CoreUObject", 23 | "Engine" 24 | }); 25 | 26 | PrivateDependencyModuleNames.AddRange( 27 | new string[] { 28 | "ImageWrapper", 29 | "HTTP", 30 | "Slate", 31 | "SlateCore", 32 | "UMG" 33 | } 34 | ); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Source/PsRemoteImage/Public/PsRemoteImageModule.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2018 Mail.Ru Group. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class PsRemoteImageModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | virtual void StartupModule() override; 13 | virtual void ShutdownModule() override; 14 | }; 15 | --------------------------------------------------------------------------------